From d1243d98d2f517055437cc67e963528771aa4c4b Mon Sep 17 00:00:00 2001 From: Valerio Virgillito Date: Fri, 16 Mar 2012 15:41:26 -0700 Subject: fixing the components drag and drop. Signed-off-by: Valerio Virgillito --- js/mediators/drag-drop-mediator.js | 11 +++++++++++ .../ComponentsPanelBase.reel/ComponentsPanelBase.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/js/mediators/drag-drop-mediator.js b/js/mediators/drag-drop-mediator.js index 3a965be5..8663b06e 100755 --- a/js/mediators/drag-drop-mediator.js +++ b/js/mediators/drag-drop-mediator.js @@ -60,6 +60,17 @@ exports.DragDropMediator = Montage.create(Component, { var i, files = e.dataTransfer.files, position = {x: e.offsetX, y: e.offsetY}, rootUrl = this.application.ninja.coreIoApi.rootUrl+escape((this.application.ninja.documentController.documentHackReference.root.split(this.application.ninja.coreIoApi.cloudData.root)[1])), rootUri = this.application.ninja.documentController.documentHackReference.root; + + var xferString = e.dataTransfer.getData("text/plain"); + if(xferString) { + // If the drop is a component, call the delegate with the top,left coordinates + if(xferString.indexOf("componentDrop") > -1) { + if(this.dropDelegate && typeof this.dropDelegate === 'object') { + this.dropDelegate.handleComponentDrop(e.offsetX - this.application.ninja.stage.userContentLeft, e.offsetY - this.application.ninja.stage.userContentTop); + return; + } + } + } // for (i=0; files[i]; i++) { if (files[i].type.indexOf('image') !== -1) { diff --git a/js/panels/Components/ComponentsPanelBase.reel/ComponentsPanelBase.js b/js/panels/Components/ComponentsPanelBase.reel/ComponentsPanelBase.js index b6bee37d..b4eec771 100755 --- a/js/panels/Components/ComponentsPanelBase.reel/ComponentsPanelBase.js +++ b/js/panels/Components/ComponentsPanelBase.reel/ComponentsPanelBase.js @@ -118,7 +118,7 @@ var ComponentsPanelBase = exports.ComponentsPanelBase = Montage.create(Component didCreate: { value: function() { // Setup the drop delegate -// this.application.ninja.dragDropMediator.dropDelegate = this; + this.application.ninja.dragDropMediator.dropDelegate = this; // Loop through the component and load the JSON data for them this._loadComponents(); } -- cgit v1.2.3 From 4bec28fbd8371deceffd1563190cb5e399d554d3 Mon Sep 17 00:00:00 2001 From: Valerio Virgillito Date: Fri, 16 Mar 2012 16:36:04 -0700 Subject: Squashed commit of SnapManagerFixes Signed-off-by: Valerio Virgillito --- js/stage/stage.reel/stage.js | 30 +++++++++++ js/tools/SelectionTool.js | 5 +- js/tools/Translate3DToolBase.js | 111 ++++++++++++++++++++++++++++---------- js/tools/TranslateObject3DTool.js | 40 +++++++++++--- js/tools/drawing-tool.js | 2 + js/tools/modifier-tool-base.js | 25 ++++----- 6 files changed, 159 insertions(+), 54 deletions(-) diff --git a/js/stage/stage.reel/stage.js b/js/stage/stage.reel/stage.js index e139af97..00b72490 100755 --- a/js/stage/stage.reel/stage.js +++ b/js/stage/stage.reel/stage.js @@ -518,6 +518,36 @@ exports.Stage = Montage.create(Component, { } }, + /** + * GetSelectableElement: Returns a selectable object (direct child of current container) at clicked point + * + * @param: X,Y + * @return: Returns the current container if the the X,Y hits an element in the exclusion list + */ + GetSelectableElement: { + value: function(pos) { + var item = this.GetElement(pos); + if(this.application.ninja.currentDocument.inExclusion(item) !== -1) { + return this.application.ninja.currentSelectedContainer; + } + var activeContainerId = this.application.ninja.currentSelectedContainer.uuid; + if(item.parentNode.uuid === activeContainerId) { + return item; + } else { + var outerElement = item.parentNode; + + while(outerElement.parentNode && outerElement.parentNode.uuid !== activeContainerId) { + // If element is higher up than current container then return + if(outerElement.id === "UserContent") return; + // else keep going up the chain + outerElement = outerElement.parentNode; + } + + return outerElement; + } + } + }, + /** * GetElement: Returns the object under the X,Y coordinates passed as an obj with x,y * diff --git a/js/tools/SelectionTool.js b/js/tools/SelectionTool.js index 5f48f74d..f9411f48 100755 --- a/js/tools/SelectionTool.js +++ b/js/tools/SelectionTool.js @@ -205,12 +205,12 @@ var SelectionTool = exports.SelectionTool = Montage.create(ModifierToolBase, { { this._handleMode = null; this._delta = null; - this.DrawHandles(); } this.endDraw(event); this._canSnap = true; this._use3DMode = false; + this.DrawHandles(); } }, @@ -577,6 +577,7 @@ var SelectionTool = exports.SelectionTool = Montage.create(ModifierToolBase, { { // form the translation vector and post translate the matrix by it. delta = vecUtils.vecSubtract( 3, data.pt1, data.pt0 ); + delta[2] = 0; var transMat = Matrix.Translation( delta ); this._moveElements(transMat); } @@ -710,7 +711,7 @@ var SelectionTool = exports.SelectionTool = Montage.create(ModifierToolBase, { { return; } - if(this._target && this._handles) + if(this._target && this._handles && (this._targets.length === 1)) { var len = this._handles.length; var i = 0, diff --git a/js/tools/Translate3DToolBase.js b/js/tools/Translate3DToolBase.js index 24a68ad1..07b6d04d 100755 --- a/js/tools/Translate3DToolBase.js +++ b/js/tools/Translate3DToolBase.js @@ -18,6 +18,7 @@ var Montage = require("montage/core/core").Montage, exports.Translate3DToolBase = Montage.create(ModifierToolBase, { _inLocalMode: { value: true, enumerable: true }, + _clickedOnStage: { value: false }, HandleDoubleClick : { value : function() @@ -47,17 +48,33 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, delta[1] = 0; break; } + if( (this.application.ninja.selectedElements.length > 1) && this._clickedOnStage ) + { + this._delta = ~~(delta[this._handleMode]); + } + else + { + this._delta += ~~(delta[this._handleMode]); + } - this._updateDelta(delta, this._handleMode); } - else if(this._mode === 1) + else { - delta[2] = delta[1]; - delta[0] = 0; - delta[1] = 0; + if(this._mode === 1) + { + delta[2] = delta[1]; + delta[0] = 0; + delta[1] = 0; + } + else + { + delta[2] = 0; + } + this._delta = delta.slice(0); } var transMat = Matrix.Translation( delta ); + if(this._inLocalMode && (this._targets.length === 1) ) { this._translateLocally(transMat); @@ -139,7 +156,7 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, var shouldUpdateStartMat = true; - if(this._clickedObject === this.application.ninja.currentDocument.documentRoot) + if(this._clickedOnStage) { shouldUpdateStartMat = false; } @@ -152,11 +169,15 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, { item = this._targets[i]; elt = item.elt; - curMat = item.mat; - - glmat4.multiply(curMat, qMat, curMat); + curMat = item.mat.slice(0); - viewUtils.setMatrixForElement( elt, curMat, true); +// glmat4.multiply(curMat, qMat, curMat); +// +// viewUtils.setMatrixForElement( elt, curMat, true); + curMat[12] += transMat[12]; + curMat[13] += transMat[13]; + curMat[14] += transMat[14]; + viewUtils.setMatrixForElement( elt, curMat, true); if(shouldUpdateStartMat) { @@ -198,6 +219,19 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, "translateTool", previousStyles ); + if(this._origin && this._delta) + { + if(this._handleMode !== null) + { + this._origin[this._handleMode] += this._delta; + } + else + { + this._origin[0] += this._delta[0]; + this._origin[1] += this._delta[1]; + } + } + this._delta = null; } // Save previous value for undo/redo this._undoArray = []; @@ -232,7 +266,7 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, } }, - _getHandlesOrigin: { + _updateHandlesOrigin: { value: function () { var ctr; @@ -243,30 +277,43 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, { var item = this._target; viewUtils.pushViewportObj( item ); - var ctr = viewUtils.getCenterOfProjection(); + ctr = viewUtils.getCenterOfProjection(); viewUtils.popViewportObj(); ctr[2] = 0; - var ctrOffset = item.elementModel.props3D.m_transformCtr; - if(ctrOffset) - { - ctr = vecUtils.vecAdd(3, ctr, ctrOffset); - } +// var ctrOffset = item.elementModel.props3D.m_transformCtr; +// if(ctrOffset) +// { +// ctr = vecUtils.vecAdd(3, ctr, ctrOffset); +// } - ctr = viewUtils.localToGlobal(ctr, item); + this._origin = viewUtils.localToGlobal(ctr, item); } else { - ctr = drawUtils._selectionCtr.slice(0); - ctr[0] += this.application.ninja.stage.userContentLeft; - ctr[1] += this.application.ninja.stage.userContentTop; - -// ctr[0] += this.m_deltaPoint[0]; -// ctr[1] += this.m_deltaPoint[1]; + if(this._origin) + { + if(this._delta) + { + if(this._handleMode !== null) + { +// this._origin[this._handleMode] = this._delta; + } + else + { + this._origin[0] += this._delta[0]; + this._origin[1] += this._delta[1]; + } + } + } + else + { + this._origin = drawUtils._selectionCtr.slice(0); + this._origin[0] += this.application.ninja.stage.userContentLeft; + this._origin[1] += this.application.ninja.stage.userContentTop; + } } } - - return ctr; } }, @@ -304,7 +351,9 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, // Draw tool handles - var base = this._getHandlesOrigin(); + this._updateHandlesOrigin(); + var base = this._origin.slice(0); + var len = this.application.ninja.selectedElements.length; var lMode = this._inLocalMode; if(len === 1) @@ -334,14 +383,18 @@ exports.Translate3DToolBase = Montage.create(ModifierToolBase, this._handles[1]._strokeStyle = 'rgba(0, 255, 0, 0.2)'; break; } + if( delta && (len > 1) ) + { + base[this._handleMode] += ~~delta; + } } this._handles[0].draw(base, item, lMode); this._handles[1].draw(base, item, lMode); this._handles[2].draw(base, item, lMode); - if(delta) + if(delta && (this._handleMode !== null)) { - this._handles[this._handleMode].drawDelta(delta); + this._handles[this._handleMode].drawDelta(~~delta); } this._handles[0]._strokeStyle = 'rgba(255, 0, 0, 1)'; diff --git a/js/tools/TranslateObject3DTool.js b/js/tools/TranslateObject3DTool.js index 92b9b2f7..60633e74 100755 --- a/js/tools/TranslateObject3DTool.js +++ b/js/tools/TranslateObject3DTool.js @@ -32,6 +32,7 @@ exports.TranslateObject3DTool = Object.create(Translate3DToolBase, { this._snapToGrid = snapManager.gridSnapEnabledAppLevel(); this._dragPlane = null; + this._clickedOnStage = false; var do3DSnap = true; if(this._handleMode === null) @@ -43,9 +44,9 @@ exports.TranslateObject3DTool = Object.create(Translate3DToolBase, { } else { - this._delta = 0; + this._delta = null; // special case for z-translation - if( this._handleMode && (this._handleMode === 2) ) + if(this._handleMode === 2) { this._dragPlane = viewUtils.getNormalToUnprojectedElementPlane(this._target); snapManager.setupDragPlaneFromPlane(this._dragPlane); @@ -65,11 +66,25 @@ exports.TranslateObject3DTool = Object.create(Translate3DToolBase, { // a snap on the mouse down var hitRec = snapManager.snap(point.x, point.y, do3DSnap); - // TODO - Check that hitRec's element matches element that browser says we clicked on - var elt = this.application.ninja.stage.GetElement(event); - if(elt !== hitRec.getElement()) + if(this._handleMode === 2) { - hitRec = snapManager.findHitRecordForElement(elt); + // translate z doesn't snap to element so hitRec's element will always be different + // from what the browser says we clicked on. So, skip this check. + } + else + { + // Check that hitRec's element matches element that browser says we clicked on + // TODO - This is still not working when using a handle that is on top of an + // element that is not currently selected + var elt = this.application.ninja.stage.GetSelectableElement(event); + if(elt && (elt !== hitRec.getElement())) + { + hitRec = snapManager.findHitRecordForElement(elt); + } + if(elt === this.application.ninja.currentSelectedContainer) + { + this._clickedOnStage = true; + } } // we don't want to snap to selected objects during the drag @@ -91,8 +106,17 @@ exports.TranslateObject3DTool = Object.create(Translate3DToolBase, { snapManager.enableSnapAlign( snapManager.snapAlignEnabledAppLevel() ); } - // parameterize the snap point on the target - this._snapParam = this.parameterizeSnap( hitRec ); + if(this._handleMode === 2) + { + // TODO - not sure how to parameterize point in z-translate mode + this.clickedObject = this._target; + this._snapParam = [0, 0, 0]; + } + else + { + // parameterize the snap point on the target + this._snapParam = this.parameterizeSnap( hitRec ); + } if(!this._dragPlane) { diff --git a/js/tools/drawing-tool.js b/js/tools/drawing-tool.js index 603f63a3..dc5f7996 100755 --- a/js/tools/drawing-tool.js +++ b/js/tools/drawing-tool.js @@ -157,6 +157,8 @@ exports.DrawingTool = Montage.create(ToolBase, { this.mouseUpHitRec = null; this.downPoint.x = null; this.downPoint.y = null; + this.upPoint.x = null; + this.upPoint.y = null; this._isDrawing = false; if (this.drawingFeedback.mode === "Draw3D") { diff --git a/js/tools/modifier-tool-base.js b/js/tools/modifier-tool-base.js index 80f7d758..94b806fd 100755 --- a/js/tools/modifier-tool-base.js +++ b/js/tools/modifier-tool-base.js @@ -134,7 +134,7 @@ exports.ModifierToolBase = Montage.create(DrawingTool, { var hitRec = snapManager.snap(point.x, point.y, do3DSnap); // TODO - Check that hitRec's element matches element that browser says we clicked on - var elt = this.application.ninja.stage.GetElement(event); + var elt = this.application.ninja.stage.GetSelectableElement(event); if(elt !== hitRec.getElement()) { hitRec = snapManager.findHitRecordForElement(elt); @@ -552,6 +552,9 @@ exports.ModifierToolBase = Montage.create(DrawingTool, { // update the target this._mouseUpHitRec = hitRec; + var pt = hitRec.getScreenPoint(); + this.upPoint.x = pt[0]; + this.upPoint.y = pt[1]; } } } @@ -579,12 +582,15 @@ exports.ModifierToolBase = Montage.create(DrawingTool, { this.downPoint.x = null; this.downPoint.y = null; + this.upPoint.x = null; + this.upPoint.y = null; // this.isDrawing = false; if(this._canSnap) { this.cleanupSnap(); } + this._mode = 0; } }, @@ -764,6 +770,8 @@ exports.ModifierToolBase = Montage.create(DrawingTool, { captureSelectionDrawn: { value: function(event){ this._targets = []; + this._origin = null; + this._delta = null; var len = this.application.ninja.selectedElements.length; if(len) @@ -866,7 +874,6 @@ exports.ModifierToolBase = Montage.create(DrawingTool, { this._updateTargets(true); } - this.endDraw(event); this._hasDraw = false; } if(this._handleMode !== null) @@ -874,6 +881,7 @@ exports.ModifierToolBase = Montage.create(DrawingTool, { this._handleMode = null; this._delta = null; } + this.endDraw(event); this.DrawHandles(); } }, @@ -998,19 +1006,6 @@ exports.ModifierToolBase = Montage.create(DrawingTool, { } }, - _updateDelta: { - value: function(delta, handleMode){ - if(this._clickedObject !== this.application.ninja.currentDocument.documentRoot) - { - this._delta += ~~(delta[handleMode]); - } - else - { - this._delta = ~~(delta[handleMode]); - } - } - }, - modifyElements: { value: function(data, event) { // override in subclasses. -- cgit v1.2.3 From 163ffbf531872a81c4d4be4321b065c22cf0715b Mon Sep 17 00:00:00 2001 From: Eric Guzman Date: Fri, 16 Mar 2012 17:09:08 -0700 Subject: Presets Panel - Adding Animations Tab --- .../animations-presets.reel/animations-presets.css | 5 ++ .../animations-presets.html | 59 ++++++++++++++ .../animations-presets.reel/animations-presets.js | 73 +++++++++++++++++ js/panels/presets/content.reel/content.html | 12 ++- js/panels/presets/default-animation-presets.js | 94 ++++++++++++++++++++++ 5 files changed, 240 insertions(+), 3 deletions(-) create mode 100644 js/panels/presets/animations-presets.reel/animations-presets.css create mode 100644 js/panels/presets/animations-presets.reel/animations-presets.html create mode 100644 js/panels/presets/animations-presets.reel/animations-presets.js create mode 100644 js/panels/presets/default-animation-presets.js diff --git a/js/panels/presets/animations-presets.reel/animations-presets.css b/js/panels/presets/animations-presets.reel/animations-presets.css new file mode 100644 index 00000000..0441c1cf --- /dev/null +++ b/js/panels/presets/animations-presets.reel/animations-presets.css @@ -0,0 +1,5 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ \ No newline at end of file diff --git a/js/panels/presets/animations-presets.reel/animations-presets.html b/js/panels/presets/animations-presets.reel/animations-presets.html new file mode 100644 index 00000000..c9d752af --- /dev/null +++ b/js/panels/presets/animations-presets.reel/animations-presets.html @@ -0,0 +1,59 @@ + + + + + + + + + +
+
+
+ + \ No newline at end of file diff --git a/js/panels/presets/animations-presets.reel/animations-presets.js b/js/panels/presets/animations-presets.reel/animations-presets.js new file mode 100644 index 00000000..ab200212 --- /dev/null +++ b/js/panels/presets/animations-presets.reel/animations-presets.js @@ -0,0 +1,73 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ + +var Montage = require("montage/core/core").Montage, + Component = require("montage/ui/component").Component, + DefaultPresets = require("js/panels/presets/default-animation-presets").animationPresets; + +exports.AnimationsLibrary = Montage.create(Component, { + hasTemplate: { + value: true + }, + presetData : { + value: null + }, + deserializedFromTemplate : { + value: function() { + this.presetData = DefaultPresets; + } + }, + handleNodeActivation: { + value: function(presetData) { + //debugger; + var selection = this.application.ninja.selectedElements, + stylesController = this.application.ninja.stylesController, + selectorBase = presetData.selectorBase, + self = this; + + if(!selection || !selection.length || selection.length === 0) { + return false; + } + + selectorBase = stylesController.generateClassName(selectorBase); + + presetData.rules.forEach(function(rule) { + if(rule.isKeyFrameRule) { + this.application.ninja.stylesController.addRule( + '@-webkit-keyframes ' + presetData.selectorBase, + this.stringifyKeys(rule.keys) + ); + } else { + this.application.ninja.stylesController.addRule('.' + selectorBase + rule.selectorSuffix, rule.styles); + } + + }, this); + + selection.forEach(function(el) { + el._element.classList.add(selectorBase); + }, this); + + } + }, + + stringifyKeys : { + value: function(keysArray) { + var keysString = ''; + + keysArray.forEach(function(key) { + var styles = '', style; + + for(style in key.styles) { + styles += style + ':' + key.styles[style] + '; '; + } + + keysString += key.keyText + ' {' + styles + ' }'; + }); + + return keysString; + } + } +}); diff --git a/js/panels/presets/content.reel/content.html b/js/panels/presets/content.reel/content.html index 38de2a6d..f01e6435 100644 --- a/js/panels/presets/content.reel/content.html +++ b/js/panels/presets/content.reel/content.html @@ -19,7 +19,8 @@ No rights, expressed or implied, whatsoever to this software are provided by Mot "tabBar" : {"#": "tab-bar" }, "tabs" : [ {"key":"styles", "tab": {"#": "styles" }}, - {"key":"transitions", "tab": {"#": "transitions" }} + {"key":"transitions", "tab": {"#": "transitions" }}, + {"key":"animations", "tab": {"#": "animations" }} ] } }, @@ -30,8 +31,8 @@ No rights, expressed or implied, whatsoever to this software are provided by Mot "element": {"#": "librarySlot"}, "switchComponents": { "styles": {"@": "stylesLibrary"}, - "transitions": {"@": "transitionsLibrary"} - + "transitions": {"@": "transitionsLibrary"}, + "animations": {"@": "animationsLibrary"} } }, "bindings": { @@ -49,6 +50,10 @@ No rights, expressed or implied, whatsoever to this software are provided by Mot "transitionsLibrary": { "module": "js/panels/presets/transitions-presets.reel", "name": "TransitionsLibrary" + }, + "animationsLibrary": { + "module": "js/panels/presets/animations-presets.reel", + "name": "AnimationsLibrary" } } @@ -59,6 +64,7 @@ No rights, expressed or implied, whatsoever to this software are provided by Mot
diff --git a/js/panels/presets/default-animation-presets.js b/js/panels/presets/default-animation-presets.js new file mode 100644 index 00000000..10a3e906 --- /dev/null +++ b/js/panels/presets/default-animation-presets.js @@ -0,0 +1,94 @@ +/* + This file contains proprietary software owned by Motorola Mobility, Inc.
+ No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
+ (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. +
*/ + +exports.animationPresets = { + "text": "Animation Presets Library", + "children": [{ + "text": "Border Animations", + "children": [ + { + "text": "Border Morph", + "selectorBase" : "border-morph", + "rules" : [{ + "selectorSuffix" : "", + "styles" : { + "-webkit-animation": "border-morph 2s infinite" + } + }, + { + "isKeyFrameRule": true, + "keys" : [{ + "keyText": "0%", + "styles": { "border-radius": "0" } + }, { + "keyText": "50%", + "styles": { + "border-radius": "100%" + } + }, { + "keyText": "100%", + "styles": { + "border-radius": "0%" + } + }] + }] + }] + }, { + "text": "3D Animations", + "children": [ + { + "text": "Rotater", + "selectorBase" : "rotate-with-alpha-keyframes", + "rules" : [{ + "selectorSuffix" : "", + "styles" : { + "-webkit-animation-name": "rotate-with-alpha-keyframes", + "-webkit-animation-duration": "5s", + "-webkit-animation-iteration-count": "infinite", + "-webkit-animation-direction": "normal", + "-webkit-animation-timing-function": "ease-out", + "-webkit-transform-origin": "200% 50%", + "-webkit-transform-style": "preserve-3d", + "-webkit-transform": "perspective(1000)", + "-webkit-animation-delay": "0s" + } + },{ + "isKeyFrameRule": true, + "keys" : [{ + "keyText": "0%", + "styles": { + "opacity": "1", + "-webkit-transform": "perspective(1000) rotateY(0deg)" + } + }, { + "keyText": "70%", + "styles": { + "opacity": "1", + "-webkit-transform": "perspective(1000) rotateY(0deg)" + } + }, { + "keyText": "85%", + "styles": { + "opacity": "0", + "-webkit-transform": "perspective(1000) rotateY(95deg)" + } + }, { + "keyText": "86%", + "styles": { + "opacity": "0", + "-webkit-transform": "perspective(1000) rotateY(-90deg)" + } + }, { + "keyText": "100%", + "styles": { + "opacity": "1", + "-webkit-transform": "perspective(1000) rotateY(0deg)" + } + }] + }] + }] + }] +}; \ No newline at end of file -- cgit v1.2.3