From a39bad832722a10f6556f91e94c3301a41f59bd5 Mon Sep 17 00:00:00 2001 From: Jonathan Duran Date: Mon, 6 Feb 2012 13:30:49 -0800 Subject: merge new timeline Signed-off-by: Jonathan Duran --- js/components/editable.reel/editable.js | 250 +++++++++ js/components/hintable.reel/hintable.js | 360 ++++++++++++ js/ninja.reel/ninja.html | 3 +- js/panels/Timeline/Collapser.js | 319 +++++++++++ js/panels/Timeline/Keyframe.reel/Keyframe.html | 27 + js/panels/Timeline/Keyframe.reel/Keyframe.js | 147 +++++ js/panels/Timeline/Keyframe.reel/css/Keyframe.css | 11 + js/panels/Timeline/Layer.reel/Layer.html | 158 ++++++ js/panels/Timeline/Layer.reel/Layer.js | 511 +++++++++++++++++ js/panels/Timeline/Layer.reel/css/Layer.css | 322 +++++++++++ js/panels/Timeline/Layer.reel/images/eye.png | Bin 0 -> 1331 bytes .../Timeline/Layer.reel/images/icon-collapsed.png | Bin 0 -> 325 bytes js/panels/Timeline/Layer.reel/images/icon-eye.png | Bin 0 -> 550 bytes js/panels/Timeline/Layer.reel/images/icon-lock.png | Bin 0 -> 475 bytes .../Timeline/Layer.reel/images/icon-minus.png | Bin 0 -> 161 bytes js/panels/Timeline/Layer.reel/images/icon-open.png | Bin 0 -> 323 bytes js/panels/Timeline/Layer.reel/images/icon-plus.png | Bin 0 -> 230 bytes .../Timeline/Layer.reel/images/lock_closed.png | Bin 0 -> 1208 bytes js/panels/Timeline/Layer.reel/images/lock_open.png | Bin 0 -> 1186 bytes .../Layer.reel/images/panelDisclosureIcon.png | Bin 0 -> 3028 bytes js/panels/Timeline/Layer.reel/scss/Layer.scss | 220 ++++++++ js/panels/Timeline/Layer.reel/scss/config.rb | 9 + js/panels/Timeline/Span.reel/Span.html | 27 + js/panels/Timeline/Span.reel/Span.js | 35 ++ js/panels/Timeline/Span.reel/css/Span.css | 6 + js/panels/Timeline/Style.reel/Style.html | 91 +++ js/panels/Timeline/Style.reel/Style.js | 603 ++++++++++++++++++++ js/panels/Timeline/Style.reel/css/Style.css | 133 +++++ js/panels/Timeline/Style.reel/scss/Style.scss | 70 +++ js/panels/Timeline/Style.reel/scss/config.rb | 9 + js/panels/Timeline/TimelineController.js | 13 - .../Timeline/TimelinePanel.reel/TimelinePanel.css | 6 - .../Timeline/TimelinePanel.reel/TimelinePanel.html | 230 +++++++- .../Timeline/TimelinePanel.reel/TimelinePanel.js | 615 +++++++++++++-------- .../TimelinePanel.reel/css/TimelinePanel.css | 249 +++++++++ .../Timeline/TimelinePanel.reel/images/pause.png | Bin 0 -> 1076 bytes .../Timeline/TimelinePanel.reel/images/play.png | Bin 0 -> 1190 bytes .../TimelinePanel.reel/images/play_next.png | Bin 0 -> 1185 bytes .../TimelinePanel.reel/images/play_prev.png | Bin 0 -> 1199 bytes .../Timeline/TimelinePanel.reel/images/plus.png | Bin 0 -> 1133 bytes .../TimelinePanel.reel/images/timetick.jpg | Bin 0 -> 737 bytes .../Timeline/TimelinePanel.reel/images/trash.png | Bin 0 -> 1154 bytes .../Timeline/TimelineTrack.reel/TimelineTrack.html | 112 ++++ .../Timeline/TimelineTrack.reel/TimelineTrack.js | 414 ++++++++++++++ .../TimelineTrack.reel/css/TimelineTrack.css | 117 ++++ .../TimelineTrack.reel/images/gridline.jpg | Bin 0 -> 724 bytes .../TimelineTrack.reel/scss/TimelineTrack.scss | 65 +++ .../Timeline/TimelineTrack.reel/scss/config.rb | 9 + js/panels/Timeline/Track.reel/Track.html | 61 ++ js/panels/Timeline/Track.reel/Track.js | 186 +++++++ js/panels/Timeline/Track.reel/css/Track.css | 26 + js/panels/Timeline/Track.reel/images/gridline.jpg | Bin 0 -> 724 bytes .../Timeline/TrackSpacer.reel/TrackSpacer.html | 26 + js/panels/Timeline/TrackSpacer.reel/TrackSpacer.js | 15 + .../Timeline/TrackSpacer.reel/css/TrackSpacer.css | 5 + js/panels/Timeline/Tween.reel/Tween.html | 47 ++ js/panels/Timeline/Tween.reel/Tween.js | 109 ++++ js/panels/Timeline/Tween.reel/css/Tween.css | 4 + 58 files changed, 5353 insertions(+), 267 deletions(-) create mode 100644 js/components/editable.reel/editable.js create mode 100644 js/components/hintable.reel/hintable.js create mode 100644 js/panels/Timeline/Collapser.js create mode 100644 js/panels/Timeline/Keyframe.reel/Keyframe.html create mode 100644 js/panels/Timeline/Keyframe.reel/Keyframe.js create mode 100644 js/panels/Timeline/Keyframe.reel/css/Keyframe.css create mode 100644 js/panels/Timeline/Layer.reel/Layer.html create mode 100644 js/panels/Timeline/Layer.reel/Layer.js create mode 100644 js/panels/Timeline/Layer.reel/css/Layer.css create mode 100644 js/panels/Timeline/Layer.reel/images/eye.png create mode 100644 js/panels/Timeline/Layer.reel/images/icon-collapsed.png create mode 100644 js/panels/Timeline/Layer.reel/images/icon-eye.png create mode 100644 js/panels/Timeline/Layer.reel/images/icon-lock.png create mode 100644 js/panels/Timeline/Layer.reel/images/icon-minus.png create mode 100644 js/panels/Timeline/Layer.reel/images/icon-open.png create mode 100644 js/panels/Timeline/Layer.reel/images/icon-plus.png create mode 100644 js/panels/Timeline/Layer.reel/images/lock_closed.png create mode 100644 js/panels/Timeline/Layer.reel/images/lock_open.png create mode 100644 js/panels/Timeline/Layer.reel/images/panelDisclosureIcon.png create mode 100644 js/panels/Timeline/Layer.reel/scss/Layer.scss create mode 100644 js/panels/Timeline/Layer.reel/scss/config.rb create mode 100644 js/panels/Timeline/Span.reel/Span.html create mode 100644 js/panels/Timeline/Span.reel/Span.js create mode 100644 js/panels/Timeline/Span.reel/css/Span.css create mode 100644 js/panels/Timeline/Style.reel/Style.html create mode 100644 js/panels/Timeline/Style.reel/Style.js create mode 100644 js/panels/Timeline/Style.reel/css/Style.css create mode 100644 js/panels/Timeline/Style.reel/scss/Style.scss create mode 100644 js/panels/Timeline/Style.reel/scss/config.rb delete mode 100644 js/panels/Timeline/TimelineController.js delete mode 100644 js/panels/Timeline/TimelinePanel.reel/TimelinePanel.css create mode 100644 js/panels/Timeline/TimelinePanel.reel/css/TimelinePanel.css create mode 100644 js/panels/Timeline/TimelinePanel.reel/images/pause.png create mode 100644 js/panels/Timeline/TimelinePanel.reel/images/play.png create mode 100644 js/panels/Timeline/TimelinePanel.reel/images/play_next.png create mode 100644 js/panels/Timeline/TimelinePanel.reel/images/play_prev.png create mode 100644 js/panels/Timeline/TimelinePanel.reel/images/plus.png create mode 100644 js/panels/Timeline/TimelinePanel.reel/images/timetick.jpg create mode 100644 js/panels/Timeline/TimelinePanel.reel/images/trash.png create mode 100644 js/panels/Timeline/TimelineTrack.reel/TimelineTrack.html create mode 100644 js/panels/Timeline/TimelineTrack.reel/TimelineTrack.js create mode 100644 js/panels/Timeline/TimelineTrack.reel/css/TimelineTrack.css create mode 100644 js/panels/Timeline/TimelineTrack.reel/images/gridline.jpg create mode 100644 js/panels/Timeline/TimelineTrack.reel/scss/TimelineTrack.scss create mode 100644 js/panels/Timeline/TimelineTrack.reel/scss/config.rb create mode 100644 js/panels/Timeline/Track.reel/Track.html create mode 100644 js/panels/Timeline/Track.reel/Track.js create mode 100644 js/panels/Timeline/Track.reel/css/Track.css create mode 100644 js/panels/Timeline/Track.reel/images/gridline.jpg create mode 100644 js/panels/Timeline/TrackSpacer.reel/TrackSpacer.html create mode 100644 js/panels/Timeline/TrackSpacer.reel/TrackSpacer.js create mode 100644 js/panels/Timeline/TrackSpacer.reel/css/TrackSpacer.css create mode 100644 js/panels/Timeline/Tween.reel/Tween.html create mode 100644 js/panels/Timeline/Tween.reel/Tween.js create mode 100644 js/panels/Timeline/Tween.reel/css/Tween.css (limited to 'js') diff --git a/js/components/editable.reel/editable.js b/js/components/editable.reel/editable.js new file mode 100644 index 00000000..1d0ad776 --- /dev/null +++ b/js/components/editable.reel/editable.js @@ -0,0 +1,250 @@ +/* ComputedStyleSubPanel.js */ +var Montage = require("montage").Montage, + Component = require("montage/ui/component").Component; + + +/* + +EDITABLE - Methods +- startEdit +- stopEdit +- value +- +- _suggest +- _suggestNext +- _suggestPrev +- _clearSuggest +- _accept +- _revert +- _setCaret + +*/ + + +exports.Editable = Montage.create(Component, { + hasTemplate: { value: false }, + + _element : { value : null }, + element : { + get : function() { + return this._element; + }, + set : function(el) { + this._element = el; + this._element.addEventListener('keydown', this, false); + this._element.addEventListener('input', this, false); + + if(this.startOnEvent) { + this._element.addEventListener(this.startOnEvent, this, false); + } + + } + }, + _readOnly : { + value: false + }, + readOnly : { + get : function() { return this._readOnly; }, + set : function(makeReadOnly) { + var action = makeReadOnly ? 'add' : 'remove'; + + this._element.classList[action](this.readOnlyClass); + + if(this.isEditable) { + this.stop(); + } + this._readOnly = makeReadOnly; + } + }, + _isEditable : { + value : false + }, + isEditable : { + get : function() { + return this._isEditable; + }, + set: function(makeEditable) { + if(this._readOnly && makeEditable) { return false; } + this._isEditable = makeEditable; + } + }, + _isDirty : { + value: false + }, + isDirty : { + get : function() { + return this._isDirty; + }, + set : function(setDirty) { + if(setDirty) { + this._isDirty = true; + this._sendEvent('dirty'); + } else { + this._isDirty = false; + } + } + }, + value : { + get: function() { + return this._element.textContent; + }, + set: function(str) { + this._element.textContent = str; + } + }, + + ///// Pre Edit Value + ///// Value stored when editing starts + ///// Useful for reverting to previous value + + _preEditValue : { + value : null + }, + start : { + value: function() { + if(!this._readOnly) { + this._isEditable = this._element.contentEditable = true; + this._element.classList.add(this.editingClass); + + ///// Save the preEditValue + this._preEditValue = this.value; + + if(this.selectOnStart) { + this.selectAll(); + } + + if(this.stopOnBlur) { + console.log('adding mousedown event listener'); + ///// Simulate blur on editable node by listening to the doc + document.addEventListener('mouseup', this, false); + } + + this._sendEvent('start'); + } + + } + }, + stop : { + value: function() { + this._isEditable = this._element.contentEditable = false; + this._element.classList.remove(this.editingClass); + + this._sendEvent('stop'); + + ///// if value is different than pre-edit val, call onchange method + if(this._preEditValue !== this.value) { + this._sendEvent('change'); + } + } + }, + selectAll : { + value : function() { + var range = document.createRange(), + sel = window.getSelection(); + + sel.removeAllRanges(); + range.selectNodeContents(this._element); + sel.addRange(range); + } + }, + setCursor : { + value : function(position) { + var index = position, + range, node, sel; + + ///// argument can be "end" or an index + if(typeof position === 'string' && position === 'end') { + index = this.value.length; + } + + sel = window.getSelection(); + sel.removeAllRanges(); + //debugger; + node = this._getFirstTextNode(); + range = document.createRange(); + range.setStart(node, index); + range.setEnd(node, index); + sel.addRange(range); + } + }, + blur : { + value : function() { + if(this._hint) { + this.accept(); + } + this.stop(); + document.removeEventListener('mouseup', this, false); + this._sendEvent('blur'); + } + }, + + /* -------------------- User Event Handling -------------------- */ + + handleKeydown : { + value : function(e) { + var k = e.keyCode; + console.log('keyCode: ' + k); + } + }, + ///// Text input has changed values + handleInput : { + value : function(e) { + if(!this.isDirty) { + this.isDirty = true; + } + + this._sendEvent('input'); + } + }, + handleMouseup : { + value : function(e) { + console.log('handle mouse down'); + ///// Listen for simulated blur event + if(this.stopOnBlur && e._event.target !== this._element) { + this.blur(); + } + } + }, + handleEvent : { + value : function(e) { + console.log("event type : " + e._event.type); + ///// If configured, start on specified event + if(e._event.type === this.startOnEvent) { + this.start(); + } + } + }, + _sendEvent : { + value : function(type) { + var evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(type, true, true); + this.dispatchEvent(evt); + } + }, + + /* -------------------- CONFIG -------------------- */ + + editingClass : { + value : 'editable' + }, + readOnlyClass : { + value : 'readOnly' + }, + selectOnStart : { + value : true + }, + startOnEvent : { + value : 'dblclick' + }, + stopOnBlur : { + value : true + }, + keyActions : { + value : { + stop : [27,9,13,186], + revert : [27], + backsp : [8] + } + } + +}); \ No newline at end of file diff --git a/js/components/hintable.reel/hintable.js b/js/components/hintable.reel/hintable.js new file mode 100644 index 00000000..79813c92 --- /dev/null +++ b/js/components/hintable.reel/hintable.js @@ -0,0 +1,360 @@ +/* ComputedStyleSubPanel.js */ +var Montage = require("montage").Montage, + Component = require("montage/ui/component").Component, + Editable = require("js/components/editable.reel").Editable; + + +/* + +EDITABLE - Methods +- startEdit +- stopEdit +- value +- +- _suggest +- _suggestNext +- _suggestPrev +- _clearSuggest +- _accept +- _revert +- _setCaret + +*/ + + +exports.Hintable = Montage.create(Editable, { + inheritsFrom : { value : Editable }, + _matchIndex : { value : 0 }, + matches : { value : [] }, + + _hint : { value : null }, + hint : { + get : function() { + return this._hint; + }, + set : function(hint) { + hint = hint || ''; + + ///// Set the hint element's text + this._getFirstTextNode(this.hintElement).textContent = hint; + ///// if hintElement was removed from the DOM, the object still + ///// exists, so it needs to be re-appended + if(this.hintElement.parentNode === null) { + this._element.appendChild(this.hintElement); + } + + this._hint = hint; + } + }, + + _hintElement : { value : null }, + hintElement : { + get : function() { + if(!this._hintElement) { + /// Remove the phantom "
" element that is generated when + /// content editable element is empty + this._children(this._element, function(item) { + return item.nodeName === 'BR'; + }).forEach(function(item) { + this._element.removeChild(item); + }, this); + + this._hintElement = document.createElement('span'); + this._hintElement.classList.add(this.hintClass); + + this._element.appendChild(this._hintElement); + } + + return this._hintElement; + }, + set : function(el) { + this._hintElement = el; + } + }, + + _getHintDifference : { + value : function() { + if(!this.matches[this._matchIndex]) { + debugger; + } + return this.matches[this._matchIndex].substr(this.value.length); + } + }, + + hintNext : { + value : function(e) { + if(e) { e.preventDefault(); } + console.log('next1'); + + if(this._matchIndex < this.matches.length - 1) { + console.log('next'); + ++this._matchIndex; + this.hint = this._getHintDifference(); + } + } + }, + hintPrev : { + value : function(e) { + if(e) { e.preventDefault(); } + console.log('prev1'); + if(this._matchIndex !== 0) { + console.log('prev'); + --this._matchIndex; + this.hint = this._getHintDifference(); + } + } + }, + + accept : { + value: function(e, preserveCaretPosition) { + if(e) { + e.preventDefault(); + } + var fullText = this._hint; + this.hint = null; + this.value += fullText; + + if(!preserveCaretPosition) { + this.setCursor('end'); + } + + this._sendEvent('accept'); + } + }, + revert : { + value : function(e, forceRevert) { + this.hint = null; + + if(this.isEditable || forceRevert) { + /// revert to old value + this.value = (this._preEditValue); + this._sendEvent('revert'); + console.log('reverting'); + + } + } + }, + value : { + get: function() { + return this._getFirstTextNode().textContent; + }, + set: function(str) { + var node = this._getFirstTextNode(); + node.textContent = str; + } + }, + + handleKeydown : { + value : function handleKeydown(e) { + var k = e.keyCode, + isCaretAtEnd, selection, text; + + this._super(arguments); + + if(k === 39) { + selection = window.getSelection(); + text = selection.baseNode.textContent; + isCaretAtEnd = (selection.anchorOffset === text.length); + } + + if(this.hint && isCaretAtEnd) { + ///// Advance the cursor + this.hint = this.hint.substr(0, 1); + this.accept(e); + this.handleInput(); + } + + this._execKeyAction(e); + } + }, + ///// Text input has changed values + handleInput : { + value : function handleInput(e) { + this._super(arguments); + + var val = this.value, + matches, hint; + console.log('val = "' + val + '"'); + //// Handle auto-suggest if configured + if(this.hints instanceof Array) { + + if(val.length > 0) { // content is not empty + + this._matchIndex = 0; + this.matches = this.hints.filter(function(h) { + return h.indexOf(val) === 0; + }).sort(); + + ///// If there are no matches, or the new value doesn't match all the + ///// previous matches, then get new list of matches + if(!this.matches.length || !this._matchesAll(val)) { + } + + if(this.matches.length) { // match(es) found + if(this.matches[this._matchIndex] !== val) { + // Suggest the matched hint, subtracting the typed-in string + // Only if the hint is not was the user has typed already + this.hint = this._getHintDifference(); + } else { + this.hint = null; + } + } else { // no matches found + this.hint = null; + } + } else { // no suggestion for empty string + this.hint = null; + } + + } + } + }, + handleBackspace : { + value : function(e) { + this.matches.length = 0; + } + }, + _matchesAll : { + value : function(value) { + return this.matches.every(function(match) { + return match.indexOf(value) === 0; + }, this); + } + }, + _execKeyAction : { + value : function(e) { + var key = e.keyCode, + keys = this.keyActions; + + if(this.hint) { + if( keys.hint.revert.indexOf(key) !== -1 ) { this.revert(e); } + if( keys.hint.accept.indexOf(key) !== -1 ) { this.accept(e); } + if( keys.hint.stop.indexOf(key) !== -1 ) { this.stop(e); } + if( keys.hint.next.indexOf(key) !== -1 ) { this.hintNext(e); } + if( keys.hint.prev.indexOf(key) !== -1 ) { this.hintPrev(e); } + if( keys.hint.backsp.indexOf(key) !== -1 ) { this.handleBackspace(e); } + } else { + if(keys.noHint.revert.indexOf(key) !== -1) { this.revert(e); } + if(keys.noHint.stop.indexOf(key) !== -1) { this.stop(e); } + //if( keys.hint.next.indexOf(key) !== -1 ) { this.handleDown(e); } + //if( keys.hint.prev.indexOf(key) !== -1 ) { this.handleUp(e); } + //if( keys.hint.backsp.indexOf(key) !== -1 ) { this.backspace(e); } + } + } + }, + + /* --------------- Utils --------------- */ + + _children : { + value : function(el, filter) { + var f = filter || function(item) { + return item.nodeType === 1; + }; + return this._toArray(el.childNodes).filter(f); + } + }, + _toArray : { + value : function(arrayLikeObj) { + return Array.prototype.slice.call(arrayLikeObj); + } + }, + _getFirstTextNode : { + value : function(el) { + ///// optional el argument specified container element + var e = el || this._element, + nodes = e.childNodes, node; + + if(nodes.length) { + for(var i=0; i" element that is generated when +// /// content editable element is empty +// this._children(this._element, function(item) { +// return item.nodeName === 'BR'; +// }).forEach(function(item) { +// this._element.removeChild(item); +// }, this); +// +// this.hintElement = document.createElement('span'); +// this.hintElement.classList.add(this.suggestClass); +// this.hintElement.appendChild(document.createTextNode(hint)); +// this._element.appendChild(this.hintElement); +// } +// +// this._hint = hint; +// } +// }, \ No newline at end of file diff --git a/js/ninja.reel/ninja.html b/js/ninja.reel/ninja.html index 706c8243..1aaa0c60 100644 --- a/js/ninja.reel/ninja.html +++ b/js/ninja.reel/ninja.html @@ -279,7 +279,8 @@ "stylesController": {"@": "stylesController"}, "filePickerController": {"@": "filePickerController"}, "newFileController": {"@": "newFileController"}, - "documentBar": {"@": "documentBar"} + "documentBar": {"@": "documentBar"}, + "timeline": {"@": "timeline"} } } diff --git a/js/panels/Timeline/Collapser.js b/js/panels/Timeline/Collapser.js new file mode 100644 index 00000000..2fbe8e83 --- /dev/null +++ b/js/panels/Timeline/Collapser.js @@ -0,0 +1,319 @@ +/* + * Collapser: Takes two elements and creates a visual "expando:" clicking on one element expands/collapses the other. + * Required properties: + * clicker: The element that will be clicked on. + * content: The element that will expand or collapse as the clicker is clicked on. + * Optional properties: + * isCollapsed: Is the content collapsed. Set to true on serialization (or initialization) to start content in collapsed state. + * Can be manually set as well. + * collapsibleClass: The CSS class to apply to the content and the clicker when collapsed. Defaults to "collapsible-collapsed". + * isAnimated: Set to true to apply a transition to expand/collapse (defaults to false). + * transitionClass: If isAnimated is set to true, the component will apply transitionClass to the content during the + * collapse process. You can then define transitionClass in your style sheet with the desired CSS transitions. + * Defaults to "collapsible-transition". + * contentHeight: If both isAnimated and isCollapsedAtStart are set to true, set contentHeight to the height of the content + * (in pixels, but without the "px") when not collapsed. If this value is not set, the first time the content is expanded + * the transition will not work. Subsequent collapses (and expansions) will transition as expected. + * isLabelClickable: Boolean that indicates whether or not the clicker should have listener events. Defaults to true; set to + * false for collapsers that will only be operated remotely. + * toggle(): Manually toggle the expand/collapse of the content. + * + */ +var Montage = require("montage/core/core").Montage, + Component = require("montage/ui/component").Component, + Collapser = exports.Collapser = Montage.create(Component, { + + // This component has no template. + hasTemplate:{ + value: false + }, + + /* === BEGIN: Models === */ + + // contentHeight: Stores the height of the content just before collapse. + _contentHeight: { + value: 0 + }, + contentHeight: { + get: function() { + return this._contentHeight; + }, + set: function(newVal) { + this._contentHeight = newVal; + } + }, + + // isCollapsing: true if the collapser is collapsing (or expanding); used in the draw cycle. + _isCollapsing: { + value: false + }, + + // isAnimated: boolean to apply transition to expand/collapse + _isAnimated : { + value: false + }, + isAnimated: { + get: function() { + return this._isAnimated; + }, + set: function(newVal) { + this._isAnimated = newVal; + } + }, + + _bypassAnimation : { + value: false + }, + bypassAnimation: { + get: function() { + return this._bypassAnimation; + }, + set: function(newVal) { + this._bypassAnimation= newVal; + } + }, + + // transitionClass: The CSS class to apply to the content during collapse to provide CSS transition. + // Note that this CSS class must be defined in your style sheet with the desired transitions. + _transitionClass : { + value: "collapsible-transition" + }, + transitionClass: { + get: function() { + return this._transitionClass; + }, + set: function(newVal) { + this._transitionClass = newVal; + } + }, + + // isCollapsed: is the content actually collapsed at this moment + _isCollapsed: { + value: "" + }, + isCollapsed : { + get: function() { + return this._isCollapsed; + }, + set: function(newVal) { + if (newVal !== this._isCollapsed) { + this._isCollapsed = newVal; + this.needsDraw = true; + } + + } + }, + + // collapsedClass: the class to apply to the clicker and content when the content is collapsed. + _collapsedClass : { + value: "collapsible-collapsed" + }, + collapsedClass: { + get: function() { + return this._collapsedClass; + }, + set: function(newVal) { + this._collapsedClass = newVal; + } + }, + + // _origOverflowValue: Stores the original overflow value of the collapsible element. + // Why store the value? While the collapsible element is collapsed, obviously we will need overflow: hidden. + // But when the collapsible element is open, we will need overflow to return to its original value. + _origOverflowValue : { + value: false + }, + + // isLabelClickable: Boolean for whether or not the label is clickable. If set to false, + // the label click listener is never applied. For collapsibles that will only be operated remotely. + // Defaults to true. + _isLabelClickable : { + value: true + }, + isLabelClickable : { + get: function() { + return this._isLabelClickable; + }, + set: function(newVal) { + this._isLabelClickable = newVal; + } + }, + + // labelClickEvent: an event to fire when the label is clicked. + _labelClickEvent: { + value: false + }, + labelClickEvent: { + get: function() { + return this._labelClickEvent; + }, + set: function(newVal) { + this._labelClickEvent = newVal; + } + }, + + // toggle: manually toggle the collapser. + toggle: { + value: function() { + if (this.bypassAnimation) { + this.isAnimated = false; + } + this.myContent.classList.remove(this.transitionClass); + this.handleCollapserLabelClick(); + } + }, + + /* === END: Models === */ + + /* === BEGIN: Draw cycle === */ + + prepareForDraw: { + value: function() { + // Add a click listener to the label for expand/collapse + if (this.isLabelClickable) { + this.clicker.identifier = "collapserLabel"; + this.clicker.addEventListener("click", this, false); + } + + // Get the original value of the overflow property: + this._origOverflowValue = window.getComputedStyle(this.myContent, null).getPropertyValue("overflow"); + + // If the content area is supposed to start out collapsed: + if (this.isCollapsed) { + this.myContent.style.height = "0px"; + // Set the overflow to hidden if it's not already + if (this._origOverflowValue !== "hidden") { + this.myContent.style.overflow = "hidden"; + } + this.myContent.classList.add(this.collapsedClass); + this.clicker.classList.add(this.collapsedClass); + } else { + this.myContent.style.height = "auto"; + this.myContent.classList.remove(this.collapsedClass); + this.clicker.classList.remove(this.collapsedClass); + } + } + }, + draw: { + value: function() { + // Is the content area expanding/collapsing? + this.myContent.classList.remove(this.transitionClass); + if (this._isCollapsing) { + + if (this.isAnimated) { + // Apply the transition class to the content. + this.myContent.classList.add(this.transitionClass); + + // Add a handler for the end of the transition, so we can tidy things up after + // the transition completes + this.myContent.addEventListener("webkitTransitionEnd", this, false); + + this.myContent.style.overflow = "hidden"; + } + + // Next, are we expanding or collapsing? + if (this.myContent.classList.contains(this.collapsedClass)) { + // It's already collapsed so we are expanding + this.myContent.style.height = this.contentHeight + "px"; + this.isCollapsed = false; + + } else { + // It's expanded so we are collapsing + this.myContent.style.height = "0px"; + this.isCollapsed = true; + + // Set the overflow to hidden if it isn't already + if (this._origOverflowValue !== "hidden") { + this.myContent.style.overflow = "hidden"; + } + } + + // Toggle the CSS class and deactivate the collapsing flag because we are now done. + this.myContent.classList.toggle(this.collapsedClass); + this.clicker.classList.toggle(this.collapsedClass); + this._isCollapsing = false; + + // Special cases: If transition does not happen (in the case of a contentHeight of 0 + // or isAnimated = false) we need to manually fire it here to do the cleanup. + if ((this.contentHeight < 3) || (!this.isAnimated)) { + this.handleWebkitTransitionEnd(); + } + } + } + }, + + /* === END: Draw cycle === */ + + /* === BEGIN: Event handlers === */ + + // Handle a click on the label + handleCollapserLabelClick: { + value: function() { + + // The user has clicked on one of the expandos. What should we do? + // First, are we expanding or collapsing? + if (!this.myContent.classList.contains(this.collapsedClass)) { + // We are collapsing! + // Save the current height of the content. + this.contentHeight = this.myContent.offsetHeight; + // Set the current height of the content to a pixel height instead of "auto" + // so that the transition can happen. + // (This doesn't actually change the appearance of the element, + // so it's okay to do here, outside the draw cycle.) + this.myContent.style.height = this.contentHeight + "px"; + + this.isCollapsed = true; + } else { + this.isCollapsed = false; + } + + // Set the collapsing flag so when the draw cycle begins + // it will know to expand/collapse. + this._isCollapsing = true; + + // Set the component to run its draw cycle. + this.needsDraw = true; + + // Dispatch my labelClick event + if (this.labelClickEvent) { + this.labelClickEvent(this.bypassAnimation); + } + + } + }, + + // This handler is bound to the transitionEnd event. If transitions + // are disabled, it is called manually. + handleWebkitTransitionEnd: { + value: function(event) { + + // Are we animating the transitions? + if (this.isAnimated) { + // Yes, transitions are animated. + // Remove the event listener so it won't fire again. + this.myContent.removeEventListener("webkitTransitionEnd", this, false); + + // remove the CSS class that supplies the transition + this.myContent.classList.remove(this.transitionClass); + } + + // Set the height of the content area to auto; this way it can expand/collapse as interactions + // happen within. + if (!this.myContent.classList.contains(this.collapsedClass)) { + this.myContent.style.height = "auto"; + // Return the overflow to its original value if it wasn't hidden + if (this._origOverflowValue !== "hidden") { + this.myContent.style.overflow = this._origOverflowValue; + } + + } + + if (this.bypassAnimation) { + this.bypassAnimation = false; + this.isAnimated = true; + } + } + } + + /* === END: Event handlers === */ +}); \ No newline at end of file diff --git a/js/panels/Timeline/Keyframe.reel/Keyframe.html b/js/panels/Timeline/Keyframe.reel/Keyframe.html new file mode 100644 index 00000000..bf21994b --- /dev/null +++ b/js/panels/Timeline/Keyframe.reel/Keyframe.html @@ -0,0 +1,27 @@ + + + + + + + + + +
+
+
+ + + \ No newline at end of file diff --git a/js/panels/Timeline/Keyframe.reel/Keyframe.js b/js/panels/Timeline/Keyframe.reel/Keyframe.js new file mode 100644 index 00000000..1259fa63 --- /dev/null +++ b/js/panels/Timeline/Keyframe.reel/Keyframe.js @@ -0,0 +1,147 @@ +var Montage = require("montage/core/core").Montage; +var Component = require("montage/ui/component").Component; + +var Keyframe = exports.Keyframe = Montage.create(Component, { + + hasTemplate:{ + value: true + }, + + _position:{ + value:0 + }, + + position:{ + serializable:true, + get:function(){ + return this._position; + }, + set:function(value){ + this._position = value; + } + }, + + _id:{ + value:0 + }, + + id:{ + serializable:true, + get:function () { + return this._id; + }, + set:function (value) { + this._id = value; + } + }, + + _timelinePosition:{ + value:0 + }, + + timelinePosition:{ + serializable:true, + get:function () { + return this._timelinePosition; + }, + set:function (value) { + this._timelinePosition = value; + } + }, + + _containingTrack:{ + value:{} + }, + + containingTrack:{ + serializable:true, + get:function () { + return this._containingTrack; + }, + set:function (value) { + this._containingTrack = value; + } + }, + + _animatedProperties:{ + value:[] + }, + + animatedProperties:{ + serializable:true, + get:function () { + return this._animatedProperties; + }, + set:function (value) { + this._animatedProperties = value; + } + }, + + prepareForDraw:{ + value:function(){ + this.tweenkeyframe.addEventListener("click", this, false); + this.animatedProperties = new Array(); + + this.animatedProperties["top"] = this.containingTrack.animatedElement.offsetTop; + this.animatedProperties["left"] = this.containingTrack.animatedElement.offsetLeft; + } + }, + + draw:{ + value:function(){ + this.tweenkeyframe.style.left = (this.position - 2) + "px"; + } + }, + + handleElementChange:{ + value:function (event) { + + if(event.detail.source && event.detail.source !== "pi") { + + var items = this.application.ninja.selectedElements; + + // update this keyframe's animated properties from the item[0] element props + this.animatedProperties["top"] = items[0]._element.offsetTop; + this.animatedProperties["left"] = items[0]._element.offsetLeft; + this.containingTrack.keyFramePropertyData[this.id] = this.animatedProperties; + + this.containingTrack.updateKeyframeRule(); + } + + + } + }, + + deselect:{ + value:function(){ + this.tweenkeyframe.classList.remove("keyframeSelected"); + + this.eventManager.removeEventListener("elementChange", this, false); + } + }, + + select:{ + value:function(){ + this.application.ninja.timeline.deselectKeyframes(); + this.tweenkeyframe.classList.add("keyframeSelected"); + this.application.ninja.timeline.playhead.style.left = (this.timelinePosition - 2) + "px"; + this.application.ninja.timeline.playheadmarker.style.left = this.timelinePosition + "px"; + this.application.ninja.timeline.selectedKeyframes.push(this); + + var currentTop = this.animatedProperties["top"] + "px"; + var currentLeft = this.animatedProperties["left"] + "px"; + + this.containingTrack.ninjaStylesContoller.setElementStyle(this.containingTrack.animatedElement, "top", currentTop); + this.containingTrack.ninjaStylesContoller.setElementStyle(this.containingTrack.animatedElement, "left", currentLeft); + + // turn on element change event listener + this.eventManager.addEventListener("elementChange", this, false); + } + }, + + handleClick:{ + value:function(ev){ + this.select(); + } + } +}); diff --git a/js/panels/Timeline/Keyframe.reel/css/Keyframe.css b/js/panels/Timeline/Keyframe.reel/css/Keyframe.css new file mode 100644 index 00000000..e66bcf10 --- /dev/null +++ b/js/panels/Timeline/Keyframe.reel/css/Keyframe.css @@ -0,0 +1,11 @@ +.tween_keyframe{ + position: absolute; + height: 16px; + width: 4px; + background-color: white; + z-index: 23; +} + +.tween_keyframe.keyframeSelected{ + background-color: blue; +} \ No newline at end of file diff --git a/js/panels/Timeline/Layer.reel/Layer.html b/js/panels/Timeline/Layer.reel/Layer.html new file mode 100644 index 00000000..add6cb38 --- /dev/null +++ b/js/panels/Timeline/Layer.reel/Layer.html @@ -0,0 +1,158 @@ + + + + + + + + + +
+
+ Label + +
+
+
+
+
+ Position + +
+
+
+
+
X
+
100px
+
+
+
Y
+
100px
+
+
+
Z
+
100px
+
+
+
+
+ Transform + +
+
+
+
+
Scale X
+
100px
+
+
+
Scale Y
+
100px
+
+
+
Skew X
+
100px
+
+
+
Skew Y
+
100px
+
+
+
Rotation
+
100px
+
+
+ +
+
+ Style + +
+
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/js/panels/Timeline/Layer.reel/Layer.js b/js/panels/Timeline/Layer.reel/Layer.js new file mode 100644 index 00000000..29171272 --- /dev/null +++ b/js/panels/Timeline/Layer.reel/Layer.js @@ -0,0 +1,511 @@ +var Montage = require("montage/core/core").Montage; +var Component = require("montage/ui/component").Component; +var Collapser = require("js/panels/Timeline/Collapser").Collapser; +var Hintable = require("js/components/hintable.reel").Hintable; +var LayerStyle = require("js/panels/Timeline/Style.reel").LayerStyle; +var DynamicText = require("montage/ui/dynamic-text.reel").DynamicText; +var defaultEventManager = require("montage/core/event/event-manager").defaultEventManager; + +var Layer = exports.Layer = Montage.create(Component, { + + hasTemplate:{ + value: true + }, + + /* Begin: Models */ + + /* Main collapser model: the main collapser for the layer */ + _mainCollapser : { + value: false + }, + mainCollapser: { + get: function() { + return this._mainCollapser; + }, + set: function(newVal) { + this._mainCollapser = newVal; + } + }, + + /* Style models: the array of styles, and the repetition that uses them */ + _arrLayerStyles : { + serializable: true, + enumerable: true, + serializable: true, + value: [] + }, + arrLayerStyles : { + serializable: true, + enumerable: true, + serializable: true, + get: function() { + return this._arrLayerStyles; + }, + set: function(newVal) { + this._arrLayerStyles = newVal; + } + }, + _styleRepetition : { + value: false + }, + styleRepetition : { + get: function() { + return this._styleRepetition; + }, + set: function(newVal) { + this._styleRepetition = newVal; + } + }, + _styleCounter : { + value: 0 + }, + + /* Layer models: the name, ID, and selected and animation booleans for the layer */ + _layerName:{ + serializable: true, + value:null, + writable:true, + enumerable:true + }, + + layerName:{ + serializable: true, + get:function(){ + return this._layerName; + }, + set:function(newVal){ + if (newVal !== this._layerName) { + this._layerEditable.value = newVal; + this._layerName = newVal; + this._layerEditable.needsDraw = true; + this.needsDraw = true; + + } + + } + }, + _layerID:{ + value:null, + writable:true, + serializable: true, + enumerable:true + }, + + layerID:{ + serializable: true, + get:function(){ + return this._layerID; + }, + set:function(value){ + this._layerID = value; + } + }, + _isSelected:{ + value: false, + writable: true, + enumerable: false + }, + + isSelected:{ + get:function(){ + return this._isSelected; + }, + set:function(value){ + this._isSelected = value; + } + }, + _isAnimated:{ + value: false, + writable: true, + enumerable: false + }, + + isAnimated:{ + get:function(){ + return this._isAnimated; + }, + set:function(value){ + this._isAnimated = value; + } + }, + _justAdded: { + value: false + }, + _layerEditable : { + value: false + }, + + // Are the various collapsers collapsed or not + _isMainCollapsed : { + value: "" + }, + isMainCollapsed : { + get: function() { + return this._isMainCollapsed; + }, + set: function(newVal) { + if (newVal !== this._isMainCollapsed) { + this._isMainCollapsed = newVal; + this.needsDraw = true; + } + } + }, + + _isTransformCollapsed : { + value: true + }, + isTransformCollapsed : { + get: function() { + return this._isTransformCollapsed; + }, + set: function(newVal) { + if (newVal !== this._isTransformCollapsed) { + this._isTransformCollapsed = newVal; + this.needsDraw = true; + } + } + }, + + _isPositionCollapsed : { + value: true + }, + isPositionCollapsed : { + get: function() { + return this._isPositionCollapsed; + }, + set: function(newVal) { + if (newVal !== this._isPositionCollapsed) { + this._isPositionCollapsed = newVal; + this.needsDraw = true; + } + } + }, + + _isStyleCollapsed : { + value: true + }, + isStyleCollapsed : { + get: function() { + return this._isStyleCollapsed; + }, + set: function(newVal) { + if (newVal !== this._isStyleCollapsed) { + this._isStyleCollapsed = newVal; + this.needsDraw = true; + } + } + }, + + + /* END: Models */ + + /* Begin: Draw cycle */ + prepareForDraw: { + value: function() { + + // Initialize myself + this.init(); + + var that = this; + + this.positionCollapser = Collapser.create(); + this.transformCollapser = Collapser.create(); + this.styleCollapser = Collapser.create(); + + // Make it editable! + this._layerEditable = Hintable.create(); + this._layerEditable.element = this.titleSelector; + this.titleSelector.identifier = "selectorEditable"; + this.titleSelector.addEventListener("click", this, false); + this._layerEditable.addEventListener("blur", function(event) { + that.handleSelectorEditableBlur(event); + }, false); + this._layerEditable.addEventListener("change", function(event) { + that.dynamicLayerName.value = that._layerEditable.value; + that.needsDraw = true; + }, false); + this._layerEditable.editingClass = "editable2"; + this._layerEditable.value = this.layerName; + this._layerEditable.needsDraw = true; + + // Change the markup into collapsible sections using the nifty Collapser component! + this.mainCollapser = Collapser.create(); + this.mainCollapser.clicker = this.clicker; + this.mainCollapser.myContent = this.myContent; + this.mainCollapser.contentHeight = 60; + this.myContent.style.height = "0px"; + this.mainCollapser.element = this.element; + //this.mainCollapser.isCollapsedAtStart = true; + this.mainCollapser.isCollapsed = this.isMainCollapsed; + this.mainCollapser.isAnimated = true; + this.element.setAttribute("data-layerid", this.layerID); + this.mainCollapser.labelClickEvent = function(boolBypass) { + var newEvent = document.createEvent("CustomEvent"); + newEvent.initCustomEvent("layerEvent", false, true); + newEvent.layerEventLocale = "content-main"; + newEvent.layerEventType = "labelClick"; + newEvent.layerID = that.layerID; + newEvent.bypassAnimation = boolBypass; + defaultEventManager.dispatchEvent(newEvent); + that.isMainCollapsed = that.mainCollapser.isCollapsed; + } + this.mainCollapser.needsDraw = true; + + this.positionCollapser.clicker = this.clickerPosition; + this.positionCollapser.myContent = this.contentPosition; + this.positionCollapser.element = this.element; + this.positionCollapser.contentHeight = 60; + // this.positionCollapser.isCollapsedAtStart = true; + this.positionCollapser.isCollapsed = this.isPositionCollapsed; + this.positionCollapser.isAnimated = true; + this.positionCollapser.labelClickEvent = function(boolBypass) { + var newEvent = document.createEvent("CustomEvent"); + newEvent.initCustomEvent("layerEvent", false, true); + newEvent.layerEventLocale = "content-position"; + newEvent.layerEventType = "labelClick"; + newEvent.layerID = that.layerID; + newEvent.bypassAnimation = boolBypass; + defaultEventManager.dispatchEvent(newEvent); + that.isPositionCollapsed = that.positionCollapser.isCollapsed; + } + this.positionCollapser.needsDraw = true; + + this.transformCollapser.clicker = this.clickerTransform; + this.transformCollapser.myContent = this.contentTransform; + this.transformCollapser.element = this.element; + this.transformCollapser.contentHeight = 100; + // this.transformCollapser.isCollapsedAtStart = true; + this.transformCollapser.isCollapsed = this.isTransformCollapsed; + this.transformCollapser.isAnimated = true; + this.transformCollapser.labelClickEvent = function(boolBypass) { + var newEvent = document.createEvent("CustomEvent"); + newEvent.initCustomEvent("layerEvent", false, true); + newEvent.layerEventLocale = "content-transform"; + newEvent.layerEventType = "labelClick"; + newEvent.layerID = that.layerID; + newEvent.bypassAnimation = boolBypass; + defaultEventManager.dispatchEvent(newEvent); + that.isTransformCollapsed = that.transformCollapser.isCollapsed; + } + this.transformCollapser.needsDraw = true; + + this.styleCollapser.clicker = this.clickerStyle; + this.styleCollapser.myContent = this.contentStyle; + this.styleCollapser.element = this.element; + this.styleCollapser.isCollapsed = this.isStyleCollapsed; + this.styleCollapser.isAnimated = true; + this.styleCollapser.labelClickEvent = function(boolBypass) { + var newEvent = document.createEvent("CustomEvent"); + newEvent.initCustomEvent("layerEvent", false, true); + newEvent.layerEventLocale = "content-style"; + newEvent.layerEventType = "labelClick"; + newEvent.layerID = that.layerID; + newEvent.bypassAnimation = boolBypass; + defaultEventManager.dispatchEvent(newEvent); + that.isStyleCollapsed = that.styleCollapser.isCollapsed; + } + this.styleCollapser.needsDraw = true; + + // Add event listeners to add and delete style buttons + this.buttonAddStyle.identifier = "addStyle"; + this.buttonAddStyle.addEventListener("click", this, false); + + this.buttonDeleteStyle.identifier = "deleteStyle"; + this.buttonDeleteStyle.addEventListener("click", this, false); + + } + }, + draw: { + value: function() { + + // Coordinate the collapsers + if (this.mainCollapser.isCollapsed !== this.isMainCollapsed) { + this.mainCollapser.bypassAnimation = true; + this.mainCollapser.toggle(); + } + if (this.positionCollapser.isCollapsed !== this.isPositionCollapsed) { + this.positionCollapser.bypassAnimation = true; + this.positionCollapser.toggle(); + } + if (this.transformCollapser.isCollapsed !== this.isTransformCollapsed) { + this.transformCollapser.bypassAnimation = true; + this.transformCollapser.toggle(); + } + if (this.styleCollapser.isCollapsed !== this.isStyleCollapsed) { + this.styleCollapser.bypassAnimation = true; + this.styleCollapser.toggle(); + } + } + }, + /* End: Draw cycle */ + + /* Begin: Controllers */ + + // Initialize a just-created layer with some basic defaults and needed selectors. + init: { + value: function() { + // Default some vars + //this.arrLayerStyles = []; + + // Get some selectors. + this.label = this.element.querySelector(".label-layer"); + this.titleSelector = this.label.querySelector(".collapsible-label"); + this.clicker = this.element.querySelector(".collapsible-clicker"); + this.myContent = this.element.querySelector(".content-layer"); + this.clickerPosition = this.element.querySelector(".clicker-position"); + this.contentPosition = this.element.querySelector(".content-position"); + this.clickerTransform = this.element.querySelector(".clicker-transform"); + this.contentTransform = this.element.querySelector(".content-transform"); + this.clickerStyle = this.element.querySelector(".clicker-style"); + this.contentStyle = this.element.querySelector(".content-style"); + this.buttonAddStyle = this.element.querySelector(".button-add"); + this.buttonDeleteStyle = this.element.querySelector(".button-delete"); + } + }, + selectLayer:{ + value:function(){ + // this.mainCollapser.header.classList.add("layerSelected"); + this.element.classList.add("layerSelected"); + this.isSelected = true; + } + }, + deselectLayer:{ + value:function(){ + // this.mainCollapser.header.classList.remove("layerSelected"); + this.element.classList.remove("layerSelected"); + this.isSelected = false; + } + }, + addStyle : { + value: function() { + // Add a new style rule. It should be added above the currently selected rule, + // Or at the end, if no rule is selected. + + var newLength = 0, + mySelection = 0, + // newStyle = LayerStyle.create(), + newStyle = {}, + newEvent = document.createEvent("CustomEvent"); + + newEvent.initCustomEvent("layerEvent", false, true); + newEvent.layerEventLocale = "styles"; + newEvent.layerEventType = "newStyle"; + newEvent.layerID = this.layerID; + newEvent.styleID = this.layerID + "@" + this._styleCounter; + + newStyle.styleID = newEvent.styleID; + newStyle.whichView = "hintable"; + newStyle.editorProperty = ""; + newStyle.editorValue = ""; + newStyle.ruleTweener = false; + + if (!!this.styleRepetition.selectedIndexes) { + mySelection = this.styleRepetition.selectedIndexes[0]; + this.arrLayerStyles.splice(mySelection, 0, newStyle); + this.styleRepetition.selectedIndexes = [mySelection]; + } else { + newLength = this.arrLayerStyles.length; + this.arrLayerStyles.push(newStyle); + mySelection = this.arrLayerStyles.length; + this.styleRepetition.selectedIndexes = [mySelection-1]; + } + + // Set up the event info and dispatch the event + + newEvent.styleSelection = mySelection; + defaultEventManager.dispatchEvent(newEvent); + + } + }, + deleteStyle : { + value: function() { + var newEvent = document.createEvent("CustomEvent"), + selectedIndex = 0; + if (this.arrLayerStyles.length > 0) { + if (!!this.styleRepetition.selectedIndexes) { + + selectedIndex = this.styleRepetition.selectedIndexes[0]; + + // Set up the event info and dispatch the event + newEvent.initCustomEvent("layerEvent", false, true); + newEvent.layerEventLocale = "styles"; + newEvent.layerEventType = "deleteStyle"; + newEvent.layerID = this.layerID; + newEvent.styleID = this.arrLayerStyles[selectedIndex].styleID; + new