@nodeject/ui-components
Version:
UI library for non-trivial components
754 lines (752 loc) • 30.7 kB
JavaScript
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
import produce from 'immer';
import { createContainer } from 'unstated-next';
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
import { arrayToTree } from '../../arrayToTree';
import { LayoutStyle } from '../dtos';
var useDnDTree = function (props) {
var mouseDownRef = useRef(0);
var _a = useState(0), mouseDown = _a[0], setMouseDown = _a[1];
mouseDownRef.current = mouseDown;
document.body.onmousedown = function () {
setMouseDown(function (m) { return m + 1; });
};
document.body.onmouseup = function () {
setMouseDown(function (m) { return m - 1; });
setIsDragValid(null);
setNodeDraggingId(null);
};
var actionsFromServer = props.actions, activeNode = props.activeNode, components = props.components, onNodeClicked = props.onNodeClicked, treeMode = props.treeMode;
var treeOptionsDefault = {
canEditTreeStructure: false,
canExpandCollapse: true
};
var treeOptions = __assign(__assign({}, treeOptionsDefault), props.treeOptions);
// const [treeDataFromServer, setTreeDataFromServer] = useState<TreeData>(
// props.treeData
// )
var treeDataFromServer = props.treeData;
var _b = useState(), newNode = _b[0], setNewNode = _b[1];
var _c = useState({ saveNewNode: null }), saveNewNode = _c[0], setSaveNewNode = _c[1];
var _d = useState(props.treeData), treeDataLocal = _d[0], setTreeDataLocal = _d[1];
var _e = useState({ id: '', isExpanded: false }), storedDraggedNodePreviousExpandedStatus = _e[0], setStoredDraggedNodePreviousExpandedStatus = _e[1];
var _f = useState(Object.assign.apply(Object, __spreadArrays([{}], props.treeData.nodes.map(function (item) {
var _a;
return _a = {},
_a[item.data.id] = { isExpanded: false },
_a;
})))), expandedNodes = _f[0], setExpandedNodes = _f[1];
var _g = useState(getUnflattenedTreeData(props.treeData.nodes)), unflattenedTreeData = _g[0], setUnflattenedTreeData = _g[1];
var _h = useState(''), draggingId = _h[0], setDraggingId = _h[1];
// Synchronizes local component state with new data coming from server (props)
useEffect(function () {
setTreeDataLocal(treeDataFromServer);
setExpandedNodes(function (expandedNodes) {
return Object.assign.apply(Object, __spreadArrays([{}], treeDataFromServer.nodes.map(function (item) {
var _a;
return _a = {},
_a[item.data.id] = {
isExpanded: expandedNodes[item.data.id]
},
_a;
})));
});
}, [JSON.stringify(treeDataFromServer.nodes)]);
// Reconciliates draggingId and expandedNodes
// It sets bach the dragged node to previous collapsed or expanded status
useEffect(function () {
if (draggingId === '') {
if (storedDraggedNodePreviousExpandedStatus.id !== '') {
setExpandedNodes(function (expandedNodes) {
var newExpandedNodes = produce(expandedNodes, function (draft) {
draft[storedDraggedNodePreviousExpandedStatus.id] = {
isExpanded: storedDraggedNodePreviousExpandedStatus.isExpanded
};
});
return newExpandedNodes;
});
}
}
}, [draggingId]);
var _j = useState(null), draggedId = _j[0], setIsDragValid = _j[1];
var isDragValidRef = useRef(null);
isDragValidRef.current = draggedId;
// treeDataFromServer
useLayoutEffect(function () {
var nextUnflattenedTreeData = produce(treeDataLocal, function (draftState) {
return getUnflattenedTreeData(draftState.nodes);
});
setUnflattenedTreeData(nextUnflattenedTreeData);
// console.log(
// 'AFTER',
// JSON.stringify(
// treeDataFromServer.nodes.map((n, i) => {
// return `id: ${n.data.id}, index: ${i} }`
// }),
// null,
// 2
// )
// )
// console.log('_______________________________________-')
}, [JSON.stringify(treeDataLocal)]);
var setNodeDraggingId = function (nodeId) {
setDraggingId(nodeId);
};
var timeoutBeforeCollapsingNodeChildren;
// onClick on Node handle
var onMouseDownHandle = function (nodeId) {
// // setDraggingId(nodeId)
// collapseNode(nodeId)
// console.log('HANDLE', nodeId)
if (treeMode === 'dragdrop' && nodeId !== isDragValidRef.current) {
setIsDragValid(nodeId);
// console.log('onMouseDown set to true', draggedId)
timeoutBeforeCollapsingNodeChildren = setTimeout(function () {
if (isDragValidRef.current) {
// console.log('onMouseDown test', isDragValidRef.current)
var mouse = mouseDownRef.current !== 0 ? true : false;
if (mouse) {
onMouseDownHandle(nodeId);
setStoredDraggedNodePreviousExpandedStatus({
id: nodeId,
isExpanded: expandedNodes[nodeId].isExpanded
});
setNodeDraggingId(nodeId);
}
}
}, 100);
}
};
var onMouseUpHandle = function (nodeId) {
// // setDraggingId(nodeId)
// collapseNode(nodeId)
// console.log('HANDLE', nodeId)
if (treeMode === 'dragdrop') {
setIsDragValid(null);
if (timeoutBeforeCollapsingNodeChildren) {
clearTimeout(timeoutBeforeCollapsingNodeChildren);
}
// setStoredDraggedNodePreviousExpandedStatus({
// id: null,
// isExpanded: expandedNodes[nodeId].isExpanded
// })
setNodeDraggingId(null);
}
};
/**
const onMouseUp = () => {
console.log('MouseUp')
setIsDragValid(false)
if (timeoutBeforeCollapsingNodeChildren) {
clearTimeout(timeoutBeforeCollapsingNodeChildren)
}
}
const onMouseDown = () => {
if (canDragDrop) {
setIsDragValid(true)
console.log('onMouseDown set to true', isDragValid)
timeoutBeforeCollapsingNodeChildren = setTimeout(() => {
if (isDragValidRef.current) {
console.log('onMouseDown test', isDragValidRef.current)
onMouseDownHandle(nodeId)
}
}, 100)
}
}
*/
var createNewNode = function (args) {
// create new node
var newNode = props.actions.createNewNode(args);
setNewNode(newNode.data.id);
// updates collapsedNodes
setExpandedNodes(function (collapsedNodes) {
var nextCollapsedNodes = produce(collapsedNodes, function (draft) {
var newCollapsedNode = { isExpanded: false };
draft[newNode.data.id] = newCollapsedNode;
});
return nextCollapsedNodes;
});
return newNode;
};
/**
* Add child node
* @param nodeId
*/
var addChildNode = function (nodeId) {
// Early exit
if (!nodeId) {
return;
}
// console.log('addChildNodeClient LOCAL')
// create new node
var child = createNewNode({
data: { parent: nodeId, layoutStyle: LayoutStyle.List }
});
// add new node as a child locally
setTreeDataLocal(function (treeDataLocal) {
var nextTreeDataLocal = produce(treeDataLocal, function (draftState) {
var index;
// Find index of node's deepest descendant, and insert child after this index
var descendants = getDescendantsFromFlatNode(nodeId, unflattenedTreeData);
if (descendants.length > 0) {
var deepestDescendantId_1 = descendants[descendants.length - 1].data.id;
var deepestDescendantIndex = treeDataLocal.nodes.findIndex(function (n) { return n.data.id === deepestDescendantId_1; });
index = deepestDescendantIndex + 1;
}
else {
index =
treeDataLocal.nodes.findIndex(function (n) { return n.data.id === nodeId; }) + 1;
}
// insert child at index
draftState.nodes.splice(index, 0, child);
});
return nextTreeDataLocal;
});
// add child on server
if (actionsFromServer.addChildNode) {
var saveNewNodeFn = function (data) {
return actionsFromServer
.addChildNode({
component: nodeId,
newComponent: child.data.id,
data: data
})
.then(function () { return setNewNode(''); }); // resets
};
setSaveNewNode({ saveNewNode: saveNewNodeFn });
}
else {
console.warn('actionsFromServer is not implemented. Please provide a actionsFromServer function');
}
};
/**
* Delete node and all descendants
* @param nodeId
*/
var deleteNode = function (nodeId) {
// Find nodeIndex to delete, find descendants count and add to the nodeIndex
var node = treeDataLocal.nodes.find(function (n) { return n.data.id === nodeId; });
var nodeIndexToDelete = treeDataLocal.nodes.findIndex(function (n) { return n.data.id === nodeId; });
if (nodeIndexToDelete === -1 || !node) {
return;
}
// Delete locally
setTreeDataLocal(function (treeDataLocal) {
var nextTreeDataLocal = produce(treeDataLocal, function (draftState) {
var descendantsCount = getDescendantsFromFlatNode(node.data.id, unflattenedTreeData).length;
draftState.nodes.splice(nodeIndexToDelete, 1 + descendantsCount);
});
return nextTreeDataLocal;
});
// Delete on server
if (actionsFromServer.deleteNode) {
actionsFromServer.deleteNode(nodeId);
}
else {
console.warn('deleteNode is not implemented. Please provide a deleteNode function');
}
};
/**
* Insert sibling before
* @param args
*/
var insertSiblingBefore = function (beforeNodeId) {
// Can't insert a sibling to a node that does not have parent
var nodeParentId = treeDataLocal.nodes.find(function (n) { return n.data.id === beforeNodeId; }).data.parent;
if (!nodeParentId) {
return;
}
var newNode = createNewNode({
data: {
layoutStyle: LayoutStyle.List,
parent: nodeParentId
}
});
setTreeDataLocal(function (treeDataLocal) {
// insert sibling locally
var nextTreeDataLocal = produce(treeDataLocal, function (draftState) {
// find beforeNode's index
var beforeNodeIndex = treeDataLocal.nodes.findIndex(function (n) { return n.data.id === beforeNodeId; });
draftState.nodes.splice(beforeNodeIndex, 0, newNode);
});
return nextTreeDataLocal;
});
// insert sibling on server
if (actionsFromServer.insertSiblingBefore) {
var saveNewNodeFn = function (data) {
return actionsFromServer
.insertSiblingBefore({
component: beforeNodeId,
newComponent: newNode.data.id,
data: data
})
.then(function () { return setNewNode(''); }); // resets
};
setSaveNewNode({ saveNewNode: saveNewNodeFn });
}
else {
console.warn('actionsFromServer is not implemented. Please provide a actionsFromServer function');
}
};
/**
* Insert sibling after
* @param args
*/
var insertSiblingAfter = function (afterNodeId) {
// Can't insert a sibling to a node that does not have parent
var nodeParentId = treeDataLocal.nodes.find(function (n) { return n.data.id === afterNodeId; }).data.parent;
if (!nodeParentId) {
return;
}
var newNode = createNewNode({
data: {
layoutStyle: LayoutStyle.List,
parent: nodeParentId
}
});
setTreeDataLocal(function (treeDataLocal) {
// insert sibling locally
var nextTreeDataLocal = produce(treeDataLocal, function (draftState) {
var afterNodeIndex = treeDataLocal.nodes.findIndex(function (n) { return n.data.id === afterNodeId; });
// find node's index
// const descendants = getDescendantsFromFlatNode(
// afterNodeId,
// unflattenedTreeData
// )
// let index: number =
// treeDataLocal.nodes.findIndex(
// (n) => n.data.id === afterNodeId
// ) + 1
// if (descendants.length > 0) {
// const deepestDescendant =
// descendants[descendants.length - 1]
// const deepestDescendantIndex = treeDataLocal.nodes.findIndex(
// (n) => n.data.id === deepestDescendant.data.id
// )
// index = deepestDescendantIndex + 1
// }
// insert new node before index
draftState.nodes.splice(afterNodeIndex + 1, 0, newNode);
});
return nextTreeDataLocal;
});
// insert sibling on server
if (actionsFromServer.insertSiblingAfter) {
var saveNewNodeFn = function (data) {
return actionsFromServer
.insertSiblingAfter({
component: afterNodeId,
newComponent: newNode.data.id,
data: data
})
.then(function () { return setNewNode(''); }); // resets
};
setSaveNewNode({ saveNewNode: saveNewNodeFn });
}
else {
console.warn('actionsFromServer is not implemented. Please provide a actionsFromServer function');
}
};
/**
* Insert parent
* @param nodeId
*/
var insertParent = function (nodeId) {
// const node = treeDataFromServer.nodes.find(n => n.data.id === nodeId)
var newParentId;
setTreeDataLocal(function (treeDataLocal) {
// Insert locally
var nextTreeDataLocal = produce(treeDataLocal, function (draftState) {
var node = treeDataLocal.nodes.find(function (n) { return n.data.id === nodeId; });
var newParentNode = createNewNode({
data: {
layoutStyle: LayoutStyle.List,
parent: node.data.parent
}
});
newParentId = newParentNode.data.id;
var newNode = produce(node, function (draftNodeState) {
draftNodeState.data.parent = newParentNode.data.id;
});
// Find current node and replace it with new node with updated parent id
var nodeIndex = treeDataLocal.nodes.findIndex(function (n) { return n.data.id === nodeId; });
draftState.nodes.splice(nodeIndex, 1, newNode);
// Insert parent before node's index
draftState.nodes.splice(nodeIndex - 1, 0, newParentNode);
});
return nextTreeDataLocal;
});
if (actionsFromServer.insertParent) {
var saveNewNodeFn = function (data) {
return actionsFromServer
.insertParent({
component: nodeId,
newComponent: newParentId,
data: data
})
.then(function () { return setNewNode(''); }); // resets
};
setSaveNewNode({ saveNewNode: saveNewNodeFn });
}
else {
console.warn('insertParent is not implemented. Please provide a insertParent function');
}
};
var remove = function (arr, index) {
var removedElement = arr[index];
var newArray = produce(arr, function (draft) {
draft.splice(index, 1)[0];
return draft;
});
return { array: newArray, removedElement: removedElement };
};
var insert = function (arr, atIndex, element) {
var newArr = produce(arr, function (draft) {
draft.splice(atIndex, 0, element);
});
return newArr;
};
var move = function (args) {
var arr = args.arr, id = args.id, toId = args.toId, newElement = args.newElement;
var fromNode = arr.find(function (n) { return n.data.id === id; });
var toNode = arr.find(function (n) { return n.data.id === toId; });
var fromIndex = arr.findIndex(function (n) { return n.data.id === id; });
var array = remove(arr, fromIndex).array;
var toIndex = toId
? array.findIndex(function (n) { return n.data.id === toId; })
: array.length;
// console.log('MOVE START___________________')
// console.log(
// 'Before remove node',
// arr.map((n, index) => `${(n as any).data.wbsId} | ${index}`),
// newElement
// )
// console.log(
// 'After remove node',
// array.map((n, index) => `${(n as any).data.wbsId} | ${index}`),
// newElement
// )
var finalIndex = toIndex;
if (toIndex === array.length - 1) {
finalIndex = toIndex + 1;
// console.log('A, CASE move to very end')
}
if (fromIndex < toIndex) {
if (fromNode.data.parent === toNode.data.parent) {
finalIndex = toIndex + 1;
// console.log('B-1')
}
else {
finalIndex = toIndex;
// console.log('B-2')
}
}
if (fromIndex === toIndex) {
if (fromNode.data.parent === toNode.data.parent) {
finalIndex = toIndex + 1;
// console.log('C-1')
}
else {
finalIndex = toIndex;
// console.log('C-2')
}
}
if (fromIndex > toIndex) {
finalIndex = toIndex;
// console.log('D')
}
if (newElement.data.parent === toId) {
// console.log('WARNING, need to do this finalIndex case')
}
// console.log('finalIndex', finalIndex)
var newArr = insert(array, finalIndex, newElement);
var sorted = sortByLevels(newArr);
// console.log(
// 'MOVE COMPLETED',
// sorted.map((n, index) => `${(n as any).data.wbsId} | ${index}`)
// )
return sorted;
};
var sortByLevels = function (arr) {
var newArr = produce(arr, function (draft) {
var levels = [];
var root = draft.find(function (n) { return n.data.parent === null; });
var loop = function (arr, node) {
// console.log(node.data.wbsId)
var level = getHierarchyLevel(arr, node.data.id);
var arrayIndex = level - 1;
while (!levels[arrayIndex]) {
levels.push([]);
}
levels[arrayIndex].push(node);
var children = arr.filter(function (n) { return n.data.parent === node.data.id; });
children.map(function (c) {
loop(arr, c);
});
};
loop(draft, root);
// console.log('levels', levels)
return [].concat.apply([], levels);
});
return newArr;
};
/**
* Root level = 1. Child of root level = 2. Child of Chilf of root level = 3. Etc.
* @param arr
* @param itemId
*/
var getHierarchyLevel = function (arr, itemId) {
var level = 1;
var item = arr.find(function (i) { return i.data.id === itemId; });
while (item.data.parent) {
level += 1;
item = arr.find(function (i) { return i.data.id === item.data.parent; });
}
return level;
};
/**
* Move node
* @param args
*/
var moveNode = function (args) {
var _a;
var index = args.index, parentContainerNode = args.parentContainerNode;
var _b = args.droppedNode, children = _b.children, droppedNode = __rest(_b, ["children"]);
// console.log('___________START MOVE_________________')
// console.log(
// 'droppedNode',
// parentContainerNode?.data.wbsId,
// droppedNode.data.wbsId,
// index
// )
var isNotDroppingInDescendant = true;
// !isDroppingNodeInDescendant({
// index,
// droppedNode,
// parentContainerNode
// })
var droppedOnNode;
if (parentContainerNode.children.length === 0) {
droppedOnNode = treeDataLocal.nodes.find(function (n) { return n.data.id === parentContainerNode.data.id; });
}
else if (index > parentContainerNode.children.length - 1) {
var i = treeDataLocal.nodes.findIndex(function (n) {
return n.data.id ===
parentContainerNode.children[index - 1].data.id;
});
droppedOnNode = treeDataLocal.nodes[i + 1] || treeDataLocal.nodes[i];
}
else {
droppedOnNode = treeDataLocal.nodes.find(function (n) { return n.data.id === parentContainerNode.children[index].data.id; });
}
var isNotDroppingAtSameIndex = droppedNode.data.id !== ((_a = parentContainerNode.children[index]) === null || _a === void 0 ? void 0 : _a.data.id);
var allowMoveNode = isNotDroppingInDescendant && isNotDroppingAtSameIndex;
if (allowMoveNode) {
setTreeDataLocal(function (treeDataLocal) {
var nextDroppedNode = produce(droppedNode, function (draftDroppedNode) {
return __assign(__assign({}, draftDroppedNode), { data: __assign(__assign({}, draftDroppedNode.data), { parent: parentContainerNode.data.id }) });
});
var nodes = move({
arr: treeDataLocal.nodes,
id: droppedNode.data.id,
toId: droppedOnNode.data.id,
newElement: nextDroppedNode
});
// console.log('AFTER MOVE')
// console.log(nodes.map((n, i) => `${n.data.wbsId}, ${i}`))
var nextTreeDataLocal = {
nodes: nodes
};
return nextTreeDataLocal;
});
if (actionsFromServer.moveNode) {
actionsFromServer.moveNode({
index: index,
nodeKey: droppedNode.data.id,
parentKey: parentContainerNode.data.id
});
}
else {
console.warn('insertParent is not implemented. Please provide a insertParent function');
}
}
// reset node dragging id
setNodeDraggingId('');
};
var collapseNode = function (nodeId) {
var nextCollapsedNodes = produce(expandedNodes, function (draft) {
draft[nodeId].isExpanded = false;
});
setExpandedNodes(nextCollapsedNodes);
};
var expandNode = function (nodeId) {
var nextCollapsedNodes = produce(expandedNodes, function (draft) {
draft[nodeId].isExpanded = true;
});
setExpandedNodes(nextCollapsedNodes);
};
var toggleNode = function (nodeId) {
var nextCollapsedNodes = produce(expandedNodes, function (draft) {
draft[nodeId].isExpanded = !draft[nodeId].isExpanded;
});
setExpandedNodes(nextCollapsedNodes);
};
var actions = {
addChildNode: addChildNode,
collapseNode: collapseNode,
expandNode: expandNode,
toggleNode: toggleNode,
createNewNode: createNewNode,
deleteNode: deleteNode,
insertSiblingBefore: insertSiblingBefore,
insertParent: insertParent,
insertSiblingAfter: insertSiblingAfter,
moveNode: moveNode,
saveNewNode: saveNewNode.saveNewNode,
setNodeDraggingId: setNodeDraggingId
};
return {
actions: actions,
activeNode: activeNode,
events: { onMouseDownHandle: onMouseDownHandle, onMouseUpHandle: onMouseUpHandle },
components: components,
onNodeClicked: onNodeClicked,
treeMode: treeMode,
treeOptions: treeOptions,
state: {
expandedNodes: expandedNodes,
draggingId: draggingId,
newNodeId: newNode,
unflattenedTreeData: unflattenedTreeData
}
};
};
export var DnDTreeContainer = createContainer(useDnDTree);
var getUnflattenedTreeData = function (nodes) {
return {
nodes: arrayToTree(nodes, {
dataField: null,
id: 'data.id',
parentId: 'data.parent'
})
};
};
var getDeepestDescendant = function (node) {
var childenCount = node.children.length;
if (childenCount > 0) {
return getDeepestDescendant(node.children[childenCount - 1]);
}
else {
return node.data.id;
}
};
var getDescendants = function (node, descendants) {
if (!node) {
return [];
}
var desc = descendants || [];
node.children.map(function (c) {
desc.push(c);
getDescendants(c, desc);
});
return desc;
};
var getDescendantsFromFlatNode = function (nodeId, treeData) {
var treeNodeUnflattened = null;
var getTreeNodeUnflattened = function (nodes) {
for (var i = 0; i < nodes.length; i++) {
if (treeNodeUnflattened) {
break;
}
if (nodes[i].data.id === nodeId) {
treeNodeUnflattened = nodes[i];
break;
}
else {
getTreeNodeUnflattened(nodes[i].children);
}
}
};
getTreeNodeUnflattened(treeData.nodes);
return getDescendants(treeNodeUnflattened);
};
/**
* Returns the position index of the node within the flat treeData list
* @param node
* @param flatTreeData
*/
var getNodeIndex = function (node, flatTreeData) {
if (typeof node === 'string') {
return flatTreeData.nodes.findIndex(function (n) { return n.data.id === node; });
}
else {
return flatTreeData.nodes.findIndex(function (n) { return n.data.id === node.data.id; });
}
};
var isDroppedAsLastChild = function (args) {
var index = args.index, parent = args.parent;
return parent.children[index] === undefined;
};
var branchNodeQty = function (node) {
var nodeCount = 1;
var descendantsCount = getDescendants(node).length;
return descendantsCount + nodeCount;
};
var indexAfterNode = function (parentIndex) {
return parentIndex + 1;
};
var isNodeInserted = function (node, parent) {
return node.data.parent !== parent.data.id;
};
var arrayToObject = function (args) {
var array = args.array, id = args.id, value = args.value;
if (typeof value === 'string') {
return Object.assign.apply(Object, __spreadArrays([{}], array.map(function (item) {
var _a;
return (_a = {},
_a[getItemNestedProperty(item, id)] = {
data: getItemNestedProperty(item, value)
},
_a);
})));
}
else {
return Object.assign.apply(Object, __spreadArrays([{}], array.map(function (item) {
var _a;
return (_a = {},
_a[getItemNestedProperty(item, id)] = true,
_a);
})));
}
};
var getItemNestedProperty = function (item, nestedProperty) {
return nestedProperty.split('.').reduce(function (o, i) { return o[i]; }, item);
};