/* <copyright> This file contains proprietary software owned by Motorola Mobility, Inc.<br/> No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/> (c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved. </copyright> */ var Montage = require("montage/core/core").Montage; var Component = require("montage/ui/component").Component; var nj = require("js/lib/NJUtils").NJUtils; var TimelinePanel = exports.TimelinePanel = Montage.create(Component, { hasTemplate:{ value:true }, /* === BEGIN: Models === */ _arrLayers:{ value:[] }, arrLayers:{ serializable:true, get:function () { return this._arrLayers; }, set:function (newVal) { this._arrLayers = newVal; this.needsDraw = true; this.cacheTimeline(); } }, _temparrLayers:{ value:[] }, temparrLayers:{ get:function () { return this._temparrLayers; }, set:function (newVal) { this._temparrLayers = newVal; } }, _layerRepetition:{ value:null }, layerRepetition:{ get:function () { return this._layerRepetition; }, set:function (newVal) { this._layerRepetition = newVal; } }, // Set to false to skip array caching array sets in current document _boolCacheArrays:{ value:true }, _currentLayerNumber:{ value:0 }, currentLayerNumber:{ get:function () { return this._currentLayerNumber; }, set:function (newVal) { if (newVal !== this._currentLayerNumber) { this._currentLayerNumber = newVal; this.cacheTimeline(); } } }, _currentLayerSelected:{ value: false }, currentLayerSelected:{ get:function () { return this._currentLayerSelected; }, set:function (newVal) { this._currentLayerSelected = newVal; this.cacheTimeline(); } }, _selectedLayerID:{ value:false }, selectedLayerID:{ get:function () { return this._selectedLayerID; }, set:function (newVal) { if (newVal === false) { // We are clearing the timeline, so just set the value and return. this._selectedLayerID = newVal; return; } if (newVal !== this._selectedLayerID) { var selectIndex = this.getLayerIndexByID(newVal); this._selectedLayerID = newVal; this._captureSelection = true; if (this.currentLayerSelected !== false) { this.selectLayer(selectIndex, true); } if (this.currentLayersSelected !== false) { this.selectLayers(this.currentLayersSelected); } if ((this.currentLayersSelected === false) && (this.currentLayerSelected === false)) { this.selectLayers([]); } } } }, _currentLayersSelected:{ value:[] }, currentLayersSelected:{ get:function () { return this._currentLayersSelected; }, set:function (newVal) { this._currentLayersSelected = newVal; this.cacheTimeline(); } }, _currentSelectedContainer: { value: null }, currentSelectedContainer: { get: function() { return this._currentSelectedContainer; }, set: function(newVal) { this._currentSelectedContainer = newVal; this.handleDocumentChange(); } }, _millisecondsOffset:{ value:1000 }, millisecondsOffset:{ get:function () { return this._millisecondsOffset; }, set:function (newVal) { if (newVal !== this._millisecondsOffset) { this._millisecondsOffset= newVal; this.drawTimeMarkers(); NJevent('tlZoomSlider',this); } } }, _masterDuration:{ value:0 }, masterDuration:{ serializable:true, get:function () { return this._masterDuration; }, set:function (val) { this._masterDuration = val; this.timebar.style.width = (this._masterDuration / 12) + "px"; } }, _trackRepetition:{ value:null }, trackRepetition:{ get:function () { return this._trackRepetition; }, set:function (newVal) { this._trackRepetition = newVal; } }, _selectedKeyframes:{ value:[] }, selectedKeyframes:{ serializable:true, get:function () { return this._selectedKeyframes; }, set:function (newVal) { this._selectedKeyframes = newVal; } }, _selectedTweens:{ value:[] }, selectedTweens:{ serializable:true, get:function () { return this._selectedTweens; }, set:function (newVal) { this._selectedTweens = newVal; } }, _breadCrumbContainer:{ value:null }, breadCrumbContainer:{ get:function () { return this._breadCrumbContainer; }, set:function (value) { if (this._breadCrumbContainer !== value) { this._breadCrumbContainer = value; //this.LayerBinding(); } } }, _isLayer:{ value:false }, _firstTimeLoaded:{ value:true }, _captureSelection:{ value:false }, _openDoc:{ value:false }, timeMarkerHolder:{ value:null }, _dragAndDropHelper : { value: false }, _dragAndDropHelperCoords: { value: false }, _dragAndDropHelperOffset : { value: false }, _dragLayerID : { value: null }, layersDragged:{ value:[], writable:true }, dragLayerID : { get: function() { return this._dragLayerID; }, set: function(newVal) { if (newVal !== this._dragLayerID) { this._dragLayerID = newVal; } } }, _dropLayerID : { value: null }, dropLayerID : { get: function() { return this._dropLayerID; }, set: function(newVal) { if (newVal !== this._dropLayerID) { this._dropLayerID = newVal; var dragLayerIndex = this.getLayerIndexByID(this.dragLayerID), dropLayerIndex = this.getLayerIndexByID(this.dropLayerID), dragLayer = this.arrLayers[dragLayerIndex]; this.layersDragged.push(dragLayer); this._layerDroppedInPlace = this.arrLayers[dropLayerIndex]; this.arrLayers.splice(dragLayerIndex, 1); this.arrLayers.splice(dropLayerIndex, 0, dragLayer); this.cacheTimeline(); // Clear for future DnD this._dropLayerID = null; this._dragLayerID = null; // Sometimes, just to be fun, the drop and dragend events don't fire. // So just in case, set the draw routine to delete the helper. this._deleteHelper = true; this.needsDraw = true; } } }, _appendHelper: { value: false }, _deleteHelper: { value: false }, _scrollTracks: { value: false }, useAbsolutePosition:{ value:true }, /* === END: Models === */ /* === BEGIN: Draw cycle === */ prepareForDraw:{ value:function () { this.initTimeline(); // Bind the event handler for the document change events //this.eventManager.addEventListener("onOpenDocument", this.handleDocumentChange.bind(this), false); this.eventManager.addEventListener("closeDocument", this.handleDocumentChange.bind(this), false); //this.eventManager.addEventListener("switchDocument", this.handleDocumentChange.bind(this), false); //this.eventManager.addEventListener("breadCrumbBinding",this,false); // Bind drag and drop event handlers this.container_layers.addEventListener("dragstart", this.handleLayerDragStart.bind(this), false); this.container_layers.addEventListener("dragend", this.handleLayerDragEnd.bind(this), false); this.container_layers.addEventListener("dragover", this.handleLayerDragover.bind(this), false); this.container_layers.addEventListener("drop", this.handleLayerDrop.bind(this), false); // Bind the handlers for the config menu this.checkable_animated.addEventListener("click", this.handleAnimatedClick.bind(this), false); this.checkable_relative.addEventListener("click", this.handleRelativeClick.bind(this), false); this.checkable_absolute.addEventListener("click", this.handleAbsoluteClick.bind(this), false); this.tl_configbutton.addEventListener("click", this.handleConfigButtonClick.bind(this), false); document.addEventListener("click", this.handleDocumentClick.bind(this), false); } }, willDraw:{ value:function () { if (this._isLayer) { this._isLayer = false; } } }, draw: { value: function() { // Drag and Drop: // Do we have a helper to append? if (this._appendHelper === true) { this.container_layers.appendChild(this._dragAndDropHelper); this._appendHelper = false; } // Do we need to move the helper? if (this._dragAndDropHelperCoords !== false) { if (this._dragAndDropHelper !== null) { this._dragAndDropHelper.style.top = this._dragAndDropHelperCoords; } this._dragAndDropHelperCoords = false; } // Do we need to scroll the tracks? if (this._scrollTracks !== false) { this.layout_tracks.scrollTop = this._scrollTracks; this._scrollTracks = false; } // Do we have a helper to delete? if (this._deleteHelper === true) { if (this._dragAndDropHelper === null) { // Problem....maybe a helper didn't get appended, or maybe it didn't get stored. // Try and recover the helper so we can delete it. var myHelper = this.container_layers.querySelector(".timeline-dnd-helper"); if (myHelper != null) { this._dragAndDropHelper = myHelper; } } if (this._dragAndDropHelper !== null) { // We need to delete the helper. Can we delete it from container_layers? if (this._dragAndDropHelper && this._dragAndDropHelper.parentNode === this.container_layers) { this.container_layers.removeChild(this._dragAndDropHelper); this._dragAndDropHelper = null; this._deleteHelper = false; } } this.application.ninja.elementMediator.reArrangeDOM(this.layersDragged , this._layerDroppedInPlace); this.layersDragged =[]; } } }, /* === END: Draw cycle === */ /* === BEGIN: Controllers === */ // Create an empty layer template object with minimal defaults and return it for use createLayerTemplate:{ value:function () { var returnObj = {}; returnObj.layerData = {}; returnObj.layerData.layerName = null; returnObj.layerData.layerID = null; returnObj.layerData.isMainCollapsed = true; returnObj.layerData.isPositionCollapsed = true; returnObj.layerData.isTransformCollapsed = true; returnObj.layerData.isStyleCollapsed = true; returnObj.layerData.arrLayerStyles = []; returnObj.layerData.arrLayerStyles = []; returnObj.layerData.elementsList = []; returnObj.layerData.deleted = false; returnObj.layerData.isSelected = false; returnObj.layerData.layerPosition = null; returnObj.layerData.created = false; returnObj.layerData.isTrackAnimated = false; returnObj.layerData.currentKeyframeRule = null; returnObj.layerData.trackPosition = 0; returnObj.layerData.arrStyleTracks = []; returnObj.layerData.tweens = []; returnObj.layerData.layerTag = ""; returnObj.layerData.isVisible = true; returnObj.layerData.docUUID = this.application.ninja.currentDocument._uuid; returnObj.layerData.isTrackAnimated = false; returnObj.parentElementUUID = null; returnObj.parentElement = null; return returnObj; } }, // cache Timeline data in currentDocument. cacheTimeline: { value: function() { // Store the timeline data in currentDocument... if (this._boolCacheArrays) { // ... but only if we're supposed to. this.application.ninja.currentDocument.tlArrLayers = this.arrLayers; this.application.ninja.currentDocument.tlCurrentSelectedContainer = this.application.ninja.currentSelectedContainer; this.application.ninja.currentDocument.tllayerNumber = this.currentLayerNumber; this.application.ninja.currentDocument.tlCurrentLayerSelected = this.currentLayerSelected; this.application.ninja.currentDocument.tlCurrentLayersSelected = this.currentLayersSelected; } } }, // Initialize Timeline cache in currentDocument. initTimelineCache: { value: function() { // Initialize the currentDocument for a new set of timeline data. this.application.ninja.currentDocument.isTimelineInitialized = true; this.application.ninja.currentDocument.tlArrLayers = []; this.application.ninja.currentDocument.tlCurrentSelectedContainer = this.application.ninja.currentSelectedContainer; this.application.ninja.currentDocument.tllayerNumber = this.currentLayerNumber; this.application.ninja.currentDocument.tlCurrentLayerSelected = false; this.application.ninja.currentDocument.tlCurrentLayersSelected = false; } }, // Create an array of style objects for an element, for use // in creating a new layer createLayerStyles : { value: function(ptrElement) { // TODO: Create logic to loop through // CSS properties on element and build // array of layer styles for return. // Right now this method just returns an array of one bogus style. var returnArray = [], newStyle = {}, styleID = "1@0"; // format: layerID + "@" + style counter /* Example new style newStyle.styleID = styleID; newStyle.whichView = "propval"; // Which view do we want to show, usually property/value view (see Style) newStyle.editorProperty = "top"; // the style property newStyle.editorValue = 0; // The current value newStyle.ruleTweener = false; newStyle.isSelected = false; returnArray.push(newStyle); */ return returnArray; } }, // Create an array of style track objects for an element, for use // in creating a new layer createStyleTracks : { value: function(ptrElement) { // TODO: Create logic to loop through // CSS properties on element and build // array of layer styles for return. // Right now this method just returns an array of one bogus style. var returnArray = []; return returnArray; } }, // Bind all document-specific events (pass in true to unbind) _bindDocumentEvents : { value: function(boolUnbind) { var arrEvents = ["deleteLayerClick", "newLayer", "deleteLayer", "elementAdded", "elementsRemoved", "elementReplaced", "selectionChange"], i, arrEventsLength = arrEvents.length; if (boolUnbind) { for (i = 0; i < arrEventsLength; i++) { this.eventManager.removeEventListener(arrEvents[i], this, false); } } else { for (i = 0; i < arrEventsLength; i++) { this.eventManager.addEventListener(arrEvents[i], this, false); } } } }, // Initialize the timeline, runs only once when the timeline component is first loaded initTimeline:{ value:function () { // Get some selectors this.layout_tracks = this.element.querySelector(".layout-tracks"); this.layout_markers = this.element.querySelector(".layout_markers"); // Add some event handlers this.timeline_leftpane.addEventListener("mousedown", this.timelineLeftPaneMousedown.bind(this), false); this.timeline_leftpane.addEventListener("mouseup", this.timelineLeftPaneMouseup.bind(this), false); this.layout_tracks.addEventListener("scroll", this.updateLayerScroll.bind(this), false); this.user_layers.addEventListener("scroll", this.updateLayerScroll.bind(this), false); this.end_hottext.addEventListener("changing", this.updateTrackContainerWidth.bind(this), false); this.playhead.addEventListener("mousedown", this.startPlayheadTracking.bind(this), false); this.playhead.addEventListener("mouseup", this.stopPlayheadTracking.bind(this), false); this.time_markers.addEventListener("click", this.updatePlayhead.bind(this), false); // Bind some bindings Object.defineBinding(this, "currentSelectedContainer", { boundObject:this.application.ninja, boundObjectPropertyPath:"currentSelectedContainer", oneway:true }); // Start the panel out in disabled mode by default // (Will be switched on later, if appropriate). this.enablePanel(false); } }, // Initialize the timeline for a document. // Called when a document is opened (new or existing), or when documents are switched. initTimelineForDocument:{ value:function () { var myIndex, boolAlreadyInitialized = false; this.drawTimeMarkers(); // Document switching // Check to see if we have saved timeline information in the currentDocument. if ((typeof(this.application.ninja.currentDocument.isTimelineInitialized) === "undefined")) { //console.log('TimelinePanel.initTimelineForDocument: new Document'); // No, we have no information stored. // This could mean we are creating a new file, OR are opening an existing file. // First, initialize the caches. this.initTimelineCache(); this.temparrLayers = []; // That's all we need to do for a brand new file. // But what if we're opening an existing document? if (!this.application.ninja.documentController.creatingNewFile) { // Opening an existing document. If it has DOM elements we need to restore their timeline info if (this.application.ninja.currentDocument.documentRoot.children[0]) { // Yes, it has DOM elements. Loop through them and create a new object for each. for (myIndex = 0; this.application.ninja.currentDocument.documentRoot.children[myIndex]; myIndex++) { this._openDoc = true; this.restoreLayer(this.application.ninja.currentDocument.documentRoot.children[myIndex]); } } } // Draw the repetition. this.arrLayers = this.temparrLayers; this.currentLayerNumber = this.arrLayers.length; boolAlreadyInitialized = true; } else if (this.application.ninja.currentDocument.setLevel) { //console.log('TimelinePanel.initTimelineForDocument: breadCrumbClick'); // Information stored, but we're moving up or down in the breadcrumb. // Get the current selection and restore timeline info for its children. var parentNode = this.application.ninja.currentSelectedContainer, storedCurrentLayerNumber = this.application.ninja.currentDocument.tllayerNumber; this.temparrLayers = []; for (myIndex = 0; parentNode.children[myIndex]; myIndex++) { this._openDoc = true; this.restoreLayer(parentNode.children[myIndex]); } // Draw the repetition. this.arrLayers = this.temparrLayers; this.currentLayerNumber = storedCurrentLayerNumber; boolAlreadyInitialized = true; this.application.ninja.currentDocument.setLevel = false; } else { //console.log('TimelinePanel.initTimelineForDocument: else fallback'); // we do have information stored. Use it. var i = 0, tlArrLayersLength = this.application.ninja.currentDocument.tlArrLayers.length; // We're reading from the cache, not writing to it. this._boolCacheArrays = false; for (i = 0; i < tlArrLayersLength; i++) { if (this.application.ninja.currentDocument.tlArrLayers[i].layerData.isSelected === true) { this.application.ninja.currentDocument.tlArrLayers[i].layerData._isFirstDraw = true; } else { this.application.ninja.currentDocument.tlArrLayers[i].layerData._isFirstDraw = false; } } this.arrLayers = this.application.ninja.currentDocument.tlArrLayers; this.currentLayerNumber = this.application.ninja.currentDocument.tllayerNumber; this.currentLayerSelected = this.application.ninja.currentDocument.tlCurrentLayerSelected; this.currentLayersSelected = this.application.ninja.currentDocument.tlCurrentLayersSelected; //debugger; if (typeof(this.application.ninja.currentDocument.tlCurrentSelectedContainer) !== "undefined") { // this.application.ninja.currentSelectedContainer=this.application.ninja.currentDocument.tlCurrentSelectedContainer; } // Are we only showing animated layers? if (this.application.ninja.currentDocument.boolShowOnlyAnimated) { // Fake a click. var evt = document.createEvent("MouseEvents"); evt.initMouseEvent("click"); this.checkable_animated.dispatchEvent(evt); } // Ok, done reading from the cache. this._boolCacheArrays = true; // Reset master duration this.resetMasterDuration(); } } }, // Clear the currently-displayed document (and its events) from the timeline. clearTimelinePanel:{ value:function () { // Remove events this._bindDocumentEvents(true); // Remove every event listener for every selected tween in the timeline this.deselectTweens(); // Reset visual appearance // Todo: Maybe this should be stored per document, so we can persist between document switch? this.application.ninja.timeline.playhead.style.left = "-2px"; this.application.ninja.timeline.playheadmarker.style.left = "0px"; this.application.ninja.timeline.updateTimeText(0.00); this.timebar.style.width = "0px"; this.checkable_animated.classList.remove("checked"); this.currentLayerNumber = 0; this.currentLayerSelected = false; this.currentLayersSelected = false; this.selectedKeyframes = []; this.selectedTweens = []; this._captureSelection = false; this._openDoc = false; this.end_hottext.value = 25; this.updateTrackContainerWidth(); this.masterDuration = 0; // Clear the repetitions if (this.arrLayers.length > 0) { this.arrLayers = []; this.arrLayers.length = 0; } } }, handleDocumentChange:{ value:function () { if (this.application.ninja.currentDocument == null) { // On app initialization, the binding is triggered before // there is a currentDocument. We don't do anything at that time. return; } // this.application.ninja.currentDocument.setLevel = true; this._boolCacheArrays = false; this.clearTimelinePanel(); this._boolCacheArrays = true; // Rebind the document events for the new document context this._bindDocumentEvents(); // Reinitialize the timeline...but only if there are open documents. if (this.application.ninja.documentController._documents.length > 0) { this.enablePanel(true); this.initTimelineForDocument(); } else { this.enablePanel(false); } } }, LayerBinding:{ value:function (node) { var i = 0; if(this._firstTimeLoaded){ this._firstTimeLoaded = false; return; } this.handleDocumentChange(node); } }, updateTrackContainerWidth:{ value:function () { this.container_tracks.style.width = (this.end_hottext.value * 80) + "px"; this.master_track.style.width = (this.end_hottext.value * 80) + "px"; this.time_markers.style.width = (this.end_hottext.value * 80) + "px"; if (this.timeMarkerHolder) { this.time_markers.removeChild(this.timeMarkerHolder); } this.drawTimeMarkers(); } }, updateLayerScroll:{ value:function () { this.user_layers.scrollTop = this.layout_tracks.scrollTop; this.layout_markers.scrollLeft = this.layout_tracks.scrollLeft; this.playheadmarker.style.top = this.layout_tracks.scrollTop + "px"; } }, startPlayheadTracking:{ value:function () { this.time_markers.onmousemove = this.updatePlayhead.bind(this); } }, stopPlayheadTracking:{ value:function () { this.time_markers.onmousemove = null; } }, updatePlayhead:{ value:function (event) { var clickedPosition = event.target.offsetLeft + event.offsetX; this.playhead.style.left = (clickedPosition - 2) + "px"; this.playheadmarker.style.left = clickedPosition + "px"; var currentMillisecPerPixel = Math.floor(this.millisecondsOffset / 80); var currentMillisec = currentMillisecPerPixel * clickedPosition; this.updateTimeText(currentMillisec); } }, handleSelectionChange:{ value:function () { var layerIndex, i = 0, j = 0, arrLayersLength = this.arrLayers.length, intNumSelected = this.application.ninja.selectedElements.length, checkIndex = 0; this.deselectTweens(); //console.log("TimelinePanel.handleSelectionChange") if (intNumSelected === 0) { this.selectLayers([]); this.currentLayerSelected = false; this.currentLayersSelected = false; } if (intNumSelected === 1) { this.currentLayersSelected = false; if (this.application.ninja.selectedElements[0]) { checkIndex = this.application.ninja.selectedElements[0].uuid; for (i = 0; i < arrLayersLength; i++) { var currIndex = this.arrLayers[i].layerData.elementsList[0].uuid, layerID = this.arrLayers[i].layerData.layerID, layerIndex = 0; if (checkIndex === currIndex) { layerIndex = this.getLayerIndexByID(layerID); this._captureSelection = false; this.selectLayer(layerIndex); this._captureSelection = true; } } } } if (intNumSelected > 1) { // Build an array of indexes of selected layers to give to the selectLayers method var arrSelectedIndexes = []; this.currentLayerSelected = false; for (i = 0; i < intNumSelected; i++) { var currentCheck = this.application.ninja.selectedElements[i].uuid; //console.log("checking ", currentCheck); for (j = 0; j < arrLayersLength; j++) { //console.log(".......... ", this.arrLayers[j].layerData.elementsList[0].uuid) if (currentCheck === this.arrLayers[j].layerData.elementsList[0].uuid) { //console.log("...............Yes!") arrSelectedIndexes.push(j); } } } this.selectLayers(arrSelectedIndexes); } } }, selectLayers:{ value:function (arrSelectedIndexes) { var i = 0, arrLayersLength = this.arrLayers.length, arrSelectedIndexesLength = arrSelectedIndexes.length, userSelection = false; //console.log(arrSelectedIndexes); if (this.selectedKeyframes) { this.deselectTweens(); } for (i = 0; i < arrLayersLength; i++) { this.arrLayers[i].layerData.isSelected = false; this.triggerLayerBinding(i); } this.currentLayersSelected = false; if (arrSelectedIndexesLength > 0) { this.currentLayersSelected = []; } for (i = 0; i < arrLayersLength; i++) { if (arrSelectedIndexes.indexOf(i) > -1) { this.arrLayers[i].layerData.isSelected = true; this.arrLayers[i].isSelected = true; this.triggerLayerBinding(i); this.currentLayersSelected.push(i); } } this.layerRepetition.selectedIndexes = arrSelectedIndexes; // TODO: Set up for user selection. if (userSelection) { if (this._captureSelection) { if (this.currentLayerSelected.layerData.elementsList.length >= 1) { this.application.ninja.selectionController.selectElements(this.currentLayerSelected.layerData.elementsList); } else { this.application.ninja.selectionController.executeSelectElement(); } } this._captureSelection = true; } // Finally, reset the master duration. this.resetMasterDuration(); } }, deselectTweens:{ value:function () { for (var i = 0; i < this.selectedTweens.length; i++) { this.selectedTweens[i].deselectTween(); } this.selectedTweens = null; this.selectedTweens = new Array(); } }, timelineLeftPaneMousedown:{ value:function (event) { var ptrParent = nj.queryParentSelector(event.target, ".container-layer"); if (ptrParent !== false) { var myIndex = this.getActiveLayerIndex(); if (myIndex !== false) { this.selectLayer(myIndex, true); } } this._isMousedown = true; } }, timelineLeftPaneMouseup:{ value:function (event) { this._isMousedown = false; } }, createNewLayer:{ value:function (object) { var newLayerName = "", thingToPush = this.createLayerTemplate(), myIndex = 0, i = 0, arrLayersLength = this.arrLayers.length; // Make up a layer name. this.currentLayerNumber = this.currentLayerNumber + 1; newLayerName = "Layer " + this.currentLayerNumber; // Possibly currentLayerNumber doesn't correctly reflect the // number of layers. Check that. // Commented out to fix WebGL rendering bug /*for(k = 0; k < arrLayersLength; k++){ if(this.arrLayers[k].layerData.layerName === newLayerName){ this.currentLayerNumber = this.currentLayerNumber + 1; newLayerName = "Layer " + this.currentLayerNumber; break; } }*/ // We will no longer have multiple things selected, so wipe that info out // if it isn't already gone. this.currentLayersSelected = false; // thingToPush is the template we just got. Now fill it in. thingToPush.layerData.layerName = newLayerName; thingToPush.layerData.layerTag = "<" + object.nodeName.toLowerCase() + ">"; thingToPush.layerData.layerID = this.currentLayerNumber; thingToPush.parentElement = this.application.ninja.currentSelectedContainer; thingToPush.layerData.isSelected = true; thingToPush.layerData._isFirstDraw = true; thingToPush.layerData.created = true; if (this.checkable_animated.classList.contains("checked")) { thingToPush.layerData.isVisible = false; } if (this.layerRepetition.selectedIndexes) { // There is a selected layer, so we need to splice the new layer on top of it. myIndex = this.layerRepetition.selectedIndexes[0]; if (typeof(myIndex) === "undefined") { // Edge case: sometimes there's nothing selected, so this will be "undefined" // In that case, set it to 0, the first layer. myIndex = 0; } for (var i = 0; i < this.layerRepetition.selectedIndexes.length; i++) { if (myIndex > this.layerRepetition.selectedIndexes[i]) { myIndex = this.layerRepetition.selectedIndexes[i]; } } thingToPush.layerData.layerPosition = myIndex; thingToPush.layerData.trackPosition = myIndex; this.arrLayers.splice(myIndex, 0, thingToPush); } else { thingToPush.layerData.layerPosition = myIndex; this.arrLayers.splice(myIndex, 0, thingToPush); } this.selectLayer(myIndex); } }, restoreLayer:{ value:function (ele) { var newLayerName, thingToPush = this.createLayerTemplate(); this.currentLayerNumber = this.currentLayerNumber + 1; newLayerName = "Layer " + this.currentLayerNumber; if(ele.dataset.storedLayerName){ newLayerName = ele.dataset.storedLayerName; } thingToPush.layerData.layerName = newLayerName; thingToPush.layerData.layerID = this.currentLayerNumber; thingToPush.layerData.layerTag = "<" + ele.nodeName.toLowerCase() + ">"; thingToPush.parentElement = this.application.ninja.currentSelectedContainer; if (this.checkable_animated.classList.contains("checked")) { thingToPush.layerData.isVisible = false; } // Are there styles to add? thingToPush.layerData.arrLayerStyles = this.createLayerStyles(); thingToPush.layerData.arrStyleTracks = this.createStyleTracks(); if (this._openDoc) { thingToPush.layerData.elementsList.push(ele); } this.temparrLayers.splice(0, 0, thingToPush); thingToPush.layerData.trackPosition = this.temparrLayers.length - 1; thingToPush.layerData.layerPosition = this.temparrLayers.length - 1; this._openDoc = false; } }, deleteLayer:{ value:function (arrElements) { // Only delete a selected layers. If no layers are selected, do nothing. var i = 0, arrLayers = document.querySelectorAll(".container-layers .container-layer"), arrLayersLength = arrLayers.length; for (i = arrLayersLength -1; i >= 0; i--) { if (arrLayers[i].classList.contains("selected")) { this.arrLayers.splice(i, 1); } } this.currentLayerSelected = false; this.currentLayersSelected = false; this.resetMasterDuration(); /* var length = elements.length; while(length>0){ if (this.layerRepetition.selectedIndexes.length > 0) { // Delete the selected layer. var myIndex = this.layerRepetition.selectedIndexes[0]; this.arrLayers.splice(myIndex, 1); var selectIndex = this.arrLayers.length; this.resetMasterDuration(); if(selectIndex>0){ this.selectLayer(selectIndex-1); } length--; } } */ } }, resetMasterDuration:{ value:function(){ var trackDuration = 0, arrLayersLength = this.arrLayers.length, i = 0; if (arrLayersLength > 0) { for (i = 0; i < arrLayersLength; i++) { var currLength = this.arrLayers[i].layerData.trackDuration; if (currLength > trackDuration) { trackDuration = currLength; } } } this.masterDuration = trackDuration; } }, handleElementAdded:{ value:function() { this.createNewLayer(this.application.ninja.selectedElements[0]); if (typeof(this.currentLayerSelected) === "undefined") { // Edge case: currentLayerSelected needs to be initialized. this.currentLayerSelected = {}; this.currentLayerSelected.layerData = {}; this.currentLayerSelected.layerData.elementsList = []; } this.currentLayerSelected.layerData.elementsList.push(this.application.ninja.selectedElements[0]); this.currentLayerSelected.layerData.elementsList[0].dataset.storedLayerName = this.currentLayerSelected.layerData.layerName; } }, handleElementsRemoved:{ value:function (event) { var deleteElements = event.detail; //console.log("TimelinePanel.handleElementsRemoved; event.detail is ", event.detail); //debugger; this.deleteLayer(deleteElements); } }, handleElementReplaced:{ value:function(event){ this.currentLayerSelected.layerData.elementsList.pop(); this.currentLayerSelected.layerData.elementsList.push(event.detail.data.newChild); this.currentLayerSelected.layerData.animatedElement = event.detail.data.newChild; } }, drawTimeMarkers:{ value:function () { this.timeMarkerHolder = document.createElement("div"); if(this.time_markers.children[0]){ this.time_markers.removeChild(this.time_markers.children[0]); } this.time_markers.appendChild(this.timeMarkerHolder); var i; var totalMarkers = Math.floor(this.time_markers.offsetWidth / 80); for (i = 0; i < totalMarkers; i++) { var timeMark = document.createElement("div"); var markValue = this.calculateTimeMarkerValue(i); timeMark.className = "timemark"; timeMark.innerHTML = markValue; this.timeMarkerHolder.appendChild(timeMark); } } }, calculateTimeMarkerValue:{ value:function (currentMarker) { var currentMilliseconds = currentMarker * this.millisecondsOffset; return this.convertMillisecondsToTime(currentMilliseconds); } }, updateTimeText:{ value:function (millisec) { this.timetext.innerHTML = this.convertMillisecondsToTime(millisec); } }, convertMillisecondsToTime:{ value:function(millisec){ var timeToReturn; var sec = (Math.floor((millisec / 1000))) % 60; var min = (Math.floor((millisec / 1000) / 60)) % 60; var milliSeconds = String(Math.round(millisec / 10)); var returnMillisec = milliSeconds.slice(milliSeconds.length - 2, milliSeconds.length); var returnSec; var returnMin; if (sec < 10) { returnSec = "0" + sec; } else { returnSec = sec; } if (min < 10) { returnMin = "0" + min; } else { returnMin = min; } if (returnMillisec == "0") { returnMillisec = "0" + returnMillisec; } timeToReturn = returnMin + ":" + returnSec + ":" + returnMillisec; return timeToReturn; } }, createLayerHashTable:{ value:function (key, value) { var hashLayerObject; hashLayerObject = Object.create(Object.prototype, { counter:{ value:0, writable:true }, setItem:{ value:function (key, value, index) { if (hashLayerObject[key] === undefined) { hashLayerObject[key] = {}; } if (hashLayerObject[key][index] !== undefined) { for (this.counter = index; hashLayerObject[key][this.counter]; this.counter++) { } for (; this.counter !== index; this.counter--) { hashLayerObject[key][this.counter] = hashLayerObject[key][this.counter - 1]; } } hashLayerObject[key][index] = value; this.counter = 0; } }, getItem:{ value:function (key) { return hashLayerObject[key]; } } }); return hashLayerObject; } }, selectLayer:{ value:function (layerIndex, userSelection) { var i = 0; var arrLayersLength = this.arrLayers.length; if (this.selectedKeyframes) { this.deselectTweens(); } for (i = 0; i < arrLayersLength; i++) { if (i === layerIndex) { this.arrLayers[i].layerData.isSelected = true; } else { this.arrLayers[i].layerData.isSelected = false; } this.triggerLayerBinding(i); } this.layerRepetition.selectedIndexes = [layerIndex]; this.currentLayerSelected = this.arrLayers[layerIndex]; if (userSelection) { if (this._captureSelection) { if (this.currentLayerSelected.layerData.elementsList.length >= 1) { this.application.ninja.selectionController.selectElements(this.currentLayerSelected.layerData.elementsList); } else { this.application.ninja.selectionController.executeSelectElement(); } } this._captureSelection = true; } this.resetMasterDuration(); } }, getLayerIndexByID:{ value:function (layerID, tempArr) { var i = 0, returnVal = false, arrLayersLength = this.arrLayers.length; if (tempArr) { var tempArrLength = this.temparrLayers.length; for (i = 0; i < tempArrLength; i++) { if (this.temparrLayers[i].layerData.layerID === layerID) { returnVal = i; } } } else { for (i = 0; i < arrLayersLength; i++) { if (this.arrLayers[i].layerData.layerID === layerID) { returnVal = i; } } } return returnVal; } }, getLayerIndexByName:{ value:function (layerName) { var i = 0, returnVal = false, arrLayersLength = this.arrLayers.length; for (i = 0; i < arrLayersLength; i++) { if (this.arrLayers[i].layerData.layerName === layerName) { returnVal = i; } } return returnVal; } }, getActiveLayerIndex:{ value:function () { var i = 0, returnVal = false, arrLayersLength = this.arrLayers.length; for (i = 0; i < arrLayersLength; i++) { if (this.arrLayers[i].layerData.isActive === true) { returnVal = i; this.arrLayers[i].layerData.isActive = false; } } return returnVal; } }, enablePanel:{ value:function (boolEnable) { if (boolEnable) { this.timeline_disabler.style.display = "none"; } else { this.timeline_disabler.style.display = "block"; } } }, handleConfigButtonClick: { value: function(event) { event.stopPropagation(); this.handleCheckableClick(event); } }, handleDocumentClick: { value: function(event) { if (this.tl_configbutton.classList.contains("checked")) { this.tl_configbutton.classList.remove("checked"); } } }, handleAnimatedClick: { value: function(event) { if (typeof(this.application.ninja.currentDocument) === "undefined") { return; } if (this.application.ninja.currentDocument == null) { return; } this.handleCheckableClick(event); this.application.ninja.currentDocument.boolShowOnlyAnimated = event.currentTarget.classList.contains("checked"); var boolHide = false, i = 0, arrLayersLength = this.arrLayers.length; if (event.currentTarget.classList.contains("checked")) { // Hide layers with isAnimated = false; boolHide = true; } for (i = 0; i < arrLayersLength; i++) { if (boolHide) { // Hide layers with isAnimated = false if (this.arrLayers[i].layerData.isTrackAnimated === false) { this.arrLayers[i].layerData.isVisible = false; this.triggerLayerBinding(i); } } else { this.arrLayers[i].layerData.isVisible = true; this.triggerLayerBinding(i); } } } }, handleRelativeClick: { value: function(event) { if (!event.currentTarget.classList.contains("checked")) { this.handleCheckableClick(event); } this.checkable_absolute.classList.remove("checked"); this.useAbsolutePosition = false; } }, handleAbsoluteClick: { value: function(event) { if (!event.currentTarget.classList.contains("checked")) { this.handleCheckableClick(event); } this.checkable_relative.classList.remove("checked"); this.useAbsolutePosition = true; } }, handleCheckableClick: { value: function(event) { if (event.currentTarget.classList.contains("checked")) { event.currentTarget.classList.remove("checked"); } else { event.currentTarget.classList.add("checked"); } } }, // Trigger the layer/track data binding triggerLayerBinding : { value: function(intIndex) { if (this.arrLayers[intIndex].layerData.triggerBinding === true) { this.arrLayers[intIndex].layerData.triggerBinding = false; } else { this.arrLayers[intIndex].layerData.triggerBinding = true; } } }, handleLayerDragStart : { value: function(event) { var dragIcon = document.createElement("img"); event.dataTransfer.effectAllowed = 'move'; event.dataTransfer.setData('Text', this.identifier); // dragIcon.src = "/images/transparent.png"; dragIcon.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAA1JREFUGFdj+P//PwMACPwC/ohfBuAAAAAASUVORK5CYII=" dragIcon.width = 1; event.dataTransfer.setDragImage(dragIcon, 0, 0); // Clone the element we're dragging this._dragAndDropHelper = event.target.cloneNode(true); this._dragAndDropHelper.style.opacity = 0.8; this._dragAndDropHelper.style.position = "absolute"; this._dragAndDropHelper.style.top = "0px"; this._dragAndDropHelper.style.left = "0px"; this._dragAndDropHelper.style.zIndex = 700; this._dragAndDropHelper.style.width = window.getComputedStyle(this.container_layers, null).getPropertyValue("width"); this._dragAndDropHelper.classList.add("timeline-dnd-helper"); // Get the offset var findYOffset = function(obj) { var curleft = curtop = 0; if (obj.offsetParent) { do { curleft += obj.offsetLeft; curtop += obj.offsetTop; } while (obj = obj.offsetParent); } return curtop; } this._dragAndDropHelperOffset = findYOffset(this.container_layers); this._appendHelper = true; this._deleteHelper = false; } }, handleLayerDragover: { value: function(event) { var currPos = 0, myScrollTest = ((event.y - (this._dragAndDropHelperOffset - this.user_layers.scrollTop)) + 28) - this.user_layers.scrollTop; if ((myScrollTest < 60) && (this.user_layers.scrollTop >0)) { this._scrollTracks = (this.user_layers.scrollTop - 10) } if ((myScrollTest < 50) && (this.user_layers.scrollTop >0)) { this._scrollTracks = (this.user_layers.scrollTop - 20) } if ((myScrollTest > (this.user_layers.clientHeight + 10))) { this._scrollTracks = (this.user_layers.scrollTop + 10) } if ((myScrollTest > (this.user_layers.clientHeight + 20))) { this._scrollTracks = (this.user_layers.scrollTop + 20) } currPos = event.y - (this._dragAndDropHelperOffset - this.user_layers.scrollTop)- 28; this._dragAndDropHelperCoords = currPos + "px"; this.needsDraw = true; } }, handleLayerDragEnd : { value: function(event) { this._deleteHelper = true; this.needsDraw = true; } }, handleLayerDrop : { value: function(event) { event.stopPropagation(); event.preventDefault(); this._deleteHelper = true; this.needsDraw = true; } }, /* === END: Controllers === */ /* === BEGIN: Logging routines === */ _boolDebug:{ enumerable:false, value:false // set to true to enable debugging to console; false for turning off all debugging. }, boolDebug:{ get:function () { return this._boolDebug; }, set:function (boolDebugSwitch) { this._boolDebug = boolDebugSwitch; } }, log:{ value:function (strMessage) { if (this.boolDebug) { console.log(this.getLineNumber() + ": " + strMessage); } } }, getLineNumber:{ value:function () { try { throw new Error('bazinga') } catch (e) { return e.stack.split("at")[3].split(":")[2]; } } } /* === END: Logging routines === */ });