@cocreate/elements 1.43.2 → 1.44.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.
package/src/index.js CHANGED
@@ -21,1474 +21,30 @@
21
21
  * you must obtain a commercial license from CoCreate LLC.
22
22
  * For details, visit <https://cocreate.app/licenses/> or contact us at sales@cocreate.app.
23
23
  */
24
+ import { init, initElement, initSocket } from "./initHandler.js";
25
+ import { initDataKey, getDataKey, getDataElements, getDataKeys, getData, getDataObject } from "./getDataHandler.js";
26
+ import { read, save } from "./crudHandler.js";
27
+ import { setDataType, setData } from "./setDataHandler.js";
28
+ import { reset } from "./formHandler.js";
24
29
 
25
- import Observer from "@cocreate/observer";
26
- import Actions from "@cocreate/actions";
27
- import CRUD from "@cocreate/crud-client";
28
- import {
29
- dotNotationToObject,
30
- queryElements,
31
- queryData,
32
- sortData,
33
- getAttributes,
34
- getAttributeNames,
35
- checkValue,
36
- getValueFromObject
37
- } from "@cocreate/utils";
38
- import filter from "@cocreate/filter";
39
- import { render, renderValue, renderedNodes } from "@cocreate/render";
40
30
  import "@cocreate/element-prototype";
