diff --git a/README.md b/README.md index 5665a9f..68563f3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Timeliner -Timeliner is a graphical javascript library that can prototype and create animations quickly that works across different javascript / webgl frameworks. You may find timeline familiar if you have used adobe flash, after effects, edge animate or other animation software. Except this in its really early (prototyping) stage with no testers/users but myself, so expect breakages and use at your own risk. +Timeliner is a graphical javascript library that helps create and prototype animations quickly. It's works with different javascript / webgl frameworks. You may find timeline familiar if you have used adobe flash, after effects, edge animate or other animation software. Except this in its really early (prototyping) stage with no testers/users but myself, so expect breakages and use at your own risk. Follow [blurspline](https://twitter.com/blurspline) on twitter for updates. @@ -21,19 +21,21 @@ There are currently already couple of timeline libraries which are pretty good ( (Side note: mrdoob's [talk on this](http://2013.jsconf.asia/blog/2013/11/8/jsconfasia-2013-mrdoob-ricardo-cabello-framejs) also showcase interesting editors used by the demoscene) 4. [TweenTime](https://github.com/idflood/TweenTime/) by idflood. -I initally wanted to polish and improve this to the point I'm satisfied first. However hearing Ben Schwarz say that a cat dies everytime code doesn't get publish during cssconf asia 2014, here it is. +I had initally wanted to polish and improve this to the point I'm satisfied first. However hearing Ben Schwarz say that a cat dies everytime code doesn't get publish during cssconf asia 2014, here it is. ## Philosophy -Timeliner should be lightweight and can be added into any webpages with ease. It could work as an included script, bookmarklet, or part of a bigger project with capabilities to work with other controls like dat.gui or gui.js. +Timeliner should be lightweight and can be added into any webpages with ease. It could work as an included script, bookmarklet, or part of a bigger project with capabilities to work with other controls like dat.gui or gui.js. Styles, HTML, Icons are all embed in the Javascript code. ## Usage -include the timeliner.js file. +Include the timeliner.js file. ```js ``` +Load data by code, file upload or loading from saved localStorage. + ```js // target is a "pojo" which gets updated when values change. var target = { @@ -63,6 +65,12 @@ or 2. Select easing type from the dropdown ## Curent Features +1.3.0 +- autosave +- load (localstorage, new, autosave, filesystem) +- save (export, localstorage, download) +- ui tweaks + 1.2.0 - icons using extracted fontawesome data - slightly npm-ify @@ -70,8 +78,10 @@ or - basic keyboard shortcuts - basic hdpi - basic touch support + 1.1.0 - undo / redo (basic) + 1.0.0 - slider time scale (basic) - fix positioning mouse events @@ -92,14 +102,13 @@ or - adjust values (basic) ## TODO -- save / load +- scrolling - attempt virtual-dom / v.rendering - curve editor +- graph editor - support audio -- scrolling - support guestures - remote control - insert keyframes should interpolate -- tweened values -- ghosting / onioning skinning +- ghosting / onioning skinning tweened values - a whole ton more \ No newline at end of file diff --git a/screenshot.png b/screenshot.png index b547cec..233933d 100644 Binary files a/screenshot.png and b/screenshot.png differ diff --git a/test.html b/test.html index 295c3be..2c5e120 100644 --- a/test.html +++ b/test.html @@ -44,8 +44,11 @@ // timeliner.addLayer('y'); // timeliner.addLayer('rotate'); -var o = [{"name":"x","values":[],"tmpValue":0,"_color":"#10b00d"},{"name":"y","values":[{"time":0,"value":3,"_color":"#4a2392","tween":"quadEaseIn"},{"time":3.3,"value":-1.1999850000000003,"_color":"#cddfd8"}],"tmpValue":-1.1999850000000003,"_color":"#344260"},{"name":"rotate","values":[{"time":0,"value":4.200005,"_color":"#ec9bb5","tween":"quadEaseInOut"},{"time":2.1,"value":9.300005,"_color":"#ac8e14","tween":"quadEaseIn"},{"time":5,"value":9.300005,"_color":"#5c659"}],"tmpValue":9.300005,"_color":"#1fa995"}]; -timeliner.load(o); +// var o = {"version":"1.2.0","modified":"Mon Dec 08 2014 10:41:11 GMT+0800 (SGT)","title":"Untitled", "layers": [{"name":"x","values":[],"tmpValue":0,"_color":"#10b00d"},{"name":"y","values":[{"time":0,"value":-1.2999770000000004,"_color":"#4a2392","tween":"quadEaseIn"},{"time":3.3,"value":-1.1999850000000003,"_color":"#cddfd8"}],"tmpValue":-1.2999770000000004,"_color":"#344260"},{"name":"rotate","values":[{"time":0,"value":4.200005,"_color":"#ec9bb5","tween":"quadEaseInOut"},{"time":2.1,"value":28.000009,"_color":"#ac8e14","tween":"quadEaseIn"},{"time":5,"value":50.400018,"_color":"#5c659"}],"tmpValue":50.400018,"_color":"#1fa995"}]}; +// timeliner.load(o); + + +timeliner.load({"version":"1.2.0","modified":"Mon Dec 08 2014 10:41:11 GMT+0800 (SGT)","title":"Untitled","layers":[{"name":"x","values":[{"time":0.1,"value":0,"_color":"#893c0f","tween":"quadEaseIn"},{"time":3,"value":3.500023,"_color":"#b074a0"}],"tmpValue":3.500023,"_color":"#6ee167"},{"name":"y","values":[{"time":0.1,"value":0,"_color":"#abac31","tween":"quadEaseOut"},{"time":0.5,"value":-1.000001,"_color":"#355ce8","tween":"quadEaseIn"},{"time":1.1,"value":0,"_color":"#47e90","tween":"quadEaseOut"},{"time":1.7,"value":-0.5,"_color":"#f76bca","tween":"quadEaseOut"},{"time":2.3,"value":0,"_color":"#d59cfd"}],"tmpValue":-0.5,"_color":"#8bd589"},{"name":"rotate","values":[{"time":0.1,"value":-25.700014000000003,"_color":"#f50ae9","tween":"quadEaseInOut"},{"time":2.8,"value":0,"_color":"#2e3712"}],"tmpValue":-25.700014000000003,"_color":"#2d9f57"}]}); var w2 = window.innerWidth / 2; var h2 = window.innerHeight / 2; diff --git a/timeliner.js b/timeliner.js index 5d61992..9050c14 100644 --- a/timeliner.js +++ b/timeliner.js @@ -1,7 +1,7 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0;) { + // quick hack + if (i >= layers.length) { + layer_uis[i].dom.style.display = 'none'; + unused_layers.push(layer_uis.pop()); + continue; + } + layer_uis[i].setState(layers[i]); layer_uis[i].repaint(s); } + visible_layers = layer_uis.length; + } this.repaint = repaint; @@ -423,10 +674,10 @@ function LayerCabinet(layers, dispatcher) { } module.exports = LayerCabinet; -},{"./font.json":3,"./settings":6,"./theme":7,"./ui/layer_view":11}],5:[function(require,module,exports){ +},{"./icon_button":4,"./settings":7,"./theme":8,"./ui/layer_view":12,"./utils":15}],6:[function(require,module,exports){ /* Layer Schema */ /* -[ +var layer_1 = [ { name: 'abc', props: { @@ -460,14 +711,16 @@ module.exports = LayerCabinet; /* Timeline Data Schema */ var sample = { - version: 1.1, + version: '1.2.0', modified: new Date, name: 'sample', - title: 'Sample Title', ui: { + current_time: 1, + duration: 100, + position: '0:0:0', bounds: '10 10 100 100', snap: 'full | left-half | top-half | right-half | bottom-half' @@ -476,8 +729,8 @@ var sample = { layers: [{ }] -} -},{}],6:[function(require,module,exports){ +}; +},{}],7:[function(require,module,exports){ var DEFAULT_TIME_SCALE = 60; module.exports = { @@ -489,7 +742,7 @@ module.exports = { LEFT_PANE_WIDTH: 250, time_scale: DEFAULT_TIME_SCALE // number of pixels to 1 secon, }; -},{}],7:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ module.exports = { // photoshop colors a: '#343434', @@ -497,7 +750,7 @@ module.exports = { c: '#b8b8b8', d: '#d6d6d6', }; -},{}],8:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ var Settings = require('./settings'), Theme = require('./theme'), @@ -958,7 +1211,7 @@ function TimelinePanel(layers, dispatcher) { } module.exports = TimelinePanel; -},{"./settings":6,"./theme":7,"./tween":10,"./utils":14}],9:[function(require,module,exports){ +},{"./settings":7,"./theme":8,"./tween":11,"./utils":15}],10:[function(require,module,exports){ /* * @author Joshua Koo http://joshuakoo.com */ @@ -972,7 +1225,12 @@ var undo = require('./undo'), utils = require('./utils'), LayerCabinet = require('./layer_cabinet'), TimelinePanel = require('./timeline_panel'), - package_json = require('../package.json') + package_json = require('../package.json'), + IconButton = require('./icon_button'), + style = utils.style, + saveToFile = utils.saveToFile, + openAs = utils.openAs, + STORAGE_PREFIX = utils.STORAGE_PREFIX ; var Z_INDEX = 999; @@ -991,20 +1249,54 @@ function LayerProp(name) { */ } -function Data() { - this.version = 1.1; - this.modified = new Date().toString(); - this.title = 'Untitled'; - - this.layers = []; +function DataStore() { + this.blank(); } +DataStore.prototype.blank = function() { + var data = {}; + + data.version = package_json.version; + data.modified = new Date().toString(); + data.title = 'Untitled'; + + data.layers = []; + + this.data = data; +}; + +DataStore.prototype.update = function() { + var data = this.data; + + data.version = package_json.version; + data.modified = new Date().toString(); +}; + +DataStore.prototype.setJSONString = function(data) { + this.data = JSON.parse(data); +}; + +DataStore.prototype.setJSON = function(data) { + this.data = data; +}; + +DataStore.prototype.getJSONString = function(format) { + return JSON.stringify(this.data, null, format); +}; + + +DataStore.prototype.get = function(path) { + return this.data[path]; +}; + function Timeliner(target) { // Aka Layer Manager / Controller // Should persist current time too. - var layers = []; - window.l2 = layers; + var data = new DataStore(); + var layers = data.get('layers'); + + window._data = data; var dispatcher = new Dispatcher(); @@ -1015,7 +1307,7 @@ function Timeliner(target) { setTimeout(function() { // hack! - undo_manager.save(new UndoState(layers, 'Loaded')); + undo_manager.save(new UndoState(data, 'Loaded'), true); }); dispatcher.on('keyframe', function(layer, value) { @@ -1035,12 +1327,12 @@ function Timeliner(target) { _color: '#' + (Math.random() * 0xffffff | 0).toString(16) }); - undo_manager.save(new UndoState(layers, 'Add Keyframe')); + undo_manager.save(new UndoState(data, 'Add Keyframe')); } else { console.log('remove from index', v); layer.values.splice(v.index, 1); - undo_manager.save(new UndoState(layers, 'Remove Keyframe')); + undo_manager.save(new UndoState(data, 'Remove Keyframe')); } layer_panel.repaint(t); @@ -1049,7 +1341,7 @@ function Timeliner(target) { }); dispatcher.on('keyframe.move', function(layer, value) { - undo_manager.save(new UndoState(layers, 'Move Keyframe')); + undo_manager.save(new UndoState(data, 'Move Keyframe')); }); // dispatcher.fire('value.change', layer, me.value); @@ -1065,10 +1357,10 @@ function Timeliner(target) { value: value, _color: '#' + (Math.random() * 0xffffff | 0).toString(16) }); - undo_manager.save(new UndoState(layers, 'Add value')); + undo_manager.save(new UndoState(data, 'Add value')); } else { v.object.value = value; - undo_manager.save(new UndoState(layers, 'Update value')); + undo_manager.save(new UndoState(data, 'Update value')); } layer_panel.repaint(t); @@ -1083,7 +1375,7 @@ function Timeliner(target) { v.entry.tween = ease_type; } - undo_manager.save(new UndoState(layers, 'Add Ease')); + undo_manager.save(new UndoState(data, 'Add Ease')); layer_panel.repaint(t); timeline.repaint(); @@ -1148,9 +1440,10 @@ function Timeliner(target) { // handle undo / redo dispatcher.on('controls.undo', function() { var history = undo_manager.undo(); - layers = JSON.parse(history.state); - layer_panel.setState(layers); - timeline.setState(layers); + data.setJSONString(history.state); + + updateState(); + var t = timeline.current_frame; layer_panel.repaint(t); timeline.repaint(); @@ -1158,18 +1451,21 @@ function Timeliner(target) { dispatcher.on('controls.redo', function() { var history = undo_manager.redo(); - layers = JSON.parse(history.state); - - layer_panel.setState(layers); - timeline.setState(layers); + data.setJSONString(history.state); + + updateState(); var t = timeline.current_frame; layer_panel.repaint(t); timeline.repaint(); }); - function repaint() { - requestAnimationFrame(repaint); + /* + Paint Routines + */ + + function paint() { + requestAnimationFrame(paint); if (start_play) { var t = (performance.now() - start_play) / 1000; @@ -1190,76 +1486,132 @@ function Timeliner(target) { timeline.resize(); timeline.repaint(); needsResize = false; - } + dispatcher.fire('resize'); + } timeline._paint(); } - repaint(); + paint(); + + /* + End Paint Routines + */ function save(name) { if (!name) name = 'autosave'; - var json = JSON.stringify(layers); + var json = data.getJSONString(); try { - localStorage['timeliner-' + name] = json; + localStorage[STORAGE_PREFIX + name] = json; + dispatcher.fire('save:done'); } catch (e) { console.log('Cannot save', name, json); } + } + + function saveAs(name) { + if (!name) name = data.get('name'); + name = prompt('Pick a name to save to (localStorage)', name); + if (name) { + data.data.name = name; + save(name); + } + } + + function saveSimply() { + var name = data.get('name'); + if (name) { + save(name); + } else { + saveAs(name); + } + } + + function exportJSON() { + var json = data.getJSONString(); + var ret = prompt('Hit OK to download otherwise Copy and Paste JSON', json); + if (!ret) return; + + // make json downloadable + json = data.getJSONString('\t'); + var fileName = 'timeliner-test' + '.json'; - prompt('Saved', json); + saveToFile(json, fileName); + } + + function loadJSONString(o) { + // should catch and check errors here + var json = JSON.parse(o); + load(json); } function load(o) { - layers = o; - layer_panel.setState(layers); - timeline.setState(layers); - layer_panel.repaint(); - timeline.repaint(); + data.setJSON(o); undo_manager.clear(); - undo_manager.save(new UndoState(layers, 'Loaded')); + undo_manager.save(new UndoState(data, 'Loaded'), true); + + updateState(); + + layer_panel.repaint(); + timeline.repaint(); } - this.save = save; - this.load = load; + function updateState() { + layers = data.get('layers'); + layer_panel.setState(layers); + timeline.setState(layers); + } - this.promptLoad = function() { - var json = prompt('Copy and Paste JSON to Load'); + function promptImport() { + var json = prompt('Paste JSON in here to Load'); if (!json) return; console.log('Loading.. ', json); - load(JSON.parse(json)); - }; - - this.promptOpen = function() { - var prefix = 'timeliner-'; - var regex = new RegExp(prefix + '(.*)'); - var matches = []; - for (var key in localStorage) { - console.log(key); - - var match = regex.exec(key); - if (match) { - matches.push(match[1]); - } - } - var title = prompt('You have saved ' + matches.join(',') - + '.\nWhich would you like to open?'); + loadJSONString(json); + } + function open(title) { if (title) { - load(JSON.parse(localStorage[prefix + title])); - } - }; - - // utils - function style(element, styles) { - for (var s in styles) { - element.style[s] = styles[s]; + loadJSONString(localStorage[STORAGE_PREFIX + title]); } } + dispatcher.on('import', function() { + promptImport(); + }.bind(this)); + + dispatcher.on('new', function() { + data.blank(); + updateState(); + + layer_panel.repaint(); + timeline.repaint(); + }); + + dispatcher.on('openfile', function() { + openAs(function(data) { + // console.log('loaded ' + data); + loadJSONString(data); + }, div); + }); + + dispatcher.on('open', open); + dispatcher.on('export', exportJSON); + + dispatcher.on('save', saveSimply); + dispatcher.on('save_as', saveAs); + + // Expose API + this.save = save; + this.load = load; + + /* + Start DOM Stuff (should separate file) + */ + var div = document.createElement('div'); div.style.cssText = 'position: absolute;'; div.style.top = '16px'; @@ -1281,6 +1633,22 @@ function Timeliner(target) { pane.style.backgroundColor = Theme.a; var pane_title = document.createElement('div'); + + var title_bar = document.createElement('span'); + pane_title.appendChild(title_bar); + + + var top_right_bar = document.createElement('span'); + top_right_bar.style.float = 'right'; + pane_title.appendChild(top_right_bar); + + // resize minimize + // var resize_small = new IconButton(10, 'resize_small', 'minimize', dispatcher); + // top_right_bar.appendChild(resize_small.dom); + + // resize full + var resize_full = new IconButton(10, 'resize_full', 'maximize', dispatcher); + top_right_bar.appendChild(resize_full.dom); style(pane_title, { position: 'absolute', @@ -1292,7 +1660,7 @@ function Timeliner(target) { overflow: 'hidden' }); - pane_title.innerHTML = 'Timeliner ' + package_json.version; + title_bar.innerHTML = 'Timeliner ' + package_json.version; var pane_status = document.createElement('div'); @@ -1317,40 +1685,63 @@ function Timeliner(target) { var label_status = document.createElement('span'); label_status.textContent = 'hello!'; + label_status.style.marginLeft = '10px'; this.setStatus = function(text) { label_status.textContent = text; }; + dispatcher.on('state:save', function(description) { + dispatcher.fire('status', description); + save('autosave'); + }); + dispatcher.on('status', this.setStatus); + // var button_save = document.createElement('button'); + // style(button_save, button_styles); + // button_save.textContent = 'Save'; + // button_save.onclick = function() { + // save(); + // }; - var button_save = document.createElement('button'); - style(button_save, button_styles); - button_save.textContent = 'Save'; - button_save.onclick = function() { - save(); - }; + // var button_load = document.createElement('button'); + // style(button_load, button_styles); + // button_load.textContent = 'Import'; + // button_load.onclick = this.promptLoad; + + // var button_open = document.createElement('button'); + // style(button_open, button_styles); + // button_open.textContent = 'Open'; + // button_open.onclick = this.promptOpen; - var button_load = document.createElement('button'); - style(button_load, button_styles); - button_load.textContent = 'Import'; - button_load.onclick = this.promptLoad; + // bottom_right.appendChild(button_load); + // bottom_right.appendChild(button_save); + // bottom_right.appendChild(button_open); - var button_open = document.createElement('button'); - style(button_open, button_styles); - button_open.textContent = 'Open'; - button_open.onclick = this.promptOpen; + var bottom_right = document.createElement('span'); + bottom_right.style.float = 'right'; pane_status.appendChild(label_status); + pane_status.appendChild(bottom_right); - pane_status.appendChild(document.createTextNode(' | ')); + /**/ + // zoom in + var zoom_in = new IconButton(12, 'zoom_in', 'zoom in', dispatcher); + // zoom out + var zoom_out = new IconButton(12, 'zoom_out', 'zoom out', dispatcher); + // settings + var cog = new IconButton(12, 'cog', 'settings', dispatcher); - pane_status.appendChild(button_open); - pane_status.appendChild(button_save); - pane_status.appendChild(button_load); - - pane_status.appendChild(document.createTextNode(' | TODO ')); + // bottom_right.appendChild(zoom_in.dom); + // bottom_right.appendChild(zoom_out.dom); + // bottom_right.appendChild(cog.dom); + + // pane_status.appendChild(document.createTextNode(' | TODO ')); + + /* + End DOM Stuff + */ var ghostpane = document.createElement('div'); ghostpane.id = 'ghostpane'; @@ -1382,18 +1773,16 @@ function Timeliner(target) { // console.log('kd', e); // }); - // Keyboard Shortcuts - // Space - play - // Enter - play from last played or beginging + // TODO: Keyboard Shortcuts + // Esc - Stop and review to last played from / to the start? + // Space - play / pause from current position + // Enter - play all // k - keyframe document.addEventListener('keydown', function(e) { var play = e.keyCode == 32; // space var enter = e.keyCode == 13; // var undo = e.metaKey && e.keyCode == 91 && !e.shiftKey; - // enter - - console.log(e.keyCode); var active = document.activeElement; // console.log( active.nodeName ); @@ -1406,9 +1795,15 @@ function Timeliner(target) { dispatcher.fire('controls.toggle_play'); } else if (enter) { + // FIXME: Return should play from the start or last played from? dispatcher.fire('controls.restart_play'); // dispatcher.fire('controls.undo'); } + else if (e.keyCode == 27) { + // Esc = stop. FIXME: should rewind head to last played from or Last pointed from? + dispatcher.fire('controls.pause'); + } + else console.log(e.keyCode); }); var needsResize = true; @@ -1483,6 +1878,17 @@ function Timeliner(target) { mouseOnTitle = false; }); + resize_full.onClick(function() { + // TOOD toggle back to restored size + if (!preSnapped) preSnapped = { + width: b.width, + height: b.height + }; + + snapType = 'full-screen'; + resizeEdges(); + }); + // pane_status.addEventListener('mouseover', function() { // mouseOnTitle = true; // }); @@ -1638,7 +2044,7 @@ function Timeliner(target) { if (clicked && clicked.isMoving) { switch(checks()) { - case 'edge-over-bounds': + case 'full-screen': setBounds(ghostpane, 0, 0, window.innerWidth, window.innerHeight); ghostpane.style.opacity = 0.2; break; @@ -1707,7 +2113,7 @@ function Timeliner(target) { // Edge Checkings // hintFull(); if (b.top < FULLSCREEN_MARGINS || b.left < FULLSCREEN_MARGINS || b.right > window.innerWidth - FULLSCREEN_MARGINS || b.bottom > window.innerHeight - FULLSCREEN_MARGINS) - return 'edge-over-bounds'; + return 'full-screen'; // hintTop(); if (b.top < MARGINS) return 'snap-top-edge'; @@ -1722,7 +2128,7 @@ function Timeliner(target) { if (b.bottom > bottomScreenEdge) return 'snap-bottom-edge'; */ - if (e.clientY < FULLSCREEN_MARGINS) return 'edge-over-bounds'; + if (e.clientY < FULLSCREEN_MARGINS) return 'full-screen'; if (e.clientY < MARGINS) return 'snap-top-edge'; @@ -1741,7 +2147,7 @@ function Timeliner(target) { function resizeEdges() { switch(snapType) { - case 'edge-over-bounds': + case 'full-screen': // hintFull(); setBounds(pane, 0, 0, window.innerWidth, window.innerHeight); break; @@ -1767,14 +2173,12 @@ function Timeliner(target) { if (clicked && clicked.isMoving) { // Snap - var snapped = { - width: b.width, - height: b.height - }; - snapType = checks(); if (snapType) { - preSnapped = snapped; + preSnapped = { + width: b.width, + height: b.height + }; resizeEdges(); } else { preSnapped = null; @@ -1792,7 +2196,7 @@ function Timeliner(target) { } window.Timeliner = Timeliner; -},{"../package.json":1,"./dispatcher":2,"./layer_cabinet":4,"./settings":6,"./theme":7,"./timeline_panel":8,"./undo":13,"./utils":14}],10:[function(require,module,exports){ +},{"../package.json":1,"./dispatcher":2,"./icon_button":4,"./layer_cabinet":5,"./settings":7,"./theme":8,"./timeline_panel":9,"./undo":14,"./utils":15}],11:[function(require,module,exports){ /**************************/ // Tweens /**************************/ @@ -1817,7 +2221,7 @@ var Tweens = { }; module.exports = Tweens; -},{}],11:[function(require,module,exports){ +},{}],12:[function(require,module,exports){ var Theme = require('../theme'), NumberUI = require('./number'), @@ -1937,7 +2341,7 @@ function LayerView(layer, dispatcher) { module.exports = LayerView; -},{"../settings":6,"../theme":7,"../tween":10,"../utils":14,"./number":12}],12:[function(require,module,exports){ +},{"../settings":7,"../theme":8,"../tween":11,"../utils":15,"./number":13}],13:[function(require,module,exports){ var Theme = require('../theme'); /**************************/ @@ -2039,13 +2443,14 @@ function NumberUI(layer, dispatcher) { } module.exports = NumberUI; -},{"../theme":7}],13:[function(require,module,exports){ +},{"../theme":8}],14:[function(require,module,exports){ /**************************/ // Undo Manager /**************************/ function UndoState(state, description) { - this.state = JSON.stringify(state); + // this.state = JSON.stringify(state); + this.state = state.getJSONString(); this.description = description; } @@ -2055,7 +2460,7 @@ function UndoManager(dispatcher, max) { this.clear(); } -UndoManager.prototype.save = function(state) { +UndoManager.prototype.save = function(state, suppress) { var states = this.states; var next_index = this.index + 1; var to_remove = states.length - next_index; @@ -2068,7 +2473,7 @@ UndoManager.prototype.save = function(state) { this.index = states.length - 1; // console.log('Undo State Saved: ', state.description); - this.dispatcher.fire('status', state.description); + if (!suppress) this.dispatcher.fire('state:save', state.description); }; UndoManager.prototype.clear = function() { @@ -2116,11 +2521,16 @@ module.exports = { UndoState: UndoState, UndoManager: UndoManager }; -},{}],14:[function(require,module,exports){ +},{}],15:[function(require,module,exports){ var Tweens = require('./tween'); module.exports = { + STORAGE_PREFIX: 'timeliner-', + Z_INDEX: 999, + style: style, + saveToFile: saveToFile, + openAs: openAs, format_friendly_seconds: format_friendly_seconds, findTimeinLayer: findTimeinLayer, timeAtLayer: timeAtLayer @@ -2130,6 +2540,87 @@ module.exports = { // Utils /**************************/ +function style(element, styles) { + for (var s in styles) { + element.style[s] = styles[s]; + } +} + +function saveToFile(string, filename) { + var a = document.createElement("a"); + document.body.appendChild(a); + a.style = "display: none"; + + var blob = new Blob([string], { type: 'octet/stream' }), // application/json + url = window.URL.createObjectURL(blob); + + a.href = url; + a.download = filename; + + fakeClick(a); + + setTimeout(function() { + // cleanup and revoke + window.URL.revokeObjectURL(url); + document.body.removeChild(a); + }, 500); +} + + + +var input, openCallback; + +function handleFileSelect(evt) { + var files = evt.target.files; // FileList object + + console.log('handle file select', files.length); + + var f = files[0]; + if (!f) return; + // Can try to do MINE match + // if (!f.type.match('application/json')) { + // return; + // } + console.log('match', f.type); + + var reader = new FileReader(); + + // Closure to capture the file information. + reader.onload = function(e) { + var data = e.target.result; + openCallback(data); + }; + + reader.readAsText(f); + + input.value = ''; +} + + +function openAs(callback, target) { + console.log('openfile...'); + openCallback = callback; + + if (!input) { + input = document.createElement('input'); + input.style.display = 'none'; + input.type = 'file'; + input.addEventListener('change', handleFileSelect); + target = target || document.body; + target.appendChild(input); + } + + fakeClick(input); +} + +function fakeClick(target) { + var e = document.createEvent("MouseEvents"); + e.initMouseEvent( + 'click', true, false, window, 0, 0, 0, 0, 0, + false, false, false, false, 0, null + ); + target.dispatchEvent(e); +} function format_friendly_seconds(s, type) { // TODO Refactor to 60fps??? @@ -2267,4 +2758,4 @@ function timeAtLayer(layer, t) { } -},{"./tween":10}]},{},[2,4,5,6,7,8,9,10,13,14]) \ No newline at end of file +},{"./tween":11}]},{},[2,4,5,6,7,8,9,10,11,14,15]) \ No newline at end of file