41
- import "./fetchSrc.js";
42
- import { reset } from "./form.js";
43
- import "./value.js";
44
-
45
- const selector = "[storage], [database], [array], [render-json]";
46
- const elements = new WeakMap();
47
- const forms = new WeakMap();
48
- const keys = new Map();
49
- const debounce = new Map();
50
-
51
- /**
52
- * Initializes elements with specific CRUD attributes. If no parameter is provided, it queries and initializes all elements
53
- * that have certain CRUD-related attributes. It can also initialize a single element or an array of elements.
54
- *
55
- * Attributes used for initialization are:
56
- * - storage: Specifies the storage mechanism.
57
- * - database: Indicates the database to interact with.
58
- * - array: Defines the array to be used.
59
- * - render-json: Determines how JSON data is rendered.
60
- *
61
- * @param {(Element|Element[])} [elements] - Optional. A single element or an array of elements to initialize.
62
- * If omitted, the function queries and initializes all elements containing
63
- * any of these CRUD attributes: "storage", "database", "array", "render-json".
64
- */
65
- async function init(element) {
66
- if (
67
- element &&
68
- !(element instanceof HTMLCollection) &&
69
- !(element instanceof NodeList) &&
70
- !Array.isArray(element)
71
- ) {
72
- element = [element];
73
- } else if (!element) {
74
- element = document.querySelectorAll(selector);
75
- initSocket();
76
- }
77
-
78
- let promises = [];
79
- for (let i = 0; i < element.length; i++) {
80
- if (
81
- elements.has(element[i]) ||
82
- element[i].tagName === "FORM" ||
83
- element[i].getAttribute("crud") === "false"
84
- )
85
- continue;
86
-
87
- promises.push(
88
- (async () => {
89
- let data = await initElement(element[i]);
90
- if (data) {
91
- let dataKey = initDataKey(element[i], data);
92
- return { string: dataKey.string, data };
93
- }
94
- })()
95
- );
96
- }
97
-
98
- let results = await Promise.all(promises);
99
- let dataObjects = new Map();
100
-
101
- for (let res of results) {
102
- if (res) dataObjects.set(res.string, res.data);
103
- }
104
-
105
- if (dataObjects && dataObjects.size > 0) {
106
- for (let key of dataObjects.keys()) {
107
- let { data, elements: mappedElements } = keys.get(key);
108
- read(Array.from(mappedElements.keys()), data, { string: key });
109
- }
110
- }
111
- }
112
-
113
- async function initElement(el) {
114
- let data = getObject(el);
115
- if (!data || !data.type) return;
116
-
117
- if (
118
- data.object &&
119
- data.object !== "pending" &&
120
- !/^[0-9a-fA-F]{24}$/.test(data.object)
121
- )
122
- return;
123
-
124
- if (!elements.has(el)) {
125
- elements.set(el, "");
126
-
127
- if (!el.elementsInputEvent) {
128
- el.elementsInputEvent = true;
129
-
130
- if (
131
- ["INPUT", "TEXTAREA", "SELECT"].includes(el.tagName) ||
132
- (el.hasAttribute("contenteditable") &&
133
- el.getAttribute("contenteditable") !== "false") ||
134
- el.contenteditable || el.value !== undefined || el.hasAttribute("value")
135
- ) {
136
- if (el.tagName == "IFRAME") {
137
- // ToDo: saving on contenteditable elements when an objectId does not exist in order to crud or crdt
138
- el = el.contentDocument.documentElement;
139
- el.setAttribute("contenteditable", "true");
140
- }
141
-
142
- el.addEventListener("input", function (e) {
143
- if (el.pendingObject) return;
144
-
145
- const { object, key, isRealtime, isCrdt } =
146
- getAttributes(el);
147
- if (!isRealtime || isRealtime === "false" || key === "_id")
148
- return;
149
- if (isCrdt == "true" && object && object !== "pending")
150
- return;
151
- if (e.detail && e.detail.skip == true) return;
152
- if (data.type !== "object" && data[data.type] === "pending")
153
- return;
154
- if (!el.value && !el.hasAttribute("value") && el.contenteditable && !el.hasAttribute("contenteditable")) return;
155
- save(el);
156
- });
157
- }
158
- }
159
- }
160
-
161
- await filter.init(el);
162
-
163
- if (el.getFilter) {
164
- data.$filter = await el.getFilter();
165
-
166
- el.setFilter = (filter) => {
167
- data.$filter = filter;
168
- el.hasRead = false;
169
- read(el, data);
170
- };
171
- }
172
-
173
- return data;
174
- }
175
-
176
- function getObject(element) {
177
- const data = getAttributes(element);
178
- const crudType = ["storage", "database", "array", "index", "object"];
179
-
180
- for (let i = 0; i < crudType.length; i++) {
181
- if (!checkValue(data[crudType[i]])) return;
182
-
183
- if (data[crudType[i]] && data[crudType[i]].includes(",")) {
184
- const array = data[crudType[i]].split(",");
185
- data[crudType[i]] = [];
186
-
187
- for (let j = 0; j < array.length; j++) {
188
- array[j].trim();
189
- if (crudType[i] === "object") {
190
- data[crudType[i]].push({ _id: array[j] });
191
- } else {
192
- data[crudType[i]].push(array[j]);
193
- }
194
- }
195
- }
196
- }
197
-
198
- if (data.object || data.object === "" || data.key || data.key === "") {
199
- if (!data.array || (data.array && !data.array.length)) return;
200
- data.type = "object";
201
- } else if (data.index || data.index === "") {
202
- if (!data.array || (data.array && !data.array.length)) return;
203
- data.type = "index";
204
- } else if (data.array || data.array === "") data.type = "array";
205
- else if (data.database || data.database === "") data.type = "database";
206
- else if (data.storage || data.storage === "") data.type = "storage";
207
- else if (data.data) data.type = "data";
208
-
209
- delete data.isRealtime;
210
- return data;
211
- }
212
-
213
- function initDataKey(element, data) {
214
- let dataKey = getDataKey(data);
215
- if (keys.has(dataKey.string))
216
- keys.get(dataKey.string).elements.set(element, "");
217
- else
218
- keys.set(dataKey.string, {
219
- elements: new Map([[element, ""]]),
220
- data,
221
- dataKey
222
- });
223
-
224
- elements.set(element, dataKey.string);
225
-
226
- if (element.parentElement) {
227
- let form = element.closest("form");
228
- if (form) {
229
- if (!form.save) form.save = () => save(form);
230
-
231
- if (!form.getData) form.getData = () => getData(form);
232
-
233
- let formObject = forms.get(form);
234
- if (formObject) {
235
- formObject.elements.set(element, data);
236
- if (formObject.types.has(data.type))
237
- formObject.types.get(data.type).set(element, data);
238
- else
239
- formObject.types.set(data.type, new Map([[element, data]]));
240
- } else
241
- forms.set(form, {
242
- elements: new Map([[element, data]]),
243
- types: new Map([[data.type, new Map([[element, data]])]])
244
- });
245
- }
246
- }
247
-
248
- if (!element.read) element.read = () => read(element);
249
- if (!element.save) element.save = () => save(element);
250
-
251
- return dataKey;
252
- }
253
-
254
- async function read(element, data, dataKey) {
255
- if (!dataKey) dataKey = { string: elements.get(element) };
256
- if (!data) {
257
- let existingData = keys.get(dataKey.string);
258
- if (existingData && existingData.dataKey && existingData.dataKey.object)
259
- data = { ...existingData.dataKey.object };
260
- }
261
-
262
- if (!dataKey || !data.type) return;
263
- if (!data.$filter && (!data[data.type] || !data[data.type].length)) return;
264
-
265
- if (!(element instanceof HTMLCollection) && !Array.isArray(element))
266
- element = [element];
267
-
268
- let delay = debounce.get(dataKey.string);
269
- if (!delay) debounce.set(dataKey.string, (delay = {}));
270
-
271
- if (!delay.elements) delay.elements = new Map();
272
-
273
- for (let el of element) {
274
- const isRead = el.getAttribute("read");
275
- if (
276
- isRead !== "false" &&
277
- !el.hasRead &&
278
- (!data.$filter || (data.$filter && data.$filter.isFilter !== false))
279
- ) {
280
- el.hasRead = true;
281
- delay.elements.set(el, true);
282
- } else {
283
- // console.log("read skipped", el);
284
- }
285
- }
286
-
287
- if (!delay.elements.size) return;
288
-
289
- clearTimeout(delay.timer);
290
- delay.timer = setTimeout(function () {
291
- // TODO: should server support string and string array for type object, methods create, read, delete
292
- if (!data.$filter && data.type === "object") {
293
- if (!data.object) return;
294
- else if (typeof data.object === "string")
295
- data.object = { _id: data.object };
296
- else if (Array.isArray(data.object)) {
297
- if (data.object.length)
298
- data.object = { _id: data.object[0]._id };
299
- } else if (!data.object._id.match(/^[0-9a-fA-F]{24}$/)) return;
300
- // if (data.object._id.startsWith('$'))
301
- // return
302
- }
303
-
304
- data.method = data.type + ".read";
305
-
306
- CRUD.send(data).then((responseData) => {
307
- debounce.delete(dataKey.string);
308
- let els = Array.from(delay.elements.keys());
309
- clearTimeout(delay.timer);
310
- delete delay.elements;
311
- delete delay.timer;
312
-
313
- setData(els, responseData);
314
- });
315
- }, 500);
316
- }
317
-
318
- function get$in(query, mergedValues = []) {
319
- for (let key of Object.keys(query)) {
320
- if (key === "$and" || key === "$or" || key === "$nor") {
321
- for (let i = 0; i < query[key].length; i++) {
322
- sort$in(query[key][i], mergedValues);
323
- }
324
- } else if (
325
- typeof query[key] === "object" &&
326
- !Array.isArray(query[key])
327
- ) {
328
- if (query[key].$in)
329
- mergedValues = [...mergedValues, ...query[key].$in];
330
- }
331
- }
332
- return mergedValues;
333
- }
334
-
335
- function sort$in(data, query) {
336
- let mergedValues = get$in(query);
337
-
338
- if (mergedValues.length) {
339
- const sorted = mergedValues
340
- .map((name) => data.find((career) => career.name === name))
341
- .filter((career) => career !== undefined);
342
- return sorted;
343
- }
344
- }
345
-
346
- async function setData(element, data) {
347
- if (!element) {
348
- element = getDataElements(data);
349
- if (!element.length) return;
350
- }
351
- if (!(element instanceof HTMLCollection) && !Array.isArray(element))
352
- element = [element];
353
-
354
- let type = data.type;
355
- if (!type && data.method) {
356
- type = data.method.split(".")[0];
357
- } else if (type == "key") type = "object";
358
-
359
- if (data.$filter && data.$filter.query) {
360
- let sortedData = sort$in(data[type], data.$filter.query);
361
- if (sortedData && sortedData.length) data[type] = sortedData;
362
- }
363
-
364
- for (let el of element) {
365
- // if rendered in server side skip
366
- if (el.hasAttribute("rendered")) return el.removeAttribute("rendered");
367
- if (el.hasAttribute("render-json")) {
368
- console.log("render-json", "");
369
- }
370
-
371
- let reference = el.getAttribute("reference");
372
- let action = el.getAttribute("actions");
373
- if (action && ["database", "array", "object", "key"].includes(action))
374
- continue;
375
-
376
- if (data.array.includes("aiConversations")) {
377
- console.log("testing ai conversations");
378
- }
379
-
380
- const { key, isRead, isUpdate, isListen, isCrdt } = getAttributes(el);
381
- let elementType = el.getAttribute("type");
382
- if (elementType !== "file" && (el.getFilter || el.renderValue))
383
- await filterData(el, data, type, key);
384
- else {
385
- if (!data[type] || !data[type].length) {
386
- if (el.hasAttribute("value-dispatch")) el.setValue("");
387
- continue;
388
- }
389
-
390
- if (key && checkValue(key)) {
391
- let value;
392
- if (key.includes("$length")) {
393
- value = CRUD.getValueFromObject(
394
- data[type][0],
395
- key.replace(/\.\$length$/, "")
396
- );
397
- if (value) value = value.length;
398
- else value = 0;
399
- } else {
400
- let $update = data[type][0].$update;
401
- if ($update) {
402
- delete data[type][0].$update;
403
- data[type][0] = { ...data[type][0], ...$update };
404
- } else if (key === "$data") {
405
- value = data;
406
- } else if (key && key.startsWith("$data")) {
407
- value = CRUD.getValueFromObject(data, key.slice(1));
408
- } else if (key === `$${type}`) {
409
- value = data[type];
410
- } else if (key && key.startsWith(`$${type}`)) {
411
- value = CRUD.getValueFromObject(data, key.slice(1));
412
- } else if (key === "{}") {
413
- value = data[type][0];
414
- if (reference === "false") {
415
- delete data[type][0].$storage;
416
- delete data[type][0].$database;
417
- delete data[type][0].$array;
418
- }
419
- } else {
420
- value = CRUD.getValueFromObject(data[type][0], key);
421
- }
422
- }
423
-
424
- if (!data[type].length) continue;
425
- if (isRead === "false" || isCrdt === "true") continue;
426
- if (
427
- isUpdate === "false" ||
428
- (isListen === "false" && !data.method.endsWith(".read"))
429
- )
430
- continue;
431
-
432
- // TODO: object.update data returned from server will not include $operator
433
- el.setValue(value);
434
- }
435
- }
436
- }
437
- }
438
-
439
- async function filterData(element, data, type, key) {
440
- let operator = "",
441
- value;
442
-
443
- if (key && !key.includes("$length") && key !== "{}") {
444
- if (!data || !type) return;
445
-
446
- if (key.startsWith("$")) {
447
- operator = key.split(".")[0] || "";
448
- }
449
-
450
- let property = key;
451
- if (operator) {
452
- property = property.replace(operator + ".", "");
453
- if (operator === "$sum") {
454
- value = 0;
455
- }
456
- }
457
- if (Array.isArray(data[type])) {
458
- let filteredItems = [],
459
- isObject;
460
- for (let doc of data[type]) {
461
- if (!doc.$storage) continue;
462
- // TODO: should have been handled by getDataElements()
463
- if (type === "object") {
464
- let _id = element.getAttribute("object");
465
- if (_id && doc._id !== _id) continue;
466
- let $update = doc.$update;
467
- if ($update) {
468
- delete doc.$update;
469
- doc = { ...doc, ...$update };
470
- }
471
- }
472
- let docValue = getValueFromObject(doc, property);
473
- if (docValue) {
474
- if (operator === "$sum" && typeof docValue === "number") {
475
- if (typeof value !== "number") value = 0;
476
- value += docValue || 0;
477
- } else if (Array.isArray(docValue)) {
478
- filteredItems.push(...docValue);
479
- } else {
480
- isObject = true;
481
- filteredItems.push(docValue);
482
- }
483
- } else continue;
484
- }
485
-
486
- if (isObject && filteredItems.length === 1) {
487
- filteredItems = filteredItems[0];
488
- }
489
- if (!operator) data = filteredItems;
490
- } else {
491
- data = { [property]: data[type][property] };
492
- }
493
- }
494
-
495
- let isRendered = element.querySelector("[render-clone]");
496
- if (operator) {
497
- element.setValue(value);
498
- } else if (
499
- element.renderValue &&
500
- data.method &&
501
- data.method.endsWith(".read") &&
502
- data.$filter &&
503
- (data.$filter.overwrite || !isRendered)
504
- ) {
505
- await element.renderValue(data);
506
- } else if (
507
- (data.$filter && data.$filter.loadmore) ||
508
- (data.method && data.method.endsWith(".read") && isRendered)
509
- ) {
510
- await loadMore(element, data, type);
511
- } else if (
512
- element.getFilter &&
513
- data.method &&
514
- !data.method.endsWith(".read")
515
- ) {
516
- await checkFilters(element, data, type);
517
- } else if (element.renderValue) {
518
- if (key)
519
- data = {[key]: data}
520
- await element.renderValue(data);
521
- } else if (key === "$length") {
522
- element.setValue(data[type].length);
523
- } else if (key && key.includes("$length")) {
524
- let value = CRUD.getValueFromObject(
525
- data[type][0],
526
- key.replace(/\.\$length$/, "")
527
- );
528
- element.setValue(value.length);
529
- } else if (key === "$data") {
530
- value = data;
531
- } else if (key && key.startsWith("$data")) {
532
- value = CRUD.getValueFromObject(data, key.slice(1));
533
- } else if (key === `$${type}`) {
534
- value = data[type];
535
- } else if (key && key.startsWith(`$${type}`)) {
536
- value = CRUD.getValueFromObject(data, key.slice(1));
537
- } else if (key === "{}") {
538
- value = data[type][0];
539
- } else if (data) element.setValue(data);
540
-
541
- if (data.$filter) {
542
- let filterElement = filter.filters.get(element);
543
- if (filterElement) filterElement.index = data.$filter.index;
544
- }
545
-
546
- if (!data[type] || !Array.isArray(data[type]) || !data[type].length) return;
547
-
548
- const evt = new CustomEvent("fetchedData", { bubbles: true });
549
- element.dispatchEvent(evt);
550
- }
551
-
552
- async function loadMore(element, data, type, sort) {
553
- let clonedData = { ...data };
554
- let renderedData = await element.getData();
555
- if (!renderedData || !renderedData[type]) {
556
- return;
557
- }
558
- for (let i = 0; i < clonedData[type].length; i++) {
559
- let index;
560
- if (type === "object") {
561
- index = renderedData[type].findIndex(
562
- (obj) => obj._id === clonedData[type][i]._id
563
- );
564
- } else {
565
- index = renderedData[type].findIndex(
566
- (obj) => obj.name === clonedData[type][i].name
567
- );
568
- }
569
-
570
- if (index >= 0) {
571
- clonedData[type].splice(i, 1);
572
- i--; // Adjust the index to account for the removed item
573
- }
574
- }
575
- if (clonedData[type].length > 0) {
576
- await element.renderValue(clonedData);
577
- }
578
- }
579
-
580
- async function checkFilters(element, data, type) {
581
- let localData;
582
- if (!element.getData) {
583
- // TODO: fix: getObject as this is related to render or form, but filter could exist on an input which does not have getObject
584
- // TODO: generate an object of current element key: value to use with filter. because filter is used object is not defined
585
- localData = getObject(element);
586
- if (Array.isArray(localData[type])) localData = localData[type][0];
587
- } else localData = await element.getData();
588
-
589
- if (!localData) return;
590
-
591
- let newData;
592
- if (type) {
593
- localData = localData[type];
594
- newData = data[type];
595
- } else newData = data;
596
-
597
- let filter = await element.getFilter();
598
- if (filter && filter.query) {
599
- newData = newData.filter(item => queryData(item, filter.query));
600
- }
601
-
602
- if (!newData || !newData.length) return;
603
-
604
- if (Array.isArray(localData)) {
605
- if (Array.isArray(newData)) {
606
- for (let i = 0; i < newData.length; i++) {
607
- checkIndex(element, data, localData, newData[i], type, filter);
608
- }
609
- } else {
610
- checkIndex(element, data, localData, newData, type, filter);
611
- }
612
- } else {
613
- let primaryKey;
614
- if (type === "object") {
615
- primaryKey = "_id";
616
- } else {
617
- primaryKey = "name";
618
- }
619
-
620
- if (Array.isArray(newData)) {
621
- for (let i = 0; i < newData.length; i++) {
622
- if (
623
- typeof localData === "string" &&
624
- localData === newData[i][primaryKey]
625
- ) {
626
- localData = newData[i];
627
- } else if (localData[primaryKey] === newData[i][primaryKey]) {
628
- localData = dotNotationToObject(newData[i], localData);
629
- }
630
- }
631
- } else if (typeof localData === "string" && localData === newData[primaryKey]) {
632
- localData = newData;
633
- } else if (localData[primaryKey] === newData[primaryKey]) {
634
- localData = dotNotationToObject(newData, localData);
635
- }
636
-
637
- if (element.renderValue) element.renderValue(data);
638
- // render({ element, data, key: type });
639
- else if (data) element.setValue(localData);
640
- }
641
- }
642
-
643
- function checkIndex(element, data, localData, newData, type, filter) {
644
- let index;
645
- if (type === "object") {
646
- index = localData.findIndex((obj) => obj._id === newData._id);
647
- } else {
648
- index = localData.findIndex((obj) => obj.name === newData.name);
649
- }
650
-
651
- if (!data.$filter) data.$filter = {};
652
-
653
- if (data.method.endsWith(".delete")) {
654
- if (index === -1) return;
655
- data.$filter.remove = true;
656
- localData.splice(index, 1);
657
- } else {
658
- if (data.method.endsWith(".create")) {
659
- if (index === -1) {
660
- data.$filter.create = true;
661
- if (filter && filter.sort) newData.isNewData = true;
662
- else index = localData.length;
663
- localData.push(newData);
664
- }
665
- } else {
666
- if (index === -1) return;
667
- data.$filter.update = true;
668
- data.$filter.currentIndex = index;
669
- localData[index] = dotNotationToObject(newData, localData[index]);
670
- if (filter && filter.sort) {
671
- localData[index].isNewData = true;
672
- }
673
- }
674
-
675
- if (filter && filter.sort) {
676
- localData = sortData(localData, filter.sort);
677
- index = localData.findIndex((obj) => obj.isNewData);
678
- }
679
- }
680
-
681
- if (index >= 0) {
682
- if (data.$filter.currentIndex === index)
683
- delete data.$filter.currentIndex;
684
- data.$filter.index = index;
685
- if (element.renderValue) element.renderValue(data);
686
- else if (data) element.setValue(localData);
687
- }
688
- }
689
-
690
- function getDataKey(data) {
691
- let dataKey = {};
692
- let attributes = [
693
- "host",
694
- "organization_id",
695
- "apikey",
696
- "method",
697
- "type",
698
- "storage",
699
- "database",
700
- "array",
701
- "index",
702
- "object",
703
- "key",
704
- "updateKey",
705
- "$filter",
706
- "upsert",
707
- "namespace",
708
- "room",
709
- "broadcast",
710
- "broadcastSender",
711
- "broadcastBrowser"
712
- ];
713
-
714
- for (let attribute of attributes) {
715
- if (attribute === "key" && data.type === "object") continue;
716
- let value = data[attribute];
717
- if (value) {
718
- if (Array.isArray(value)) {
719
- dataKey[attribute] = [...value];
720
- if (typeof value[0] === "string") dataKey[attribute].sort(); // Sort the values alphabetically
721
- } else {
722
- dataKey[attribute] = value;
723
- }
724
- }
725
- }
726
-
727
- const object = Object.fromEntries(
728
- Object.entries(dataKey).sort(([a], [b]) => a.localeCompare(b))
729
- );
730
- const string = JSON.stringify(object);
731
-
732
- return { string, object };
733
- }
734
-
735
- function getDataElements(data) {
736
- let element = [];
737
- let matchingKeys = getDataKeys(data);
738
- for (let i = 0; i < matchingKeys.length; i++) {
739
- let matchingElements = keys.get(matchingKeys[i]);
740
- if (matchingElements?.elements?.size)
741
- element.push(...matchingElements.elements.keys());
742
- }
743
- return element;
744
- }
745
-
746
- // TODO: Correctly check for matches
747
- function getDataKeys(data) {
748
- const matchingKeyStrings = [];
749
- const targetKeys = [
750
- "type",
751
- "storage",
752
- "database",
753
- "array",
754
- "index",
755
- "object",
756
- "$filter"
757
- ];
758
-
759
- for (const [keyString, sortedKey] of keys.entries()) {
760
- let hasMatch = true;
761
-
762
- for (const key of targetKeys) {
763
- if (
764
- !data.$filter &&
765
- key === "$filter" &&
766
- sortedKey.data.hasOwnProperty(key)
767
- )
768
- hasMatch = true;
769
- else if (data.hasOwnProperty(key)) {
770
- if (!sortedKey.data.hasOwnProperty(key)) {
771
- hasMatch = false;
772
- break;
773
- }
774
- if (
775
- Array.isArray(sortedKey.data[key]) &&
776
- Array.isArray(data[key])
777
- ) {
778
- // if key is object check _id
779
- if (key === "object" && sortedKey.data.$filter)
780
- hasMatch = true;
781
- else {
782
- const matches = sortedKey.data[key].some((value) => {
783
- if (key === "object") {
784
- return data[key].some((obj) => {
785
- return obj._id === value._id;
786
- });
787
- } else {
788
- return data[key].includes(value);
789
- }
790
- });
791
-
792
- if (!matches) {
793
- hasMatch = false;
794
- break;
795
- }
796
- }
797
- } else if (sortedKey.data[key] !== data[key]) {
798
- hasMatch = false;
799
- break;
800
- }
801
- }
802
- }
803
-
804
- if (hasMatch) {
805
- matchingKeyStrings.push(keyString);
806
- }
807
- }
808
-
809
- return matchingKeyStrings;
810
- }
811
-
812
- function formatPayload(payload, value, element) {
813
- if (!payload[payload.type] && payload.key) {
814
- payload.method = payload.type + ".create";
815
- if (payload.type === "object") {
816
- if (!payload.object) payload.object = {};
817
- else if (typeof payload.object === "string")
818
- payload.object = { _id: payload.object };
819
-
820
- if (payload.key) {
821
- if (payload.key == "{}") {
822
- if (Array.isArray(value)) {
823
- payload.object = value;
824
- } else payload.object = { ...payload.object, ...value };
825
- } else payload.object[payload.key] = value;
826
- }
827
- if (payload.isUpsert && payload.$filter)
828
- payload.method = "object.update";
829
- } else payload[payload.type] = value;
830
- } else if (payload[payload.type] && payload.key) {
831
- let attributes = element.attributes;
832
- if (payload.key.startsWith("$")) {
833
- const operators = ["$rename", "$inc", "$push", "$each", "$splice", "$unset", "$delete", "$slice", "$pop", "$shift", "$addToSet", "$pull"];
834
- const matchedAttr = Array.from(attributes).find(attr => operators.includes(attr.name));
835
- if (matchedAttr) {
836
- payload.key = matchedAttr.name + "." + payload.key;
837
- }
838
- }
839
-
840
- if (payload.type === "object") {
841
- if (typeof payload[payload.type] === "string")
842
- if (payload.key == "{}") {
843
- if (Array.isArray(value))
844
- payload[payload.type] = {
845
- _id: payload[payload.type],
846
- ...value[0]
847
- };
848
- else
849
- payload[payload.type] = {
850
- _id: payload[payload.type],
851
- ...value
852
- };
853
- } else
854
- payload[payload.type] = {
855
- _id: payload[payload.type],
856
- [payload.key]: value
857
- };
858
- else if (Array.isArray(payload[payload.type]))
859
- if (payload.key == "{}")
860
- payload[payload.type][0] = {
861
- ...payload[payload.type][0],
862
- ...value
863
- };
864
- else {
865
- if (payload[payload.type][0]) {
866
- payload[payload.type][0][payload.key] = value;
867
- }
868
- if (!payload[payload.type][0]._id)
869
- payload.method = payload.type + ".create";
870
- }
871
- else if (typeof payload[payload.type] === "object")
872
- if (payload.key == "{}")
873
- payload[payload.type] = { ...payload[payload.type], ...value };
874
- else {
875
- payload[payload.type][payload.key] = value;
876
- if (!payload[payload.type]._id)
877
- payload.method = payload.type + ".create";
878
- }
879
- } else {
880
- payload[payload.type] = { [payload[payload.type]]: value };
881
- }
882
-
883
- if (payload.isUpdate || payload.isUpdate === "") {
884
- if (!payload.key) return false;
885
- delete payload.isUpdate;
886
- payload.updateKey = { [payload.key]: value };
887
- payload.method = payload.type + ".update";
888
- } else if (payload.isDelete || payload.isDelete === "") {
889
- delete payload.isDelete;
890
- if (payload.type == "object" && payload.key) {
891
- payload.method = payload.type + ".update";
892
- if (typeof payload[payload.type] === "string")
893
- payload[payload.type] = {
894
- _id: payload[payload.type],
895
- [payload.key]: undefined
896
- };
897
- else if (Array.isArray(payload[payload.type])) {
898
- console.log(
899
- "payload.type is an array function incomplete"
900
- );
901
- } else if (typeof payload[payload.type] === "object")
902
- payload[payload.type][payload.key] = undefined;
903
- } else {
904
- payload.method = payload.type + ".delete";
905
- }
906
- }
907
- }
908
- return true;
909
- }
910
-
911
- async function getData(form) {
912
- let dataKeys = new Map();
913
- let formObject = forms.get(form);
914
- if (!formObject) {
915
- let elements = form.querySelectorAll(selector);
916
- await init(elements);
917
- return await getData(form);
918
- }
919
-
920
- for (let type of formObject.types.values()) {
921
- for (let [element, data] of type.entries()) {
922
- if (
923
- !element.hasAttribute("key") ||
924
- element.getAttribute("save") === "false"
925
- )
926
- continue;
927
-
928
- if (element.hasAttribute("actions")) {
929
- let attribute = element.getAttribute("actions");
930
- if (attribute.includes("save") || attribute.includes("delete")) continue;
931
- }
932
-
933
- if (element.matches("[render-query]")) {
934
- let type = element.getAttribute("type");
935
- if (type !== "file") continue;
936
- }
937
-
938
- let value = await element.getValue();
939
- if (value === undefined) continue;
940
-
941
- let payload = { ...data };
942
- let dataKey = elements.get(element);
943
-
944
- let success = formatPayload(payload, value, element);
945
- if (!success) continue;
946
-
947
- //dataKey should be used to group
948
- let key = dataKey; //+ payload.method;
949
- if (dataKeys.has(key)) {
950
- let storedData = dataKeys.get(key)[payload.type];
951
- dataKeys.get(key)[payload.type] = {
952
- ...storedData,
953
- ...payload[payload.type]
954
- };
955
- } else {
956
- dataKeys.set(key, payload);
957
- }
958
- }
959
- }
960
-
961
- // TODO: group by methods so we can make one crud request per method
962
- let dataArray = Array.from(dataKeys.values());
963
-
964
- return dataArray;
965
- }
966
-
967
- async function save(element, action) {
968
- if (!element) return;
969
- let data, value, form;
970
- let upsert = element.getAttribute("upsert");
971
- if (upsert && upsert !== "false") upsert = true;
972
- if (element.tagName === "FORM") {
973
- data = await element.getData();
974
- } else {
975
- let isSave = element.getAttribute("save");
976
- if (isSave === "false") return;
977
- if (action) {
978
- form = element.closest("form");
979
- if (form) {
980
- return save(form);
981
- }
982
- }
983
-
984
- let dataKey = elements.get(element);
985
- data = { ...keys.get(dataKey).dataKey.object };
986
-
987
- value = await element.getValue();
988
- let key = element.getAttribute("key");
989
-
990
- if (typeof data[data.type] === "string")
991
- if (key == "{}")
992
- data[data.type] = { _id: data[data.type], ...value };
993
- else data[data.type] = { _id: data[data.type], [key]: value };
994
- else if (typeof data[data.type] === "object")
995
- if (key == "{}") data[data.type] = { ...data[data.type], ...value };
996
- else data[data.type][key] = value;
997
-
998
- if (/\.([0-9]*)/g.test(data.key)) {
999
- let splice = element.getAttribute("splice");
1000
- let slice = element.getAttribute("slice");
1001
- let update = element.getAttribute("update");
1002
-
1003
- if (splice || splice === "") {
1004
- data[data.type][key] = { $splice: value };
1005
- } else if (slice) {
1006
- data[data.type][key] = "$delete";
1007
- } else if (update) {
1008
- value = data.key.replace(/\[.*?\]/, "[" + value + "]");
1009
- data.updateKey[key] = value;
1010
- data[data.type][key] = { $update: value }; // $update is string use the value as the key name
1011
-
1012
- // data[data.type][key] = { $update: {[value]: value} } // $update is an object use the key as the value to use for the new key
1013
- }
1014
- }
1015
-
1016
- data = [data];
1017
- }
1018
-
1019
- let responseDataList = [];
1020
- for (let i = 0; i < data.length; i++) {
1021
- if (data[i].type === "object") {
1022
- let hasId = false;
1023
-
1024
- // 1. Check your three scenarios just like before
1025
- if (typeof data[i].object === "string") {
1026
- hasId = !!data[i]._id;
1027
- } else if (Array.isArray(data[i].object)) {
1028
- hasId = !!data[i].object[0]?._id;
1029
- } else if (typeof data[i].object === "object" && data[i].object !== null) {
1030
- hasId = !!data[i].object?._id;
1031
- }
1032
-
1033
- // 2. Apply the method based on what we found
1034
- if (!data[i].object || !hasId) {
1035
- data[i].method = "object.create";
1036
- } else {
1037
- data[i].method = "object.update";
1038
- if (upsert) data[i].upsert = true;
1039
- }
1040
- }
1041
-
1042
- if (data[i].isUpsert) {
1043
- data[i].upsert = true;
1044
- delete data[i].isUpsert;
1045
- }
1046
-
1047
- if (data[i].method && data[i].method.endsWith(".create")) {
1048
- element.setAttribute(data[i].type, "pending");
1049
- } else if (
1050
- data[i].method &&
1051
- data[i].method.endsWith(".update") &&
1052
- data[i].type == "object" &&
1053
- typeof value == "string" &&
1054
- window.CoCreate.crdt &&
1055
- !"crdt"
1056
- ) {
1057
- return window.CoCreate.crdt.replaceText({
1058
- array: data[i].array,
1059
- key: data[i].key,
1060
- object: data[i].object._id,
1061
- value
1062
- });
1063
- }
1064
-
1065
- data[i] = await CRUD.send(data[i]);
1066
-
1067
- responseDataList.push(data[i]);
1068
-
1069
- if (
1070
- data[i] &&
1071
- (data[i].method.endsWith(".create") ||
1072
- (data[i].type !== "object" &&
1073
- data[i].method.endsWith(".update")))
1074
- ) {
1075
- setTypeValue(element, data[i], action);
1076
- } else if (data[i]) {
1077
- if (action) {
1078
- action.element.dispatchEvent(
1079
- new CustomEvent("saved", {
1080
- detail: data
1081
- })
1082
- );
1083
- } else {
1084
- document.dispatchEvent(
1085
- new CustomEvent("saved", {
1086
- detail: data
1087
- })
1088
- );
1089
- }
1090
- }
1091
- }
1092
-
1093
- return responseDataList;
1094
- }
1095
-
1096
- function setTypeValue(element, data, action) {
1097
- // TODO: if an array name is updated, the attibute array="" needs to be updated.
1098
-
1099
- if (!data) return;
1100
-
1101
- let form;
1102
- if (element.tagName === "FORM") form = element;
1103
- else if (element.parentElement)
1104
- form = element.parentElement.closest("form");
1105
-
1106
- if (!form) {
1107
- if (data.type === "object") {
1108
- if (data.object?.[0]?._id) {
1109
- element.setAttribute("object", data.object[0]._id);
1110
- }
1111
- } else {
1112
- element.setAttribute(data.type, data[data.type].name);
1113
- }
1114
- } else {
1115
- let formObject = forms.get(form);
1116
- if (form.getAttribute("object") === "pending")
1117
- form.setAttribute("object", data.object[0]._id);
1118
-
1119
- let elements = formObject.types.get(data.type);
1120
-
1121
- for (let [el, elData] of elements.entries()) {
1122
- if (data.type === "object") {
1123
- if (!elData.object || elData.object === "pending") {
1124
- elData.object = data.object[0]._id;
1125
- el.setAttribute("object", data.object[0]._id);
1126
- }
1127
- } else if (!elData[data.type]) {
1128
- elData[data.type] = data[data.type].name;
1129
- el.setAttribute(data.type, data[data.type].name);
1130
- }
1131
- }
1132
- setData(Array.from(elements.keys()), data);
1133
- }
1134
-
1135
- if (action) {
1136
- action.element.dispatchEvent(
1137
- new CustomEvent("saved", {
1138
- detail: data
1139
- })
1140
- );
1141
- } else {
1142
- document.dispatchEvent(
1143
- new CustomEvent("saved", {
1144
- detail: data
1145
- })
1146
- );
1147
- }
1148
- }
1149
-
1150
- async function remove(element) {
1151
- if (
1152
- element &&
1153
- !(element instanceof HTMLCollection) &&
1154
- !Array.isArray(element)
1155
- )
1156
- element = [element];
1157
- for (let i = 0; i < element.length; i++) {
1158
- delete element[i].hasRead;
1159
- if (element[i].tagName === "FORM") {
1160
- let form = forms.get(element[i]);
1161
- if (!form) return;
1162
-
1163
- form = form.elements;
1164
- for (let el of form.keys()) {
1165
- let key = elements.get(el);
1166
- if (key) {
1167
- keys.get(key).elements.delete(el);
1168
- elements.delete(el);
1169
- // debounce.delete(key)
1170
- }
1171
- }
1172
- let key = elements.get(element[i]);
1173
- if (key) {
1174
- keys.get(key).elements.delete(element[i]);
1175
- if (!keys.get(key).elements.size) keys.delete(key);
1176
- }
1177
- elements.delete(element[i]);
1178
- forms.delete(element[i]);
1179
- } else {
1180
- let key = elements.get(element[i]);
1181
- if (key) {
1182
- if (element[i].getFilter) filter.filters.delete(element[i]);
1183
- keys.get(key).elements.delete(element[i]);
1184
- elements.delete(element[i]);
1185
- if (!keys.get(key).elements.size) keys.delete(key);
1186
-
1187
- // debounce.delete(key)
1188
- let form = element[i].closest("form");
1189
- form = forms.get(form);
1190
- if (form) {
1191
- form.elements.delete(element[i]);
1192
- if (!form.elements.size) forms.delete(form);
1193
- }
1194
- } else elements.delete(element[i]);
1195
- }
1196
- }
1197
- }
1198
-
1199
- function initSocket() {
1200
- const type = ["storage", "database", "array", "index", "object", "filter"];
1201
- const method = ["create", "update", "delete"];
1202
-
1203
- for (let i = 0; i < type.length; i++) {
1204
- for (let j = 0; j < method.length; j++) {
1205
- const action = type[i] + "." + method[j];
1206
-
1207
- CRUD.listen(action, function (data) {
1208
- if (
1209
- data.resolved &&
1210
- data.status === "received" &&
1211
- CRUD.socket.clientId === data.clientId
1212
- )
1213
- return;
1214
- if (data.rendered) return;
1215
-
1216
- data.rendered = true;
1217
-
1218
- setData(null, data);
1219
- });
1220
- }
1221
- }
1222
- }
1223
-
1224
- Observer.init({
1225
- name: "render",
1226
- types: ["addedNodes"],
1227
- selector: "[render-clone]",
1228
- callback: function (mutation) {
1229
- if (
1230
- mutation.parentElement &&
1231
- !mutation.parentElement.hasAttribute("dnd")
1232
- )
1233
- return;
1234
- let delayTimer = debounce.get(mutation);
1235
- clearTimeout(delayTimer);
1236
- debounce.delete(mutation.target);
1237
-
1238
- let renderedNode = renderedNodes.get(mutation.target);
1239
- if (!renderedNode) return;
1240
-
1241
- if (!mutation.movedFrom) return;
1242
- let draggedEl = mutation.target;
1243
- let draggedFrom = mutation.movedFrom.parentElement;
1244
- let droppedEl =
1245
- mutation.target.nextElementSibling ||
1246
- mutation.target.previousElementSibling;
1247
- let droppedIn = mutation.parentElement;
1248
-
1249
- if (!draggedFrom || !droppedIn) return;
1250
- dndCrud(draggedEl, draggedFrom, droppedEl, droppedIn);
1251
- }
1252
- });
1253
-
1254
- // TODO: has the potential to work on most of the crud elements specifically if the value is an object or an array
1255
- async function dndCrud(draggedEl, draggedFrom, droppedEl, droppedIn) {
1256
- let from = await dndCrudData(draggedEl, draggedFrom, "remove");
1257
- let to = await dndCrudData(droppedEl, droppedIn, "add");
1258
-
1259
- if (from && to && !draggedFrom.isSameNode(droppedIn)) {
1260
- let element = getDataElements(from.data);
1261
- if (!element.length) return;
1262
-
1263
- if (!element.includes(droppedIn)) {
1264
- if (from.keyPath.includes(".")) {
1265
- let test = { [from.keyPath]: undefined };
1266
-
1267
- from.clonedParentData = dotNotationToObject(test, from.clonedParentData);
1268
- dndCrudSend(from.clonedParentData, "update");
1269
- } else {
1270
- const index = from.keyPath.match(/\[(\d+)\]/);
1271
- let removeData = from.clonedParentData[from.clonedParentData.type].splice(
1272
- index,
1273
- 1
1274
- )[0];
1275
- if (removeData) {
1276
- removeData.type = from.clonedParentData.type;
1277
- removeData[removeData.type] = removeData;
1278
- dndCrudSend(removeData, "delete");
1279
- }
1280
-
1281
- if (from.clonedParentData[from.clonedParentData.type].length)
1282
- dndCrudSend(from.clonedParentData, "update");
1283
- }
1284
- }
1285
- }
1286
-
1287
- if (to) dndCrudSend(to.clonedParentData, "update");
1288
- }
1289
-
1290
- async function dndCrudData(element, parent, operator) {
1291
- if (!elements.has(parent)) return;
1292
- let data = await parent.getData();
1293
- let clonedParentData = getObject(parent);
1294
-
1295
- let { dragData, sortName, sortDirection, keyPath, clones, index } = dndNewData(
1296
- element,
1297
- data
1298
- );
1299
- clonedParentData[clonedParentData.type] = [];
1300
-
1301
- if (sortName) {
1302
- for (let i = 0; i < clones.length; i++) {
1303
- if (i > index) {
1304
- let previousData = data[data.type][index];
1305
- dragData[sortName] = operator === "add" ? i + 1 : i - 1;
1306
-
1307
- clonedParentData[clonedParentData.type].push({ ...previousData, ...dragData });
1308
- }
1309
- }
1310
- } else {
1311
- clonedParentData[clonedParentData.type] = [{ ...data[data.type][index], ...dragData }];
1312
- }
1313
-
1314
- return { data, clonedParentData, keyPath, clones, index };
1315
- }
1316
-
1317
- function dndNewData(element, data) {
1318
- let dragData = {};
1319
- if (data.$filter.query) {
1320
- dndNewDataUpdate(dragData, data.$filter.query);
1321
- }
1322
-
1323
- let sortName, sortDirection;
1324
- let sort = data.$filter.sort;
1325
- if (sort && sort.length) {
1326
- for (let i = sort.length - 1; i >= 0; i--) {
1327
- if (typeof data[sort.key] === "number") {
1328
- sortName = sort.key;
1329
- sortDirection = sort.direction;
1330
- break;
1331
- }
1332
- }
1333
- }
1334
-
1335
- let keyPath, clonesMap, clones, index;
1336
-
1337
- let renderedNode = renderedNodes.get(element);
1338
- if (renderedNode) {
1339
- keyPath = renderedNode.keyPath;
1340
- clonesMap = renderedNode.template.clones;
1341
- clones = Array.from(clonesMap.values());
1342
- index = clones.indexOf(element);
1343
- }
1344
-
1345
- return { dragData, sortName, sortDirection, keyPath, clones, index };
1346
- }
1347
-
1348
- function dndNewDataUpdate(dragData, query) {
1349
- for (let key of Object.keys(query)) {
1350
- if (key === "$and" || key === "$or" || key === "$nor") {
1351
- for (let i = 0; i < query[key].length; i++) {
1352
- dndNewDataUpdate(dragData, query[key][i]);
1353
- }
1354
- } else if (
1355
- typeof query[key] === "object" &&
1356
- !Array.isArray(query[key])
1357
- ) {
1358
- if (query[key].$eq) dragData[key] = query[key].$eq;
1359
- if (query[key].$ne) dragData[key] = query[key].$ne;
1360
- } else {
1361
- dragData[key] = query[key];
1362
- }
1363
- }
1364
- }
1365
-
1366
- function dndCrudSend(data, crudType) {
1367
- if (CRUD) {
1368
- let action = data.type;
1369
- action = action.charAt(0).toUpperCase() + action.slice(1);
1370
- CRUD[crudType + action](data);
1371
- } else console.log("dnd reordered data set as crud is unavailable");
1372
- }
1373
-
1374
- Observer.init({
1375
- name: "CoCreateElementsChildList",
1376
- types: ["addedNodes"],
1377
- selector: selector,
1378
- callback: function (mutation) {
1379
- init([mutation.target]);
1380
- }
1381
- });
1382
-
1383
- Observer.init({
1384
- name: "CoCreateElementsRemovedNodes",
1385
- types: ["removedNodes"],
1386
- selector: selector,
1387
- callback: function (mutation) {
1388
- if (mutation.target.hasAttribute("render-clone")) return;
1389
- remove(mutation.target);
1390
- }
1391
- });
1392
-
1393
- Observer.init({
1394
- name: "CoCreateElementsAttributes",
1395
- types: ["attributes"],
1396
- attributeFilter: [
1397
- "organization_id",
1398
- "host",
1399
- "storage",
1400
- "database",
1401
- "array",
1402
- "index",
1403
- "object",
1404
- "key"
1405
- ],
1406
- // target: selector, // blocks mutations when applied
1407
- callback: function (mutation) {
1408
- let currentValue = mutation.target.getAttribute(mutation.attributeName);
1409
- if (currentValue !== mutation.oldValue) {
1410
- if (mutation.target.tagName === "FORM") return;
1411
- remove(mutation.target);
1412
- init([mutation.target]);
1413
- }
1414
- }
1415
- });
1416
-
1417
- Actions.init([
1418
- {
1419
- name: "save",
1420
- endEvent: "saved",
1421
- callback: (action) => {
1422
- if ((action.form, action)) save(action.form, action);
1423
- }
1424
- },
1425
- {
1426
- name: "delete",
1427
- endEvent: "deleted",
1428
- callback: async (action) => {
1429
- // TODO: use selector to target elements for deletion if element is apart of render get render get rendering element for more crud detils
1430
- // this way any selector can be used to target crud deletions not just .selected
1431
- let elements = [];
1432
- if (action.element.hasAttribute("delete-query")) {
1433
- elements = queryElements({
1434
- element: action.element,
1435
- prefix: "delete"
1436
- });
1437
- } else {
1438
- elements = [action.element];
1439
- }
1440
-
1441
- for (let i = 0; i < elements.length; i++) {
1442
- const data = getObject(elements[i]);
1443
- if (!data) return;
1444
- data.method = data.type + ".delete";
1445
-
1446
- if (elements[i].renderValue) {
1447
- let selected = elements[i].querySelectorAll(".selected");
1448
- data[data.type] = [];
1449
- for (let j = 0; j < selected.length; j++) {
1450
- let attribute = selected[j].getAttribute(data.type);
1451
- if (attribute) {
1452
- attribute = attribute.split(",");
1453
- for (let k = 0; k < attribute.length; k++) {
1454
- if (data.type === "object")
1455
- data[data.type].push({ _id: attribute[k] });
1456
- else {
1457
- data[data.type].push(attribute[k]);
1458
- }
1459
- }
1460
- }
1461
- }
1462
- } else if (
1463
- data.type === "object" &&
1464
- typeof data[data.type] === "string"
1465
- )
1466
- data[data.type] = { _id: data[data.type] };
1467
-
1468
- let response = await CRUD.send(data);
1469
- action.element.dispatchEvent(
1470
- new CustomEvent("deleted", {
1471
- detail: response
1472
- })
1473
- );
1474
- }
1475
- }
1476
- }
1477
- ]);
31
+ import "./dndHandler.js";
32
+ import "./srcHandler.js";
33
+ import "./valueQuery.js";
1478
34
 
1479
- init();
35
+ import { elements, forms, keys, debounce } from "./mapRegistry.js"
1480
36
 
1481
37
  export default {
1482
38
  init,
1483
39
  read,
1484
40
  save,
1485
41
  getData,
1486
- getObject,
42
+ getDataObject,
1487
43
  reset,
1488
44
  elements,
1489
45
  keys,
1490
46
  forms,
1491
47
  debounce,
1492
48
  getAttributes,
1493
- setTypeValue
49
+ setDataType
1494
50
  };