├── .gitignore ├── Cakefile ├── Gemfile ├── Gemfile.lock ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── TODO.md ├── app.rb ├── assets ├── images │ ├── bg-grid.png │ ├── blacky.png │ ├── crosshairs.png │ ├── grid.png │ ├── multiple.png │ └── whitey.png ├── javascripts │ ├── app │ │ ├── controllers │ │ │ ├── element.module.coffee │ │ │ ├── element │ │ │ │ └── resizing.module.coffee │ │ │ ├── elements │ │ │ │ ├── button.module.coffee │ │ │ │ ├── canvas.module.coffee │ │ │ │ ├── ellipsis.module.coffee │ │ │ │ ├── image.module.coffee │ │ │ │ ├── input.module.coffee │ │ │ │ ├── line.module.coffee │ │ │ │ ├── rectangle.module.coffee │ │ │ │ ├── text.module.coffee │ │ │ │ └── triangle.module.coffee │ │ │ ├── header.module.coffee │ │ │ ├── inspector.module.coffee │ │ │ ├── inspector │ │ │ │ ├── background.module.coffee │ │ │ │ ├── border.module.coffee │ │ │ │ ├── border_radius.module.coffee │ │ │ │ ├── box_shadow.module.coffee │ │ │ │ ├── dimensions.module.coffee │ │ │ │ ├── font.module.coffee │ │ │ │ ├── opacity.module.coffee │ │ │ │ ├── popup_menu.module.coffee │ │ │ │ ├── stage.module.coffee │ │ │ │ ├── text_position.module.coffee │ │ │ │ ├── text_shadow.module.coffee │ │ │ │ └── text_style.module.coffee │ │ │ ├── stage.module.coffee │ │ │ └── stage │ │ │ │ ├── clipboard.module.coffee │ │ │ │ ├── context_menu.module.coffee │ │ │ │ ├── dragging.module.coffee │ │ │ │ ├── drop_area.module.coffee │ │ │ │ ├── history.module.coffee │ │ │ │ ├── key_bindings.module.coffee │ │ │ │ ├── resizing.module.coffee │ │ │ │ ├── select_area.module.coffee │ │ │ │ ├── selection.module.coffee │ │ │ │ ├── snapping.module.coffee │ │ │ │ └── zindex.module.coffee │ │ ├── index.module.coffee │ │ ├── models │ │ │ ├── history.module.coffee │ │ │ ├── properties.module.coffee │ │ │ ├── properties │ │ │ │ ├── background.module.coffee │ │ │ │ ├── border.module.coffee │ │ │ │ ├── color.module.coffee │ │ │ │ └── shadow.module.coffee │ │ │ ├── property.module.coffee │ │ │ └── serialize.module.coffee │ │ ├── parsers │ │ │ ├── css.module.js │ │ │ ├── css.pegjs │ │ │ └── import.module.coffee │ │ └── views │ │ │ ├── context_menu.jst.eco │ │ │ ├── header.jst.eco │ │ │ ├── inspector.jst.eco │ │ │ └── inspector │ │ │ ├── background.jst.eco │ │ │ ├── background │ │ │ ├── linear_gradient.jst.eco │ │ │ ├── list.jst.eco │ │ │ ├── menu.jst.eco │ │ │ └── url.jst.eco │ │ │ ├── border.jst.eco │ │ │ ├── border_radius.jst.eco │ │ │ ├── box_shadow.jst.eco │ │ │ ├── box_shadow │ │ │ ├── list.jst.eco │ │ │ └── menu.jst.eco │ │ │ ├── dimensions.jst.eco │ │ │ ├── font.jst.eco │ │ │ ├── opacity.jst.eco │ │ │ ├── text_position.jst.eco │ │ │ └── text_shadow.jst.eco │ ├── application.js │ └── lib │ │ ├── collection.module.coffee │ │ ├── color_picker.module.coffee │ │ ├── ext │ │ └── jquery.coffee │ │ ├── gradient_picker.module.coffee │ │ ├── popup.module.coffee │ │ ├── position_picker.module.coffee │ │ ├── utils.module.coffee │ │ └── views │ │ └── color_picker.jst.eco └── stylesheets │ ├── app │ ├── color_picker.css.styl │ ├── context_menu.css.styl │ ├── gradient_picker.css.styl │ ├── header.css.styl │ ├── index.css.styl │ ├── inspector.css.styl │ ├── inspector │ │ ├── background.css.styl │ │ ├── border.css.styl │ │ ├── border_radius.css.styl │ │ ├── box_shadow.css.styl │ │ ├── dimensions.css.styl │ │ ├── font.css.styl │ │ ├── opacity.css.styl │ │ ├── popup_menu.css.styl │ │ ├── text_position.css.styl │ │ └── text_shadow.css.styl │ ├── popup.css.styl │ └── stage.css.styl │ ├── application.css │ ├── mixins.styl │ └── theme.css.styl ├── config.ru ├── public ├── application.icns ├── application.png ├── assets │ ├── app │ │ ├── controllers │ │ │ ├── element.module.js │ │ │ ├── element │ │ │ │ └── resizing.module.js │ │ │ ├── elements │ │ │ │ ├── button.module.js │ │ │ │ ├── canvas.module.js │ │ │ │ ├── ellipsis.module.js │ │ │ │ ├── image.module.js │ │ │ │ ├── input.module.js │ │ │ │ ├── line.module.js │ │ │ │ ├── rectangle.module.js │ │ │ │ ├── text.module.js │ │ │ │ └── triangle.module.js │ │ │ ├── header.module.js │ │ │ ├── inspector.module.js │ │ │ ├── inspector │ │ │ │ ├── background.module.js │ │ │ │ ├── border.module.js │ │ │ │ ├── border_radius.module.js │ │ │ │ ├── box_shadow.module.js │ │ │ │ ├── dimensions.module.js │ │ │ │ ├── font.module.js │ │ │ │ ├── opacity.module.js │ │ │ │ ├── popup_menu.module.js │ │ │ │ ├── stage.module.js │ │ │ │ ├── text_position.module.js │ │ │ │ ├── text_shadow.module.js │ │ │ │ └── text_style.module.js │ │ │ ├── stage.module.js │ │ │ └── stage │ │ │ │ ├── clipboard.module.js │ │ │ │ ├── context_menu.module.js │ │ │ │ ├── dragging.module.js │ │ │ │ ├── drop_area.module.js │ │ │ │ ├── history.module.js │ │ │ │ ├── key_bindings.module.js │ │ │ │ ├── resizing.module.js │ │ │ │ ├── select_area.module.js │ │ │ │ ├── selection.module.js │ │ │ │ ├── snapping.module.js │ │ │ │ └── zindex.module.js │ │ ├── index.module.js │ │ ├── models │ │ │ ├── history.module.js │ │ │ ├── properties.module.js │ │ │ ├── properties │ │ │ │ ├── background.module.js │ │ │ │ ├── border.module.js │ │ │ │ ├── color.module.js │ │ │ │ └── shadow.module.js │ │ │ ├── property.module.js │ │ │ └── serialize.module.js │ │ └── parsers │ │ │ ├── background.pegjs │ │ │ ├── color.module.js │ │ │ ├── color.pegjs │ │ │ ├── css.module.js │ │ │ ├── css.pegjs │ │ │ ├── import.module.js │ │ │ ├── json.pegjs │ │ │ └── shadow.pegjs │ ├── application.css │ ├── application.js │ ├── bg-grid.png │ ├── blacky.png │ ├── crosshairs.png │ ├── grid.png │ ├── lib │ │ ├── collection.module.js │ │ ├── color_picker.module.js │ │ ├── gradient_picker.module.js │ │ ├── popup.module.js │ │ ├── position_picker.module.js │ │ └── utils.module.js │ ├── multiple.png │ ├── parsers │ │ ├── background.pegjs │ │ ├── color.module.js │ │ ├── color.pegjs │ │ ├── css.module.js │ │ ├── css.pegjs │ │ ├── gradient.pegjs │ │ ├── json.module.js │ │ ├── json.pegjs │ │ ├── shadow.peg │ │ └── shadow.pegjs │ ├── sprockets │ │ └── commonjs.rb │ └── whitey.png └── index.html └── vendor └── assets └── javascripts ├── gfx.coffee ├── gfx └── effects.coffee ├── jquery.js ├── spine.coffee └── spine ├── ajax.coffee ├── list.coffee ├── local.coffee ├── manager.coffee ├── relation.coffee ├── route.coffee ├── tabs.coffee └── tmpl.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | Stylo.app 4 | -------------------------------------------------------------------------------- /Cakefile: -------------------------------------------------------------------------------- 1 | {print} = require 'util' 2 | {spawn} = require 'child_process' 3 | 4 | task 'build', 'Build lib/ from src/', -> 5 | coffee = spawn 'coffee', ['-c', '-o', 'lib', 'src'] 6 | coffee.stderr.on 'data', (data) -> 7 | process.stderr.write data.toString() 8 | coffee.stdout.on 'data', (data) -> 9 | print data.toString() 10 | coffee.on 'exit', (code) -> 11 | callback?() if code is 0 12 | 13 | task 'watch', 'Watch src/ for changes', -> 14 | coffee = spawn 'coffee', ['-w', '-c', '-o', 'lib', 'src'] 15 | coffee.stderr.on 'data', (data) -> 16 | process.stderr.write data.toString() 17 | coffee.stdout.on 'data', (data) -> 18 | print data.toString() -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'sinatra', :require => 'sinatra/base' 4 | gem 'rack' 5 | gem 'sinatra-contrib' 6 | gem 'json' 7 | gem 'thin' 8 | 9 | gem 'coffee-script' 10 | gem 'eco' 11 | gem 'uglifier' 12 | gem 'sprockets' 13 | gem 'sprockets-commonjs', :git => 'git://github.com/maccman/sprockets-commonjs.git' 14 | gem 'stylus' 15 | 16 | group :test, :development do 17 | gem 'growl' 18 | gem 'guard-sprockets2', :git => 'git://github.com/maccman/guard-sprockets2.git' 19 | gem 'rb-fsevent' 20 | gem 'ruby-debug19', :require => 'ruby-debug' 21 | end -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GIT 2 | remote: git://github.com/maccman/guard-sprockets2.git 3 | revision: e2883010e38007bb7e4161fe5f8e3e8db8069c2d 4 | specs: 5 | guard-sprockets2 (0.0.2) 6 | guard 7 | sprockets (~> 2.0) 8 | 9 | GIT 10 | remote: git://github.com/maccman/sprockets-commonjs.git 11 | revision: 36d8e9ff61653afbc3782d098c18075e7c3ce906 12 | specs: 13 | sprockets-commonjs (0.0.2) 14 | sprockets (~> 2.4.0) 15 | 16 | GEM 17 | remote: http://rubygems.org/ 18 | specs: 19 | archive-tar-minitar (0.5.2) 20 | backports (2.5.1) 21 | coffee-script (2.2.0) 22 | coffee-script-source 23 | execjs 24 | coffee-script-source (1.3.1) 25 | columnize (0.3.6) 26 | daemons (1.1.8) 27 | eco (1.0.0) 28 | coffee-script 29 | eco-source 30 | execjs 31 | eco-source (1.1.0.rc.1) 32 | eventmachine (0.12.10) 33 | execjs (1.3.0) 34 | multi_json (~> 1.0) 35 | ffi (1.0.11) 36 | growl (1.0.3) 37 | guard (1.0.1) 38 | ffi (>= 0.5.0) 39 | thor (~> 0.14.6) 40 | hike (1.2.1) 41 | json (1.6.6) 42 | linecache19 (0.5.12) 43 | ruby_core_source (>= 0.1.4) 44 | multi_json (1.2.0) 45 | rack (1.4.1) 46 | rack-protection (1.2.0) 47 | rack 48 | rack-test (0.6.1) 49 | rack (>= 1.0) 50 | rb-fsevent (0.9.1) 51 | ruby-debug-base19 (0.11.25) 52 | columnize (>= 0.3.1) 53 | linecache19 (>= 0.5.11) 54 | ruby_core_source (>= 0.1.4) 55 | ruby-debug19 (0.11.6) 56 | columnize (>= 0.3.1) 57 | linecache19 (>= 0.5.11) 58 | ruby-debug-base19 (>= 0.11.19) 59 | ruby_core_source (0.1.5) 60 | archive-tar-minitar (>= 0.5.2) 61 | sinatra (1.3.2) 62 | rack (~> 1.3, >= 1.3.6) 63 | rack-protection (~> 1.2) 64 | tilt (~> 1.3, >= 1.3.3) 65 | sinatra-contrib (1.3.1) 66 | backports (>= 2.0) 67 | eventmachine 68 | rack-protection 69 | rack-test 70 | sinatra (~> 1.3.0) 71 | tilt (~> 1.3) 72 | sprockets (2.4.0) 73 | hike (~> 1.2) 74 | multi_json (~> 1.0) 75 | rack (~> 1.0) 76 | tilt (~> 1.1, != 1.3.0) 77 | stylus (0.5.0) 78 | execjs 79 | stylus-source 80 | stylus-source (0.24.0) 81 | thin (1.3.1) 82 | daemons (>= 1.0.9) 83 | eventmachine (>= 0.12.6) 84 | rack (>= 1.0.0) 85 | thor (0.14.6) 86 | tilt (1.3.3) 87 | uglifier (1.2.4) 88 | execjs (>= 0.3.0) 89 | multi_json (>= 1.0.2) 90 | 91 | PLATFORMS 92 | ruby 93 | 94 | DEPENDENCIES 95 | coffee-script 96 | eco 97 | growl 98 | guard-sprockets2! 99 | json 100 | rack 101 | rb-fsevent 102 | ruby-debug19 103 | sinatra 104 | sinatra-contrib 105 | sprockets 106 | sprockets-commonjs! 107 | stylus 108 | thin 109 | uglifier 110 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | require './app' 2 | 3 | guard 'sprockets2', :sprockets => App.sprockets, :precompile => App.precompile do 4 | watch(%r{^assets/.+$}) 5 | watch('app.rb') 6 | end -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Alex MacCaw (info@eribium.org) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stylo 2 | 3 | Stylo is a web app designer written in [CoffeeScript](http://coffeescript.org) and [Spine](http://spinejs.com). It allows you to manipulate various HTML elements, add styles and edit text. 4 | 5 | You can find a [demo here](http://styloapp.com/) *(webkit only)*. 6 | 7 | [![Stylo](http://img.svbtle.com/maccman-24077671832628-raw.png)](http://styloapp.com/) 8 | 9 | ## Code examples 10 | 11 | The code should provide good examples of how to achieve the following: 12 | 13 | * [Element snapping](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/snapping.module.coffee), [resizing](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/resizing.module.coffee) and [drag/drop](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/dragging.module.coffee) 14 | * [Copy/paste](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/clipboard.module.coffee), [undo/redo](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/history.module.coffee) & [keyboard shortcuts](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/key_bindings.module.coffee) 15 | * [Context menus](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/context_menu.module.coffee) and [z-index ordering](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/controllers/stage/zindex.module.coffee) 16 | * [Color picker using canvas](https://github.com/maccman/stylo/blob/master/assets/javascripts/lib/color_picker.module.coffee) 17 | * [JSON object instance serialization](https://github.com/maccman/stylo/blob/master/assets/javascripts/app/models/serialize.module.coffee) 18 | 19 | ## More 20 | 21 | For more information, please see the [blog post](http://blog.alexmaccaw.com/stylo). -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | namespace :macgap do 2 | task :build do 3 | `macgap build --name Stylo ./public` 4 | end 5 | end 6 | 7 | namespace :pegjs do 8 | task :build do 9 | `pegjs assets/javascripts/app/parsers/css.pegjs assets/javascripts/app/parsers/css.module.js` 10 | end 11 | end -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | A Photoshop replacement. Inspired by Keynote. Democratizing design. 2 | 3 | ##TODO 4 | 5 | Phase 1: 6 | 7 | * ✓ Move 8 | * ✓ Resize 9 | * ✓ Select 10 | * ✓ Select multiple items 11 | * ✓ Toolbar menu 12 | * Inspector panel (Element/Text/Ruler) 13 | * ½ Background (direction) 14 | * ✓ Border 15 | * ✓ Shadow 16 | * ✓ Text-shadow 17 | * Rotation 18 | * ✓ Opacity 19 | * ✓ content editable 20 | * ✓ context menu 21 | * Font alignment 22 | * ✓ Font family/size/color 23 | * ✓ Text-shadow 24 | * ✓ Text-spacing (line height) 25 | 26 | Phase 2: 27 | 28 | * ✓ Stage snapping 29 | * ✓ Color picker 30 | * Stage inspector 31 | * ✓ Z-index 32 | * ✓ Copy paste 33 | * ✓ Undo/redo 34 | * ½ Saving/opening 35 | 36 | Phase 3: 37 | 38 | * Resizing snapping 39 | * ✓ Edge snapping 40 | * ✓ Element snapping 41 | * Pen tool 42 | 43 | Nice to have: 44 | 45 | * HTML import 46 | * ✓ Keyboard shortcuts 47 | * Versioning 48 | * ✓ Exporting CSS 49 | * Share (dropbox/email?) 50 | * WebFonts 51 | * Layers? 52 | * ✓ Context menu 53 | * Similar distance snapping 54 | * Bootstrap integration 55 | 56 | ##Elements 57 | 58 | * Triangle 59 | * ✓ Rectangle 60 | * ✓ Ellipsis 61 | * Checkbox input 62 | * ✓ Button input 63 | * ✓ Text Input 64 | * ✓ Text 65 | * ✓ Image 66 | * Line 67 | 68 | ##Fixes: 69 | 70 | * Background image 71 | * Background gradient rotation 72 | * Drop image needs background image set 73 | * Color inspector movable window 74 | * Dragging right and up doesn't select elements - offset by the header 75 | * We want the resize to be around the selection? 76 | * ✓ We want the inspector not to completely re-render on selection 77 | * ✓ You can have negative widths 78 | 79 | ------------------------------------------------------------------------------------------------------------------------------------- 80 | 81 | #Save to png 82 | 83 | https://github.com/paulhammond/webkit2png/blob/master/webkit2png 84 | https://developer.apple.com/library/mac/#samplecode/ScreenSnapshot/Listings/ScreenSnapshot_ImageView_m.html#//apple_ref/doc/uid/DTS40011158-ScreenSnapshot_ImageView_m-DontLinkElementID_7 85 | http://www.cocoadev.com/index.pl?HowToAcquireScreenshots 86 | http://www.sticksoftware.com/developer/Screensnap.m.txt 87 | https://github.com/appcelerator/titanium_desktop/blob/master/modules/ti.Platform/PlatformMac.mm 88 | 89 | http://www.cssdesk.com/ 90 | 91 | http://10k.aneventapart.com/2/Uploads/579/ 92 | 93 | http://10k.aneventapart.com/2/Uploads/504/ 94 | 95 | AN EASY WAY TO CHOOSE COLORS - I.E. BURN ETC 96 | Video of desiging various interfaces = include Stylo. 97 | Tool to generate noise. 98 | 99 | https://developers.google.com/webfonts 100 | https://typekit.com/ 101 | 102 | Issue with border radius is that properties are being applied in the wrong order. 103 | Should 'selected' not be a property? 104 | Only save selection in history. 105 | We should have a benchmarking testing library. 106 | 107 | -------------- 108 | 109 | # Style 110 | # Decoration 111 | # Capitalize 112 | # Alignment -------------------------------------------------------------------------------- /app.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'bundler' 3 | require 'pathname' 4 | require 'sinatra/json' 5 | require 'sinatra/reloader' 6 | 7 | Bundler.require 8 | 9 | require 'sprockets/commonjs' 10 | require 'stylus/tilt' 11 | require 'stylus/import_processor' 12 | 13 | module AssetHelpers 14 | def asset_path(source) 15 | '/assets/' + settings.sprockets.find_asset(source).digest_path 16 | end 17 | end 18 | 19 | class App < Sinatra::Base 20 | set :root, Pathname(File.expand_path('../', __FILE__)) 21 | set :raise_errors, true 22 | set :show_exceptions, true 23 | set :sprockets, Sprockets::Environment.new(root) 24 | set :precompile, [ /\w+\.(?!js|css).+/, /application.(css|js)$/ ] 25 | set :protection, false 26 | 27 | configure do 28 | sprockets.append_path(root.join('assets', 'javascripts')) 29 | sprockets.append_path(root.join('assets', 'stylesheets')) 30 | sprockets.append_path(root.join('assets', 'images')) 31 | 32 | sprockets.append_path(root.join('vendor', 'assets', 'javascripts')) 33 | sprockets.append_path(root.join('vendor', 'assets', 'stylesheets')) 34 | 35 | sprockets.register_engine '.styl', Tilt::StylusTemplate 36 | sprockets.register_preprocessor 'text/css', Stylus::ImportProcessor 37 | Stylus.paths.concat sprockets.paths 38 | 39 | sprockets.context_class.instance_eval do 40 | include AssetHelpers 41 | end 42 | end 43 | 44 | helpers Sinatra::JSON 45 | 46 | helpers do 47 | include AssetHelpers 48 | 49 | def url(path) 50 | base = "#{request.scheme}://#{request.env['HTTP_HOST']}" 51 | base + path 52 | end 53 | end 54 | 55 | # use Rack::Auth::Basic, "Protected Area" do |username, password| 56 | # (username == 'dragon' && password == 'tamer') 57 | # end 58 | 59 | get '/' do 60 | send_file File.join(settings.public_folder, 'index.html') 61 | end 62 | end -------------------------------------------------------------------------------- /assets/images/bg-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/assets/images/bg-grid.png -------------------------------------------------------------------------------- /assets/images/blacky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/assets/images/blacky.png -------------------------------------------------------------------------------- /assets/images/crosshairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/assets/images/crosshairs.png -------------------------------------------------------------------------------- /assets/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/assets/images/grid.png -------------------------------------------------------------------------------- /assets/images/multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/assets/images/multiple.png -------------------------------------------------------------------------------- /assets/images/whitey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/assets/images/whitey.png -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/element/resizing.module.coffee: -------------------------------------------------------------------------------- 1 | class Thumb extends Spine.Controller 2 | className: 'thumb' 3 | 4 | events: 5 | 'mousedown': 'listen' 6 | 7 | constructor: (@type) -> 8 | super() 9 | @el.addClass(@type) 10 | 11 | listen: (e) => 12 | e.preventDefault() 13 | e.stopPropagation() 14 | 15 | @dragPosition = {left: e.pageX, top: e.pageY} 16 | $(document).mousemove(@drag) 17 | $(document).mouseup(@drop) 18 | @el.trigger('start.resize', [@type]) 19 | 20 | drag: (e) => 21 | difference = 22 | left: e.pageX - @dragPosition.left 23 | top: e.pageY - @dragPosition.top 24 | 25 | @dragPosition = {left: e.pageX, top: e.pageY} 26 | @el.trigger('drag.resize', [@type, difference, e.shiftKey]) 27 | 28 | drop: (e) => 29 | @el.trigger('end.resize') 30 | 31 | $(document).unbind('mousemove', @drag) 32 | $(document).unbind('mouseup', @drop) 33 | 34 | class Resizing extends Spine.Controller 35 | className: 'resizing' 36 | 37 | events: 38 | 'drag.resize': 'resize' 39 | 40 | constructor: (@element) -> 41 | super(el: @element.el) 42 | 43 | render: -> 44 | @thumbs = $('
') 45 | @thumbs.append(new Thumb('tl').el) 46 | @thumbs.append(new Thumb('tt').el) 47 | @thumbs.append(new Thumb('tr').el) 48 | @thumbs.append(new Thumb('rr').el) 49 | @thumbs.append(new Thumb('br').el) 50 | @thumbs.append(new Thumb('bb').el) 51 | @thumbs.append(new Thumb('bl').el) 52 | @thumbs.append(new Thumb('ll').el) 53 | @thumbs = @thumbs.children() 54 | @append(@thumbs) 55 | 56 | remove: -> 57 | @thumbs?.remove() 58 | 59 | toggle: (bool) -> 60 | if bool then @render() else @remove() 61 | 62 | resize: (e, type, position, lockAR) -> 63 | area = @element.area() 64 | 65 | switch type 66 | when 'tl' 67 | area.width -= position.left 68 | area.height -= position.top 69 | area.top += position.top 70 | area.left += position.left 71 | 72 | when 'tt' 73 | area.height -= position.top 74 | area.top += position.top 75 | 76 | when 'tr' 77 | area.width += position.left 78 | area.height -= position.top 79 | area.top += position.top 80 | 81 | when 'rr' 82 | area.width += position.left 83 | 84 | when 'br' 85 | area.width += position.left 86 | area.height += position.top 87 | 88 | when 'bb' 89 | area.height += position.top 90 | 91 | when 'bl' 92 | area.width -= position.left 93 | area.height += position.top 94 | area.left += position.left 95 | 96 | when 'll' 97 | area.width -= position.left 98 | area.left += position.left 99 | 100 | if lockAR 101 | # TODO - FIXME, this doesn't lock AR properly 102 | area.width = Math.max(area.width, area.height) 103 | area.height = area.width 104 | 105 | # Make sure we can't have negative widths/heights 106 | area.width = Math.max(0, area.width) 107 | area.height = Math.max(0, area.height) 108 | 109 | @element.resize(area) 110 | 111 | module.exports = Resizing -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/button.module.coffee: -------------------------------------------------------------------------------- 1 | Element = require('../element') 2 | Color = require('app/models/properties/color') 3 | Background = require('app/models/properties/background') 4 | 5 | class Button extends Element 6 | className: 'button' 7 | id: module.id 8 | 9 | events: 10 | 'resize.element': 'syncLineHeight' 11 | 12 | defaults: -> 13 | result = 14 | width: 100 15 | height: 40 16 | textAlign: 'center' 17 | lineHeight: 40 18 | borderRadius: 5 19 | borderWidth: 1 20 | borderStyle: 'solid' 21 | borderColor: new Color(166, 166, 166) 22 | backgroundImage: [new Background.LinearGradient( 23 | new Background.Position(270), 24 | [ 25 | new Background.ColorStop(new Color.White, 0), 26 | new Background.ColorStop(new Color.White, 30), 27 | new Background.ColorStop(new Color(242,242,242), 100) 28 | ] 29 | )] 30 | 31 | syncLineHeight: -> 32 | @set(lineHeight: @get('height')) 33 | 34 | module.exports = Button -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/canvas.module.coffee: -------------------------------------------------------------------------------- 1 | Element = require('../element') 2 | 3 | class Canvas extends Element 4 | tag: 'canvas' 5 | 6 | points: [] 7 | 8 | constructor: -> 9 | super 10 | @ctx = @el[0].getContext('2d') 11 | 12 | paint: -> 13 | first = @points[0] 14 | points = @points[1...@points.length] 15 | return unless first 16 | 17 | @ctx.beginPath() 18 | @ctx.moveTo(first...) 19 | 20 | for point in @points 21 | @ctx.lineTo(point...) 22 | 23 | @ctx.fill() 24 | 25 | width: (val) -> 26 | 27 | height: (val) -> 28 | 29 | backgroundImage: (val) -> 30 | 31 | backgroundColor: (val) -> 32 | 33 | borderBottom: (val) -> 34 | 35 | boxShadow: (val) -> 36 | 37 | borderRadius: (val) -> 38 | 39 | module.exports = Canvas -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/ellipsis.module.coffee: -------------------------------------------------------------------------------- 1 | Element = require('../element') 2 | 3 | class Ellipsis extends Element 4 | className: 'ellipsis' 5 | id: module.id 6 | 7 | constructor: -> 8 | super 9 | @properties['borderRadius'] = '50%' 10 | @paint() 11 | 12 | # Disable setting borderRadius in 13 | # the element inspector 14 | borderRadius: -> false 15 | 16 | module.exports = Ellipsis -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/image.module.coffee: -------------------------------------------------------------------------------- 1 | Element = require('../element') 2 | Color = require('app/models/properties/color') 3 | 4 | class Image extends Element 5 | className: 'image' 6 | id: module.id 7 | 8 | constructor: (attrs = {}) -> 9 | super 10 | @setSrc(attrs.src) 11 | 12 | setSrc: (@src) -> 13 | @set( 14 | backgroundColor: new Color.Transparent 15 | backgroundImage: "url(#{@src})" 16 | backgroundSize: '100% 100%' 17 | backgroundRepeat: 'no-repeat' 18 | backgroundPosition: 'center center' 19 | ) if @src 20 | 21 | toValue: -> 22 | result = super 23 | result.src = @src 24 | result 25 | 26 | module.exports = Image -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/input.module.coffee: -------------------------------------------------------------------------------- 1 | Element = require('../element') 2 | Color = require('app/models/properties/color') 3 | Shadow = require('app/models/properties/shadow') 4 | 5 | class Text extends Element 6 | className: 'textInput' 7 | id: module.id + '.Text' 8 | 9 | defaults: -> 10 | result = 11 | width: 125 12 | height: 20 13 | padding: 3 14 | borderWidth: 1 15 | borderStyle: 'solid' 16 | borderColor: new Color(155,155,155) 17 | boxShadow: [new Shadow( 18 | inset: true, x: 0, y: 1, blur: 2, 19 | color: new Color(0, 0, 0, 0.12) 20 | )] 21 | backgroundColor: new Color.White 22 | 23 | class CheckBox extends Element 24 | 25 | module.exports = 26 | Text: Text 27 | CheckBox: CheckBox -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/line.module.coffee: -------------------------------------------------------------------------------- 1 | Element = require('../element') 2 | 3 | class Line extends Element 4 | className: 'line' 5 | 6 | module.exports = Line -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/rectangle.module.coffee: -------------------------------------------------------------------------------- 1 | Element = require('../element') 2 | 3 | class Rectangle extends Element 4 | className: 'rectangle' 5 | id: module.id 6 | 7 | module.exports = Rectangle -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/text.module.coffee: -------------------------------------------------------------------------------- 1 | Color = require('app/models/properties/color') 2 | Rectangle = require('./rectangle') 3 | 4 | class Text extends Rectangle 5 | className: 'text' 6 | id: module.id 7 | 8 | events: 9 | 'dblclick .thumb.br': 'fitToText' 10 | 11 | defaults: -> 12 | result = 13 | height: 30 14 | fontSize: 18 15 | backgroundColor: new Color.Transparent 16 | 17 | $.extend({}, super, result) 18 | 19 | startEditing: -> 20 | return if @editing 21 | super 22 | 23 | @autoSize() 24 | 25 | stopEditing: -> 26 | return unless @editing 27 | super 28 | 29 | if @text() 30 | @fixSize() 31 | else 32 | # Remove the element if empty 33 | @remove() 34 | 35 | autoSize: -> 36 | @el.css(width: 'auto', height: 'auto') 37 | 38 | fixSize: -> 39 | @set( 40 | width: @el.outerWidth(), 41 | height: @el.outerHeight() 42 | ) 43 | 44 | module.exports = Text -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/elements/triangle.module.coffee: -------------------------------------------------------------------------------- 1 | Canvas = require('./canvas') 2 | 3 | class Triangle extends Canvas 4 | className: 'triangle' 5 | 6 | points: [1, 2, 3] 7 | 8 | module.exports = Rectangle -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/header.module.coffee: -------------------------------------------------------------------------------- 1 | Rectangle = require('./elements/rectangle') 2 | Ellipsis = require('./elements/ellipsis') 3 | Text = require('./elements/text') 4 | Input = require('./elements/input') 5 | Button = require('./elements/button') 6 | 7 | class Header extends Spine.Controller 8 | tag: 'header' 9 | className: 'header' 10 | 11 | events: 12 | 'click .rectangle': 'addRectangle' 13 | 'click .ellipsis': 'addEllipsis' 14 | 'click .text': 'addText' 15 | 'click .textInput': 'addTextInput' 16 | 'click .button': 'addButton' 17 | 18 | render: -> 19 | @html JST['app/views/header'](this) 20 | this 21 | 22 | addRectangle: -> 23 | @addElement(new Rectangle) 24 | 25 | addEllipsis: -> 26 | @addElement(new Ellipsis) 27 | 28 | addText: -> 29 | @addElement(element = new Text) 30 | element.startEditing() 31 | 32 | addTextInput: -> 33 | @addElement(new Input.Text) 34 | 35 | addButton: -> 36 | @addElement(new Button) 37 | 38 | addElement: (element) -> 39 | @stage.history.record() 40 | 41 | position = @stage.center() 42 | position.left -= element.get('width') or 50 43 | position.top -= element.get('height') or 50 44 | element.set(position) 45 | 46 | @stage.add(element) 47 | @stage.selection.clear() 48 | @stage.selection.add(element) 49 | 50 | module.exports = Header -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector.module.coffee: -------------------------------------------------------------------------------- 1 | Background = require('./inspector/background') 2 | Border = require('./inspector/border') 3 | BorderRadius = require('./inspector/border_radius') 4 | Opacity = require('./inspector/opacity') 5 | BoxShadow = require('./inspector/box_shadow') 6 | TextShadow = require('./inspector/text_shadow') 7 | Dimensions = require('./inspector/dimensions') 8 | TextPosition = require('./inspector/text_position') 9 | Font = require('./inspector/font') 10 | Utils = require('lib/utils') 11 | 12 | class TextInspector extends Spine.Controller 13 | className: 'textInspector' 14 | 15 | constructor: -> 16 | super 17 | @append(@font = new Font(stage: @stage)) 18 | @append(@textPosition = new TextPosition(stage: @stage)) 19 | @append(@textShadow = new TextShadow(stage: @stage)) 20 | 21 | render: -> 22 | @font.render() 23 | @textPosition.render() 24 | @textShadow.render() 25 | this 26 | 27 | release: -> 28 | @font?.release() 29 | @textPosition?.release() 30 | @textShadow?.release() 31 | super 32 | 33 | class DisplayInspector extends Spine.Controller 34 | className: 'displayInspector' 35 | 36 | constructor: -> 37 | super 38 | 39 | @append(@dimensions = new Dimensions(stage: @stage)) 40 | @append(@background = new Background(stage: @stage)) 41 | @append(@border = new Border(stage: @stage)) 42 | @append(@borderRadius = new BorderRadius(stage: @stage)) 43 | @append(@boxShadow = new BoxShadow(stage: @stage)) 44 | @append(@opacity = new Opacity(stage: @stage)) 45 | 46 | render: -> 47 | @dimensions.render() 48 | @background.render() 49 | @border.render() 50 | @borderRadius.render() 51 | @boxShadow.render() 52 | @opacity.render() 53 | this 54 | 55 | release: -> 56 | @dimensions?.release() 57 | @background?.release() 58 | @border?.release() 59 | @borderRadius?.release() 60 | @boxShadow?.release() 61 | @opacity?.release() 62 | 63 | class Inspector extends Spine.Controller 64 | className: 'inspector' 65 | 66 | events: 67 | 'click header [data-type]': 'changeInspector' 68 | 69 | elements: 70 | 'header div': '$headers' 71 | 72 | constructor: -> 73 | super 74 | 75 | @append(JST['app/views/inspector']()) 76 | @append(@textInspector = new TextInspector(stage: @stage)) 77 | @append(@displayInspector = new DisplayInspector(stage: @stage)) 78 | 79 | # Make sure only one inspector is active 80 | @manager = new Spine.Manager 81 | @manager.add(@textInspector, @displayInspector) 82 | @manager.bind 'change', @changeHeader 83 | 84 | # Display the display inspector by default 85 | @displayInspector.active() 86 | 87 | # We can increase performance dramatically by using 88 | # requestAnimationFrame and rendering async 89 | @stage.selection.bind 'change', @paint 90 | 91 | @render() 92 | 93 | paint: => 94 | return if @rendering 95 | @rendering = true 96 | Utils.requestAnimationFrame(@render) 97 | 98 | render: => 99 | # Do update in one paint by hiding 100 | # before rendering inspectors 101 | @el.hide() 102 | 103 | # Render the currently active inspector 104 | @manager.current?.render() 105 | 106 | @el.show() 107 | @rendering = false 108 | this 109 | 110 | changeInspector: (e) -> 111 | name = $(e.target).data('type') 112 | 113 | if name is 'DisplayInspector' 114 | @displayInspector.render() 115 | @displayInspector.active() 116 | 117 | else if name is 'TextInspector' 118 | @textInspector.render() 119 | @textInspector.active() 120 | 121 | changeHeader: => 122 | name = @manager.current.constructor.name 123 | @$headers.removeClass('active') 124 | @$headers.filter("[data-type=#{name}]").addClass('active') 125 | 126 | release: -> 127 | @textInspector.release() 128 | @displayInspector.release() 129 | @stage.selection.unbind 'change', @paint 130 | super 131 | 132 | module.exports = Inspector -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/border.module.coffee: -------------------------------------------------------------------------------- 1 | Border = require('app/models/properties/border') 2 | ColorPicker = require('lib/color_picker') 3 | 4 | class BorderController extends Spine.Controller 5 | className: 'border' 6 | 7 | events: 8 | 'click [data-border]': 'borderClick' 9 | 'change': 'inputChange' 10 | 11 | elements: 12 | '.borders div': '$borders' 13 | 'select[name=style]': '$style' 14 | 'input[name=width]': '$width' 15 | 'input, select': '$inputs' 16 | 17 | current: 'border' 18 | 19 | constructor: -> 20 | super 21 | 22 | @html JST['app/views/inspector/border'](this) 23 | 24 | # Color input 25 | @$color = new ColorPicker.Preview 26 | @$color.bind 'change', => @inputChange() 27 | 28 | @$('input[type=color]').replaceWith(@$color.el) 29 | 30 | render: => 31 | @disabled = not @stage.selection.isAny() 32 | @disabled = true if @stage.selection.get('border') is false 33 | 34 | @change(@current) 35 | 36 | @el.toggleClass('disabled', @disabled) 37 | @$inputs.attr('disabled', @disabled) 38 | this 39 | 40 | # Private 41 | 42 | change: (@current) -> 43 | return if @disabled 44 | 45 | @$borders.removeClass('active') 46 | @$borders.filter("[data-border=#{@current}]").addClass('active') 47 | 48 | @currentBorder = @stage.selection.get(@current) 49 | 50 | unless @currentBorder 51 | # Base this border on the default one. 52 | @currentBorder = @stage.selection.get('border')?.clone() 53 | @currentBorder or= new Border 54 | 55 | @$width.val(@currentBorder.width) 56 | @$style.val(@currentBorder.style) 57 | @$color.val(@currentBorder.color) 58 | 59 | # Events 60 | 61 | borderClick: (e) -> 62 | @change($(e.currentTarget).data('border')) 63 | 64 | inputChange: -> 65 | @stage.history.record('border') 66 | 67 | @currentBorder.width = parseInt(@$width.val(), 10) 68 | @currentBorder.style = @$style.val() 69 | @currentBorder.color = @$color.val() 70 | @set() 71 | 72 | set: -> 73 | @stage.history.record('border') 74 | 75 | # Border overrides everything else 76 | if @current is 'border' 77 | @stage.selection.set( 78 | borderTop: null 79 | borderRight: null 80 | borderBottom: null 81 | borderLeft: null 82 | ) 83 | 84 | @stage.selection.set(@current, @currentBorder) 85 | 86 | release: -> 87 | @$color?.release() 88 | super 89 | 90 | module.exports = BorderController -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/border_radius.module.coffee: -------------------------------------------------------------------------------- 1 | class BorderRadius extends Spine.Controller 2 | className: 'borderRadius' 3 | 4 | events: 5 | 'click [data-border-radius]': 'borderClick' 6 | 'change input': 'inputChange' 7 | 8 | elements: 9 | '.borders div': '$borders' 10 | 'input': '$inputs' 11 | 12 | current: 'borderRadius' 13 | 14 | constructor: -> 15 | super 16 | @html JST['app/views/inspector/border_radius'](this) 17 | 18 | render: => 19 | # Disable unless elements are selected or if an 20 | # element, such as an ellipsis, is selected. 21 | @disabled = not @stage.selection.isAny() 22 | @disabled = true if @stage.selection.get('borderRadius') is false 23 | 24 | @change(@current) 25 | 26 | @el.toggleClass('disabled', @disabled) 27 | @$inputs.attr('disabled', @disabled) 28 | 29 | this 30 | 31 | # Private 32 | 33 | change: (@current) -> 34 | return if @disabled 35 | 36 | @$borders.removeClass('active') 37 | @$borders.filter("[data-border-radius=#{@current}]").addClass('active') 38 | 39 | @radius = @stage.selection.get(@current) 40 | @radius or= @stage.selection.get('borderRadius') 41 | @radius or= 0 42 | 43 | @$inputs.val(@radius) 44 | 45 | # Events 46 | 47 | borderClick: (e) -> 48 | @change($(e.currentTarget).data('border-radius')) 49 | 50 | inputChange: (e) -> 51 | val = parseInt($(e.currentTarget).val(), 10) 52 | @$inputs.val(val) 53 | @set(val) 54 | 55 | set: (val) -> 56 | @stage.history.record('borderRadius') 57 | 58 | # borderRadius overrides everything else 59 | if @current is 'borderRadius' 60 | @stage.selection.set( 61 | borderTopLeftRadius: null 62 | borderTopRightRadius: null 63 | borderBottomRightRadius: null 64 | borderBottomLeftRadius: null 65 | ) 66 | 67 | @stage.selection.set(@current, val) 68 | 69 | module.exports = BorderRadius -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/dimensions.module.coffee: -------------------------------------------------------------------------------- 1 | class Dimensions extends Spine.Controller 2 | className: 'dimensions' 3 | 4 | events: 5 | 'change input': 'change' 6 | 7 | elements: 8 | 'input': '$inputs' 9 | 'input[name=width]': '$width' 10 | 'input[name=height]': '$height' 11 | 'input[name=x]': '$x' 12 | 'input[name=y]': '$y' 13 | 14 | constructor: -> 15 | super 16 | $(document).bind 'resize.element move.element', @update 17 | @html JST['app/views/inspector/dimensions'](this) 18 | 19 | render: => 20 | @disabled = not @stage.selection.isSingle() 21 | 22 | @update() 23 | 24 | @el.toggleClass('disabled', @disabled) 25 | @$inputs.attr('disabled', @disabled) 26 | this 27 | 28 | update: => 29 | @disabled = not @stage.selection.isSingle() 30 | return if @disabled 31 | 32 | @$width.val(@stage.selection.get('width')) 33 | @$height.val(@stage.selection.get('height')) 34 | @$x.val(@stage.selection.get('left')) 35 | @$y.val(@stage.selection.get('top')) 36 | 37 | change: (e) -> 38 | @stage.history.record('dimensions') 39 | @stage.selection.set('width', parseInt(@$width.val(), 10)) 40 | @stage.selection.set('height', parseInt(@$height.val(), 10)) 41 | @stage.selection.set('left', parseInt(@$x.val(), 10)) 42 | @stage.selection.set('top', parseInt(@$y.val(), 10)) 43 | 44 | release: -> 45 | $(document).unbind 'resize.element', @update 46 | $(document).unbind 'move.element', @update 47 | super 48 | 49 | module.exports = Dimensions -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/font.module.coffee: -------------------------------------------------------------------------------- 1 | Color = require('app/models/properties/color') 2 | ColorPicker = require('lib/color_picker') 3 | 4 | class Font extends Spine.Controller 5 | className: 'font' 6 | 7 | elements: 8 | 'input': '$inputs' 9 | 'input[name=size]': '$size' 10 | 'select[name=family]': '$family' 11 | 12 | events: 13 | 'change input': 'change' 14 | 'change select': 'change' 15 | 16 | constructor: -> 17 | super 18 | @html JST['app/views/inspector/font'](this) 19 | 20 | @$color = new ColorPicker.Preview 21 | @$color.bind 'change', => @change() 22 | 23 | @$('input[type=color]').replaceWith(@$color.el) 24 | 25 | render: => 26 | @disabled = not @stage.selection.isAny() 27 | @$color.val(@stage.selection.get('color') or new Color.Black) 28 | @$size.val(@stage.selection.get('fontSize') ? 12) 29 | @$family.val(@stage.selection.get('fontFamily')) 30 | 31 | change: (e) -> 32 | @stage.history.record('font') 33 | 34 | @stage.selection.set('color', @$color.val()) 35 | @stage.selection.set('fontSize', parseInt(@$size.val(), 10)) 36 | @stage.selection.set('fontFamily', @$family.val()) 37 | 38 | module.exports = Font -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/opacity.module.coffee: -------------------------------------------------------------------------------- 1 | class Opacity extends Spine.Controller 2 | className: 'opacity' 3 | 4 | events: 5 | 'change input': 'change' 6 | 'focus input': 'inputFocus' 7 | 8 | elements: 9 | 'input': '$inputs' 10 | 11 | render: => 12 | @disabled = not @stage.selection.isAny() 13 | @opacity = @stage.selection.get('opacity') ? 1 14 | 15 | @html JST['app/views/inspector/opacity'](this) 16 | 17 | change: (e) -> 18 | @stage.history.record('opacity') 19 | 20 | val = parseFloat($(e.currentTarget).val()) 21 | val = Math.round(val * 100) / 100 22 | 23 | @stage.history.record('opacity') 24 | @stage.selection.set('opacity', val) 25 | @$inputs.val(val) 26 | 27 | module.exports = Opacity -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/popup_menu.module.coffee: -------------------------------------------------------------------------------- 1 | class PopupMenu extends Spine.Controller 2 | @open: (position) -> 3 | (new this).open(position) 4 | 5 | popupMenuEvents: 6 | 'mousedown': 'cancelEvent' 7 | 8 | constructor: -> 9 | super 10 | @delegateEvents(@popupMenuEvents) 11 | @el.addClass('popupMenu') 12 | @el.css(position: 'absolute') 13 | 14 | open: (position = {}) -> 15 | @el.css(position) 16 | $('body').append(@el) 17 | $('body').bind('mousedown', @close) 18 | this 19 | 20 | close: => 21 | @release() 22 | this 23 | 24 | release: -> 25 | $('body').unbind('mousedown', @close) 26 | super 27 | 28 | cancelEvent: (e) -> 29 | e.stopPropagation() 30 | 31 | module.exports = PopupMenu -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/stage.module.coffee: -------------------------------------------------------------------------------- 1 | class StageInspector extends Spine.Controller 2 | className: 'stageInspector' 3 | # Background 4 | 5 | module.exports = StageInspector -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/text_position.module.coffee: -------------------------------------------------------------------------------- 1 | class TextPosition extends Spine.Controller 2 | className: 'textPosition' 3 | 4 | types: [ 5 | 'textIndent', 6 | 'lineHeight', 7 | 'letterSpacing', 8 | 'wordSpacing' 9 | ] 10 | 11 | elements: 12 | 'input[name=textIndent]': '$textIndent' 13 | 'input[name=lineHeight]': '$lineHeight' 14 | 'input[name=letterSpacing]': '$letterSpacing' 15 | 'input[name=wordSpacing]': '$wordSpacing' 16 | 17 | events: 18 | 'change input': 'change' 19 | 20 | constructor: -> 21 | super 22 | @html JST['app/views/inspector/text_position'](this) 23 | 24 | render: -> 25 | @disabled = not @stage.selection.isAny() 26 | 27 | @current = {} 28 | 29 | for type in @types 30 | value = @stage.selection.get(type) 31 | @['$' + type].val(value) 32 | 33 | change: -> 34 | for type in @types 35 | value = parseInt(@['$' + type].val(), 10) 36 | @stage.selection.set(type, value) 37 | 38 | 39 | module.exports = TextPosition -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/text_shadow.module.coffee: -------------------------------------------------------------------------------- 1 | Shadow = require('app/models/properties/shadow') 2 | ColorPicker = require('lib/color_picker') 3 | PositionPicker = require('lib/position_picker') 4 | 5 | class TextShadow extends Spine.Controller 6 | className: 'textShadow' 7 | 8 | events: 9 | 'change input': 'change' 10 | 11 | elements: 12 | 'input[name=x]': '$x' 13 | 'input[name=y]': '$y' 14 | 'input[name=blur]': '$blur' 15 | 16 | constructor: -> 17 | super 18 | 19 | @$position = new PositionPicker 20 | 21 | @$position.bind 'change', (position) => 22 | @shadow.x = position.left 23 | @shadow.y = position.top 24 | @set() 25 | @update() 26 | 27 | @$color = new ColorPicker.Preview 28 | @$color.bind 'change', => @change() 29 | 30 | @html JST['app/views/inspector/text_shadow'](@) 31 | @$('input[type=color]').replaceWith(@$color.el) 32 | @$('input[type=position]').replaceWith(@$position.el) 33 | 34 | render: -> 35 | @disabled = not @stage.selection.isAny() 36 | 37 | @shadow = @stage.selection.get('textShadow') 38 | @shadow or= new Shadow 39 | 40 | @update() 41 | 42 | this 43 | 44 | update: -> 45 | @$('input').attr('disabled', @disabled) 46 | 47 | @$x.val @shadow.x 48 | @$y.val @shadow.y 49 | @$blur.val @shadow.blur 50 | @$color.val @shadow.color 51 | 52 | change: -> 53 | @shadow.x = parseFloat(@$x.val()) 54 | @shadow.y = parseFloat(@$y.val()) 55 | @shadow.blur = parseFloat(@$blur.val()) 56 | @shadow.color = @$color.val() 57 | 58 | @$position.change( 59 | left: @shadow.x, 60 | top: @shadow.y 61 | ) 62 | 63 | @set() 64 | @update() 65 | 66 | set: -> 67 | # Text shadows need a blur 68 | # to be formatted correctly 69 | @shadow.blur ?= 0 70 | 71 | @stage.history.record('textShadow') 72 | @stage.selection.set('textShadow', @shadow) 73 | 74 | release: -> 75 | @$position?.release() 76 | @$color?.release() 77 | super 78 | 79 | module.exports = TextShadow -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/inspector/text_style.module.coffee: -------------------------------------------------------------------------------- 1 | class TextStyle extends Spine.Controller 2 | className: 'textStyle' 3 | 4 | # Style 5 | # Decoration 6 | # Capitalize 7 | # Alignment 8 | 9 | module.exports = TextStyle -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/clipboard.module.coffee: -------------------------------------------------------------------------------- 1 | Serialize = require('app/models/serialize') 2 | 3 | class Clipboard 4 | constructor: (@stage) -> 5 | $(window).bind 'beforecopy', @cancel 6 | $(window).bind 'copy', @copy 7 | 8 | # Remove pasting for the moment, until 9 | # it's better supported in browsers 10 | # 11 | # $(window).bind 'beforepaste', @cancel 12 | # $(window).bind 'paste', @paste 13 | 14 | cancel: (e) => 15 | return if 'value' of e.target 16 | return if $(e.target).attr('contenteditable') 17 | 18 | # We need to cancel the default to get 19 | # the 'copy' event to trigger 20 | e.preventDefault() 21 | 22 | copy: (e) => 23 | return unless @stage.selection.isAny() 24 | 25 | e.preventDefault() 26 | e = e.originalEvent 27 | 28 | json = JSON.stringify(@stage.selection.elements) 29 | e.clipboardData.setData('json/x-stylo', json) 30 | 31 | styles = (el.outerCSS() for el in @stage.selection.elements) 32 | e.clipboardData.setData('text/plain', styles.join("\n\n")) 33 | 34 | paste: (e) => 35 | return if 'value' of e.target 36 | 37 | e.preventDefault() 38 | e = e.originalEvent 39 | 40 | # Some browsers restrict the clipboard data types, 41 | # so we need to revert back to text/html 42 | json = e.clipboardData.getData('json/x-stylo') 43 | return unless json 44 | 45 | elements = Serialize.fromJSON(json) 46 | 47 | @stage.history.record() 48 | @stage.add(el) for el in elements 49 | @stage.selection.refresh(elements) 50 | @stage.selection.moveBy(left: 10, top: 10) 51 | 52 | data: null 53 | 54 | copyInternal: -> 55 | @data = (el.clone() for el in @stage.selection.elements) 56 | 57 | pasteInternal: (e) -> 58 | return unless @data 59 | 60 | e?.preventDefault() 61 | 62 | @stage.history.record() 63 | @stage.add(el) for el in @data 64 | @stage.selection.refresh(@data) 65 | @stage.selection.moveBy(left: 10, top: 10) 66 | 67 | # Re-clone the elements 68 | @copyInternal() 69 | 70 | release: -> 71 | $(window).unbind 'beforecopy', @cancel 72 | $(window).unbind 'copy', @copy 73 | $(window).unbind 'beforepaste', @cancel 74 | $(window).unbind 'paste', @paste 75 | 76 | module.exports = Clipboard -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/context_menu.module.coffee: -------------------------------------------------------------------------------- 1 | class Menu extends Spine.Controller 2 | className: 'contextMenu' 3 | 4 | events: 5 | 'mousedown': 'cancelEvent' 6 | 'click [data-type]': 'click' 7 | 8 | constructor: (@stage, position) -> 9 | super() 10 | 11 | @el.css(position) 12 | @html(JST['app/views/context_menu'](this)) 13 | 14 | @selectDisabled = not @stage.selection.isAny() 15 | @$('[data-require=select]').toggleClass('disabled', @selectDisabled) 16 | 17 | click: (e) -> 18 | e.preventDefault() 19 | @release() 20 | 21 | item = $(e.currentTarget) 22 | type = item.data('type') 23 | 24 | unless item.hasClass('disabled') 25 | @[type]() 26 | 27 | cancelEvent: (e) -> 28 | # Stop the menu closing immediately 29 | e.preventDefault() 30 | e.stopPropagation() 31 | 32 | # Types 33 | 34 | copy: -> 35 | @stage.clipboard.copyInternal() 36 | 37 | paste: -> 38 | @stage.clipboard.pasteInternal() 39 | 40 | bringForward: -> 41 | @stage.history.record() 42 | @stage.bringForward() 43 | 44 | bringBack: -> 45 | @stage.history.record() 46 | @stage.bringBack() 47 | 48 | bringToFront: -> 49 | @stage.history.record() 50 | @stage.bringToFront() 51 | 52 | bringToBack: -> 53 | @stage.history.record() 54 | @stage.bringToBack() 55 | 56 | class ContextMenu extends Spine.Controller 57 | events: 58 | 'contextmenu': 'open' 59 | 60 | constructor: (@stage) -> 61 | super(el: @stage.el) 62 | $('body').bind('mousedown', @close) 63 | 64 | open: (e) -> 65 | return if e.metaKey 66 | 67 | e.preventDefault() 68 | @close() 69 | 70 | position = 71 | left: e.pageX + 1 72 | top: e.pageY + 1 73 | 74 | @menu = new Menu(@stage, position) 75 | $('body').append(@menu.el) 76 | 77 | close: => 78 | @menu?.release() 79 | @menu = null 80 | 81 | release: -> 82 | $('body').unbind('mousedown', @close) 83 | 84 | module.exports = ContextMenu -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/dragging.module.coffee: -------------------------------------------------------------------------------- 1 | class CoordTitle extends Spine.Controller 2 | className: 'coordTitle' 3 | 4 | change: (area) -> 5 | @html("x: #{area.left}px   y: #{area.top}px") 6 | 7 | move: (position) -> 8 | @el.css(left: position.left, top: position.top) 9 | 10 | class Dragging extends Spine.Controller 11 | events: 12 | 'mousedown .selected': 'listen' 13 | 14 | constructor: (@stage) -> 15 | super(el: @stage.el) 16 | 17 | listen: (e) => 18 | e.preventDefault() 19 | 20 | # Copy elements when alt dragging 21 | if e.altKey 22 | clones = @stage.cloneSelected() 23 | @stage.selection.refresh(clones) 24 | 25 | @stage.history.record() 26 | 27 | @dragPosition = {left: e.pageX, top: e.pageY} 28 | @active = false 29 | 30 | $(document).mousemove(@drag) 31 | $(document).mouseup(@drop) 32 | 33 | drag: (e) => 34 | if @active is false 35 | @trigger('start.dragging') 36 | 37 | @active = true 38 | 39 | difference = 40 | left: e.pageX - @dragPosition.left 41 | top: e.pageY - @dragPosition.top 42 | 43 | @dragPosition = {left: e.pageX, top: e.pageY} 44 | @stageArea = @stage.area() 45 | @selectionArea = @stage.selection.area() 46 | 47 | if e.altKey or e.metaKey 48 | @stage.snapping.release() 49 | else 50 | # Check vertical/center stage snapping 51 | difference = @stage.snapping.snap(@selectionArea, difference) 52 | 53 | # Setup CoordTitle 54 | @moveCoordTitle(e) 55 | 56 | @stage.selection.moveBy(difference) 57 | @el.trigger('move.dragging') 58 | 59 | drop: (e) => 60 | $(document).unbind('mousemove', @drag) 61 | $(document).unbind('mouseup', @drop) 62 | @el.trigger('end.dragging') if @active 63 | 64 | # Reset coordTitle 65 | @coordTitle?.release() 66 | @coordTitle = null 67 | 68 | moveCoordTitle: -> 69 | unless @coordTitle 70 | @append(@coordTitle = new CoordTitle) 71 | 72 | @coordTitle.move( 73 | left: @dragPosition.left - @stageArea.left + 10, 74 | top: @dragPosition.top - @stageArea.top + 10 75 | ) 76 | 77 | @coordTitle.change(@selectionArea) 78 | 79 | module.exports = Dragging -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/drop_area.module.coffee: -------------------------------------------------------------------------------- 1 | module.exports = DropArea 2 | 3 | Image = require('app/controllers/elements/image') 4 | 5 | class DropArea extends Spine.Controller 6 | events: 7 | 'dragover': 'cancel' 8 | 'drop': 'drop' 9 | 10 | constructor: (@stage) -> 11 | super(el: @stage.el) 12 | $('body').bind('dragover', @cancel) 13 | 14 | drop: (e) -> 15 | e.preventDefault() 16 | e = e.originalEvent 17 | 18 | for file in e.dataTransfer.files 19 | reader = new FileReader 20 | reader.onload = (e) => 21 | @addImage(e.target.result) 22 | reader.readAsDataURL(file) 23 | 24 | addImage: (src) -> 25 | @stage.history.record() 26 | 27 | element = new Image(src: src) 28 | 29 | @stage.add(element) 30 | @stage.selection.clear() 31 | @stage.selection.add(element) 32 | 33 | cancel: (e) -> 34 | e.stopPropagation() 35 | e.preventDefault() 36 | 37 | release: -> 38 | $('body').unbind('dragover', @cancel) 39 | 40 | module.exports = DropArea -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/history.module.coffee: -------------------------------------------------------------------------------- 1 | Model = require('app/models/history') 2 | Serialize = require('app/models/serialize') 3 | 4 | class History extends Spine.Controller 5 | constructor: (@stage) -> 6 | super(el: @stage.el) 7 | 8 | undo: -> 9 | Model.undo() 10 | 11 | redo: -> 12 | Model.redo() 13 | 14 | record: (type) -> 15 | @recordState() unless @throttle(type) 16 | 17 | # Private 18 | 19 | recordState: (isUndo) -> 20 | elements = JSON.stringify(@stage.elements) 21 | 22 | action = (isUndo) => 23 | # Record the opposite action (i.e. redo) 24 | @recordState( !isUndo ) 25 | 26 | # Replace stage with previous state 27 | elements = Serialize.fromJSON(elements) 28 | 29 | # TODO: This is too simple, and should 30 | # be replaced with something that's more 31 | # performant. 32 | @stage.refresh(elements) 33 | 34 | Model.add(action, isUndo) 35 | 36 | # Throttling 37 | 38 | throttleLimit: 500 39 | 40 | throttle: (type) -> 41 | throttled = false 42 | current = new Date 43 | 44 | if type and @throttleType is type and 45 | (current - @throttleDate) <= @throttleLimit 46 | throttled = true 47 | 48 | @throttleType = type 49 | @throttleDate = current 50 | 51 | throttled 52 | 53 | release: -> 54 | Model.clear() 55 | 56 | module.exports = History -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/key_bindings.module.coffee: -------------------------------------------------------------------------------- 1 | class KeyBindings extends Spine.Module 2 | @include Spine.Log 3 | 4 | mapping: 5 | 8: 'backspace' 6 | 37: 'leftArrow' 7 | 38: 'upArrow' 8 | 39: 'rightArrow' 9 | 40: 'downArrow' 10 | 46: 'backspace' 11 | 65: 'aKey' 12 | 67: 'cKey' 13 | 68: 'dKey' 14 | 79: 'oKey' 15 | 83: 'sKey' 16 | 86: 'vKey' 17 | 90: 'zKey' 18 | 187: 'plusKey' 19 | 189: 'minusKey' 20 | 21 | constructor: (@stage) -> 22 | $(document).bind('keydown', @keypress) 23 | 24 | keypress: (e) => 25 | # Disable keyboard shortcuts in inputs 26 | return if 'value' of e.target 27 | return if $(e.target).attr('contenteditable') 28 | @[@mapping[e.which]]?(e) 29 | 30 | backspace: (e) -> 31 | e.preventDefault() 32 | @stage.history.record() 33 | @stage.removeSelected() 34 | 35 | leftArrow: (e) -> 36 | e.preventDefault() 37 | amount = -1 38 | amount *= 10 if e.shiftKey 39 | @stage.history.record('leftArrow') 40 | @stage.selection.moveBy(left: amount, top: 0) 41 | 42 | upArrow: (e) -> 43 | e.preventDefault() 44 | amount = -1 45 | amount *= 10 if e.shiftKey 46 | @stage.history.record('upArrow') 47 | @stage.selection.moveBy(left: 0, top: amount) 48 | 49 | rightArrow: (e) -> 50 | e.preventDefault() 51 | amount = 1 52 | amount *= 10 if e.shiftKey 53 | @stage.history.record('rightArrow') 54 | @stage.selection.moveBy(left: amount, top: 0) 55 | 56 | downArrow: (e) -> 57 | e.preventDefault() 58 | amount = 1 59 | amount *= 10 if e.shiftKey 60 | @stage.history.record('downArrow') 61 | @stage.selection.moveBy(left: 0, top: amount) 62 | 63 | aKey: (e) -> 64 | return unless e.metaKey 65 | e.preventDefault() 66 | @stage.selectAll() 67 | 68 | dKey: (e) -> 69 | return unless e.metaKey 70 | e.preventDefault() 71 | @stage.selection.clear() if e.metaKey 72 | 73 | oKey: (e) -> 74 | return unless e.metaKey 75 | e.preventDefault() 76 | @stage.load() 77 | 78 | sKey: (e) -> 79 | return unless e.metaKey 80 | e.preventDefault() 81 | @stage.save() 82 | 83 | plusKey: (e) -> 84 | return unless e.metaKey 85 | e.preventDefault() 86 | @log('zoomIn') 87 | 88 | minusKey: (e) -> 89 | return unless e.metaKey 90 | e.preventDefault() 91 | @log('zoomOut') 92 | 93 | cKey: (e) -> 94 | return unless e.metaKey 95 | @stage.clipboard.copyInternal(e) 96 | 97 | vKey: (e) -> 98 | return unless e.metaKey 99 | @stage.clipboard.pasteInternal(e) 100 | 101 | zKey: (e) -> 102 | return unless e.metaKey 103 | e.preventDefault() 104 | 105 | if e.shiftKey 106 | @stage.history.redo() 107 | else 108 | @stage.history.undo() 109 | 110 | release: -> 111 | $(document).unbind('keydown', @keypress) 112 | 113 | module.exports = KeyBindings -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/resizing.module.coffee: -------------------------------------------------------------------------------- 1 | class AreaTitle extends Spine.Controller 2 | className: 'areaTitle' 3 | 4 | change: (area) -> 5 | @html("width: #{area.width}px   height: #{area.height}px") 6 | 7 | move: (position) -> 8 | @el.css(left: position.left, top: position.top) 9 | 10 | class Resizing extends Spine.Controller 11 | events: 12 | 'start.resize': 'resizeStart' 13 | 'resize.element': 'resized' 14 | 'end.resize': 'resizeEnd' 15 | 16 | constructor: (@stage) -> 17 | super(el: @stage.el) 18 | 19 | resizeStart: -> 20 | @stage.history.record() 21 | 22 | resized: (e, element) -> 23 | area = element.area() 24 | 25 | unless @areaTitle 26 | @append(@areaTitle = new AreaTitle) 27 | 28 | @areaTitle.move( 29 | left: area.left + area.width + 10, 30 | top: area.top + area.height + 10 31 | ) 32 | 33 | @areaTitle.change(area) 34 | 35 | resizeEnd: -> 36 | @areaTitle?.release() 37 | @areaTitle = null 38 | 39 | module.exports = Resizing -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/select_area.module.coffee: -------------------------------------------------------------------------------- 1 | class Area extends Spine.Controller 2 | className: 'selectArea' 3 | 4 | constructor: (@left, @top) -> 5 | super() 6 | @el.css(left: @left, top: @top) 7 | 8 | area: -> 9 | area = @el.position() 10 | area.height = @el.height() 11 | area.width = @el.width() 12 | area 13 | 14 | resize: (left, top) -> 15 | dimensions = 16 | width: left - @left 17 | height: top - @top 18 | 19 | # Support negative areas 20 | if dimensions.width < 0 21 | dimensions.left = @left + dimensions.width 22 | dimensions.width *= -1 23 | 24 | if dimensions.height < 0 25 | dimensions.top = @top + dimensions.height 26 | dimensions.height *= -1 27 | 28 | @el.css(dimensions) 29 | 30 | class SelectArea extends Spine.Controller 31 | events: 32 | 'mousedown': 'listen' 33 | 34 | constructor: (@stage) -> 35 | super(el: @stage.el) 36 | 37 | listen: (e) => 38 | # Only listen to mousedown's on the stage 39 | return if e.target isnt e.currentTarget 40 | 41 | @offset = @el.offset() 42 | @offset.left -= @el.scrollLeft() 43 | @offset.top -= @el.scrollTop() 44 | 45 | @selectArea?.release() 46 | 47 | $(document).mousemove(@drag) 48 | $(document).mouseup(@drop) 49 | 50 | drag: (e) => 51 | # We offset by 1, so it doesn't 52 | # mess up click events 53 | unless @selectArea 54 | @selectArea = new Area( 55 | e.pageX - @offset.left + 1, 56 | e.pageY - @offset.top + 1 57 | ) 58 | 59 | @append(@selectArea) 60 | 61 | @selectArea.resize( 62 | e.pageX - @offset.left, 63 | e.pageY - @offset.top 64 | ) 65 | 66 | area = @selectArea.area() 67 | 68 | for element in @stage.elements 69 | if element.inArea(area) 70 | @stage.selection.add(element) 71 | else 72 | @stage.selection.remove(element) 73 | 74 | drop: (e) => 75 | @selectArea?.release() 76 | @selectArea = null 77 | $(document).unbind('mousemove', @drag) 78 | $(document).unbind('mouseup', @drop) 79 | 80 | module.exports = SelectArea -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/selection.module.coffee: -------------------------------------------------------------------------------- 1 | min = (a = 0, b = 0) -> 2 | return b if a is 0 3 | Math.min(a, b) 4 | 5 | max = (a = 0, b = 0) -> 6 | return b if a is 0 7 | Math.max(a, b) 8 | 9 | class Selection extends Spine.Module 10 | @include Spine.Events 11 | 12 | constructor: (@elements = []) -> 13 | 14 | # Returns a property for an element selection. 15 | # Returns null unless all of the elements have the same value. 16 | get: (key) -> 17 | return null unless @isAny() 18 | 19 | first = @elements[0]?.get(key) 20 | for el in @elements 21 | if el.get(key) isnt first 22 | return null 23 | first 24 | 25 | # Sets a property on all elements 26 | set: (key, value) -> 27 | el.set(key, value) for el in @elements 28 | 29 | isMultiple: -> 30 | @elements.length > 1 31 | 32 | isSingle: -> 33 | @elements.length is 1 34 | 35 | isAny: -> 36 | @elements.length > 0 37 | 38 | # Select an element 39 | add: (element) -> 40 | return if element in @elements 41 | 42 | @elements.push(element) 43 | element.setSelected(true) 44 | 45 | @trigger('change') 46 | 47 | # Remove a selected element 48 | remove: (element) -> 49 | return if element not in @elements 50 | element.setSelected(false) 51 | 52 | index = @elements.indexOf(element) 53 | elements = @elements.slice() 54 | elements.splice(index, 1) 55 | @elements = elements 56 | 57 | @trigger('change') 58 | 59 | refresh: (elements) -> 60 | @clear() 61 | @add(el) for el in elements 62 | 63 | clear: -> 64 | @remove(el) for el in @elements 65 | 66 | # Find total area of selected elements 67 | area: -> 68 | 69 | # Quick return for single elements 70 | if @elements.length is 1 71 | return @elements[0].area() 72 | 73 | area = {} 74 | 75 | # Loop through the elements, find the upper and 76 | # lower most ones to calculate the total area 77 | for element in @elements 78 | elementArea = element.area() 79 | area.left = min(area.left, elementArea.left) 80 | area.top = min(area.top, elementArea.top) 81 | area.right = max(area.right, elementArea.left + elementArea.width) 82 | area.bottom = max(area.bottom, elementArea.top + elementArea.height) 83 | 84 | area.width = area.right - area.left 85 | area.height = area.bottom - area.top 86 | 87 | delete area.right 88 | delete area.bottom 89 | 90 | area 91 | 92 | moveBy: (toPosition) -> 93 | # Shortcut to reduce calls on set() and paint() 94 | el.moveBy(toPosition) for el in @elements 95 | 96 | release: -> 97 | @elements = [] 98 | 99 | module.exports = Selection -------------------------------------------------------------------------------- /assets/javascripts/app/controllers/stage/zindex.module.coffee: -------------------------------------------------------------------------------- 1 | Collection = require('lib/collection') 2 | 3 | class ZIndex 4 | constructor: (@stage) -> 5 | @order = @stage.elements 6 | 7 | bringForward: (element) -> 8 | index = @order.indexOf(element) 9 | 10 | if index isnt -1 or index isnt (@order.length - 1) 11 | # Swap item forwards 12 | @order[index] = @order[index + 1] 13 | @order[index + 1] = element 14 | 15 | @set() 16 | 17 | bringBack: (element) -> 18 | index = @order.indexOf(element) 19 | 20 | if index isnt -1 or index isnt 0 21 | # Swap item backwards 22 | @order[index] = @order[index - 1] 23 | @order[index - 1] = element 24 | 25 | @set() 26 | 27 | bringToFront: (element) -> 28 | # Remove element 29 | index = @order.indexOf(element) 30 | @order.splice(index, 1) 31 | 32 | # Add it to the end 33 | @order.push(element) 34 | 35 | @set() 36 | 37 | bringToBack: (element) -> 38 | # Remove element 39 | index = @order.indexOf(element) 40 | @order.splice(index, 1) 41 | 42 | # Add it to the start 43 | @order.unshift(element) 44 | 45 | @set() 46 | 47 | set: -> 48 | for element, index in @order 49 | element.order(index) 50 | 51 | module.exports = ZIndex -------------------------------------------------------------------------------- /assets/javascripts/app/index.module.coffee: -------------------------------------------------------------------------------- 1 | Stage = require('./controllers/stage') 2 | Header = require('./controllers/header') 3 | Inspector = require('./controllers/inspector') 4 | 5 | class App extends Spine.Controller 6 | className: 'app' 7 | 8 | constructor: -> 9 | super 10 | @stage = new Stage 11 | @header = new Header(stage: @stage) 12 | @inspector = new Inspector(stage: @stage) 13 | 14 | @append( 15 | @header.render(), 16 | @stage.render(), 17 | @inspector.render() 18 | ) 19 | 20 | module.exports = App -------------------------------------------------------------------------------- /assets/javascripts/app/models/history.module.coffee: -------------------------------------------------------------------------------- 1 | class History 2 | @undoStack: [] 3 | @redoStack: [] 4 | @max: 50 5 | 6 | @add: (action, isUndo) -> 7 | if isUndo is true 8 | # Push onto the undo 9 | # stack from a redo 10 | stack = @undoStack 11 | 12 | else if isUndo is false 13 | # Push onto the redo 14 | # stack from a undo 15 | stack = @redoStack 16 | 17 | else 18 | # By default, push onto 19 | # an undo stack 20 | stack = @undoStack 21 | stack.shift() if stack.length >= @max 22 | @redoStack = [] 23 | 24 | stack.push(action) 25 | 26 | @undo: -> 27 | action = @undoStack.pop() 28 | if action 29 | action.call(this, true) 30 | else false 31 | 32 | @redo: -> 33 | action = @redoStack.pop() 34 | if action 35 | action.call(this, false) 36 | else false 37 | 38 | @clear: -> 39 | @undoStack = [] 40 | @redoStack = [] 41 | 42 | module.exports = History -------------------------------------------------------------------------------- /assets/javascripts/app/models/properties.module.coffee: -------------------------------------------------------------------------------- 1 | Property = require('./property') 2 | Values = Property.Values 3 | Background = require('./properties/background') 4 | 5 | module.exports = 6 | Property: Property 7 | Values: Values 8 | Background: Background -------------------------------------------------------------------------------- /assets/javascripts/app/models/properties/background.module.coffee: -------------------------------------------------------------------------------- 1 | Property = require('app/models/property') 2 | Color = require('./color') 3 | 4 | class Position extends Property 5 | id: "#{module.id}.Position" 6 | 7 | constructor: (@angle = 0) -> 8 | 9 | toString: -> 10 | "#{@angle}deg" 11 | 12 | toValue: -> @angle 13 | 14 | class ColorStop extends Property 15 | id: "#{module.id}.ColorStop" 16 | 17 | constructor: (@color, @length) -> 18 | @color or= new Color.Black 19 | 20 | toString: -> 21 | if @length 22 | "#{@color} #{@length}%" 23 | else 24 | "#{@color}" 25 | 26 | toValue: -> 27 | [@color, @length] 28 | 29 | class BackgroundImage extends Property 30 | id: "#{module.id}.BackgroundImage" 31 | 32 | class LinearGradient extends BackgroundImage 33 | id: "#{module.id}.LinearGradient" 34 | 35 | constructor: (@position = new Position, @stops = []) -> 36 | 37 | toString: -> 38 | stops = @stops.sort((a, b) -> a.length - b.length) 39 | "-webkit-linear-gradient(#{[@position, stops...].join(', ')})" 40 | 41 | toDisplayString: -> 42 | stops = @stops.sort((a, b) -> a.length - b.length) 43 | "linear-gradient(#{[@position, stops...].join(', ')})" 44 | 45 | toValue: -> 46 | [@position, @stops] 47 | 48 | addStop: (stop) -> 49 | @stops.push(stop) 50 | 51 | removeStop: (stop) -> 52 | index = @stops.indexOf(stop) 53 | @stops.splice(index, 1) 54 | 55 | class URL extends BackgroundImage 56 | id: "#{module.id}.URL" 57 | 58 | constructor: (@url) -> 59 | 60 | toString: -> 61 | "url('#{@url or ''}')" 62 | 63 | toValue: -> @url 64 | 65 | class Background 66 | id: module.id 67 | 68 | constructor: (@color, @images = []) -> 69 | 70 | toString: -> 71 | "#{@color} #{@images}" 72 | 73 | toValue: -> 74 | [@color, @images] 75 | 76 | module.exports = Background 77 | module.exports.BackgroundImage = BackgroundImage 78 | module.exports.LinearGradient = LinearGradient 79 | module.exports.URL = URL 80 | module.exports.Position = Position 81 | module.exports.ColorStop = ColorStop 82 | module.exports.Color = Color -------------------------------------------------------------------------------- /assets/javascripts/app/models/properties/border.module.coffee: -------------------------------------------------------------------------------- 1 | Property = require('app/models/property') 2 | Color = require('./color') 3 | 4 | class Border extends Property 5 | id: module.id 6 | 7 | constructor: (@width = 0, @style = 'solid', @color = new Color.Black) -> 8 | 9 | toString: -> 10 | [@width + 'px', @style, @color].join(' ') 11 | 12 | toValue: -> 13 | [@width, @style, @color] 14 | 15 | module.exports = Border -------------------------------------------------------------------------------- /assets/javascripts/app/models/properties/color.module.coffee: -------------------------------------------------------------------------------- 1 | Property = require('app/models/property') 2 | 3 | class Color extends Property 4 | @regex: /(?:#([0-9a-f]{3,6})|rgba?\(([^)]+)\))/i 5 | 6 | @fromHex: (hex) -> 7 | if hex[0] is '#' 8 | hex = hex.substring(1, 7) 9 | 10 | if hex.length is 3 11 | hex = hex.charAt(0) + hex.charAt(0) + 12 | hex.charAt(1) + hex.charAt(1) + 13 | hex.charAt(2) + hex.charAt(2) 14 | 15 | r = parseInt(hex.substring(0,2), 16) 16 | g = parseInt(hex.substring(2,4), 16) 17 | b = parseInt(hex.substring(4,6), 16) 18 | 19 | new this(r, g, b) 20 | 21 | @fromString: (str) -> 22 | match = str.match(@regex) 23 | return null unless match 24 | 25 | if hex = match[1] 26 | @fromHex(hex) 27 | 28 | else if rgba = match[2] 29 | [r, g, b, a] = rgba.split(/\s*,\s*/) 30 | new this(r, g, b, a) 31 | 32 | @White: (alpha) -> 33 | new Color(255, 255, 255, alpha) 34 | 35 | @Black: (alpha) -> 36 | new Color(0, 0, 0, alpha) 37 | 38 | @Transparent: -> 39 | new Color 40 | 41 | constructor: (r, g, b, a = 1) -> 42 | @r = parseInt(r, 10) if r? 43 | @g = parseInt(g, 10) if g? 44 | @b = parseInt(b, 10) if b? 45 | @a = parseFloat(a) 46 | 47 | toHex: -> 48 | unless @r? and @g? and @b? 49 | return 'transparent' 50 | 51 | a = (@b | @g << 8 | @r << 16).toString(16) 52 | a = '#' + '000000'.substr(0, 6 - a.length) + a 53 | a.toUpperCase() 54 | 55 | isTransparent: -> 56 | not @a 57 | 58 | set: (values) -> 59 | @[key] = value for key, value of values 60 | this 61 | 62 | rgb: -> 63 | result = 64 | r: @r 65 | g: @g 66 | b: @b 67 | 68 | rgba: -> 69 | result = 70 | r: @r 71 | g: @g 72 | b: @b 73 | a: @a 74 | 75 | clone: -> 76 | new @constructor(@r, @g, @b, @a) 77 | 78 | toString: -> 79 | if @r? and @g? and @b? 80 | if @a? and @a isnt 1 81 | "rgba(#{@r}, #{@g}, #{@b}, #{@a})" 82 | else 83 | @toHex() 84 | else 85 | 'transparent' 86 | 87 | id: module.id 88 | 89 | toValue: -> 90 | [@r, @g, @b, @a] 91 | 92 | module.exports = Color -------------------------------------------------------------------------------- /assets/javascripts/app/models/properties/shadow.module.coffee: -------------------------------------------------------------------------------- 1 | Property = require('app/models/property') 2 | Color = require('./color') 3 | 4 | class Shadow extends Property 5 | id: module.id 6 | 7 | constructor: (properties = {}) -> 8 | @[k] = v for k, v of properties 9 | 10 | @x or= 0 11 | @y or= 0 12 | @color or= new Color.Black(0.3) 13 | 14 | toString: -> 15 | result = [] 16 | result.push('inset') if @inset 17 | result.push(@x + 'px') 18 | result.push(@y + 'px') 19 | result.push(@blur + 'px') if @blur? 20 | result.push(@spread + 'px') if @spread? 21 | result.push(@color.toString()) 22 | result.join(' ') 23 | 24 | toValue: -> 25 | value = 26 | inset: @inset 27 | x: @x 28 | y: @y 29 | blur: @blur 30 | spread: @spread 31 | color: @color 32 | 33 | module.exports = Shadow -------------------------------------------------------------------------------- /assets/javascripts/app/models/property.module.coffee: -------------------------------------------------------------------------------- 1 | Collection = require('lib/collection') 2 | Serialize = require('./serialize').Serialize 3 | 4 | class Values extends Collection 5 | toString: -> 6 | @join(', ') 7 | 8 | class Property extends Spine.Module 9 | @include Serialize 10 | 11 | # We override valueOf to properties 12 | # can be easily compared with each other. 13 | valueOf: -> @toString() 14 | 15 | module.exports = Property 16 | module.exports.Values = Values -------------------------------------------------------------------------------- /assets/javascripts/app/models/serialize.module.coffee: -------------------------------------------------------------------------------- 1 | # This class serializes objects into a JSON string, 2 | # and then can de-serialize JSON strings into instances. 3 | # 4 | # Serialized objects are in the following format: 5 | # {id: 'path/to/module.Exports', value: objectValue} 6 | # 7 | # You should override the object's value: 8 | # toValue: -> @properties 9 | # 10 | # The objects value will be passed to it's constructor 11 | # function upon instantiation. 12 | 13 | fromObject = (object) -> 14 | return object unless object? 15 | 16 | # If type is native, return 17 | unless typeof object is 'object' 18 | return object 19 | 20 | # If type is array, call recursively 21 | if Array.isArray(object) 22 | return (fromObject(o) for o in object) 23 | 24 | # If type is object, and we're not dealing 25 | # with a instance, resolve recursively 26 | unless object.id 27 | for k, v of object 28 | object[k] = fromObject(v) 29 | return object 30 | 31 | # Otherwise, we're dealing with an instance. 32 | # Find the instance by ID, and then instantiate 33 | # it, passing object.value 34 | 35 | [path, name] = object.id.split('.', 2) 36 | constructor = require(path) 37 | constructor = constructor[name] if name 38 | 39 | # Constructor supports a custom fromValue function 40 | if result = constructor.fromValue?(object) 41 | return result 42 | 43 | # Recursively find objects 44 | args = fromObject(object.value) 45 | 46 | if Array.isArray(args) 47 | 48 | # Constructor functions can't 49 | # use apply() or call(), so we have 50 | # to manually pass arguments. 51 | new constructor( 52 | args[0], args[1], args[2], 53 | args[3], args[4], args[5] 54 | ) 55 | 56 | else 57 | new constructor(args) 58 | 59 | fromJSON = (object) -> 60 | if typeof object is 'string' 61 | object = JSON.parse(object) 62 | fromObject(object) 63 | 64 | Serialize = 65 | # ID Needs to be overridden in each 66 | # class Serialize is included in 67 | id: -> 68 | module.id 69 | 70 | toJSON: -> 71 | result = 72 | id: @id?() or @id 73 | value: @toValue?() or @toValue 74 | 75 | clone: -> 76 | fromJSON(JSON.stringify(this)) 77 | 78 | module.exports.Serialize = Serialize 79 | module.exports.fromJSON = fromJSON -------------------------------------------------------------------------------- /assets/javascripts/app/parsers/import.module.coffee: -------------------------------------------------------------------------------- 1 | importPage = (tree) -> 2 | # Iterate recursively over nodes. 3 | # Parse out some css properties, keep unknown ones 4 | # Set position to absolute 5 | # Return array of elements 6 | 7 | module.exports = importPage -------------------------------------------------------------------------------- /assets/javascripts/app/views/context_menu.jst.eco: -------------------------------------------------------------------------------- 1 |
Copy
2 |
Paste
3 |
Bring Forward
4 |
Bring to Front
5 |
Send Backward
6 |
Send To Back
-------------------------------------------------------------------------------- /assets/javascripts/app/views/header.jst.eco: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector.jst.eco: -------------------------------------------------------------------------------- 1 |
2 |
Display
3 |
Text
4 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/background.jst.eco: -------------------------------------------------------------------------------- 1 |
2 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/background/linear_gradient.jst.eco: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/assets/javascripts/app/views/inspector/background/linear_gradient.jst.eco -------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/background/list.jst.eco: -------------------------------------------------------------------------------- 1 |
2 | <% for background in @backgrounds: %> 3 |
4 |
5 | <%= background.toDisplayString?() or background.toString() %> 6 |
7 | <% end %> 8 |
9 | 10 | -------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/background/menu.jst.eco: -------------------------------------------------------------------------------- 1 |
Add Background Color
2 |
Add Linear Gradient
3 | 4 |
Add Image URL
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/background/url.jst.eco: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/border.jst.eco: -------------------------------------------------------------------------------- 1 |

Border

2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 27 | 28 |
29 | 33 | 34 | 38 |
39 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/border_radius.jst.eco: -------------------------------------------------------------------------------- 1 |

Border Radius

2 | 3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | 12 | 22 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/box_shadow.jst.eco: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 |
6 | 10 | 11 | 15 |
16 | 17 |
18 | 22 | 23 | 27 |
28 |
29 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/box_shadow/list.jst.eco: -------------------------------------------------------------------------------- 1 |
2 | <% for shadow in @shadows: %> 3 |
4 | 5 | <%= shadow.toString() %> 6 |
7 | <% end %> 8 |
9 | 10 | -------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/box_shadow/menu.jst.eco: -------------------------------------------------------------------------------- 1 |
Add Drop Shadow
2 |
Add Inner Shadow
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/dimensions.jst.eco: -------------------------------------------------------------------------------- 1 |

Dimensions

2 | 3 |
4 |
5 | 9 | 10 | 14 |
15 | 16 |
17 | 21 | 22 | 26 |
27 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/font.jst.eco: -------------------------------------------------------------------------------- 1 |

Font

2 | 3 |
4 | 8 | 9 | 13 | 14 | 24 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/opacity.jst.eco: -------------------------------------------------------------------------------- 1 |

Opacity

2 | 3 |
4 | 18 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/text_position.jst.eco: -------------------------------------------------------------------------------- 1 |

Text Position

2 | 3 |
4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 |
-------------------------------------------------------------------------------- /assets/javascripts/app/views/inspector/text_shadow.jst.eco: -------------------------------------------------------------------------------- 1 |

Text Shadow

2 | 3 |
4 |
5 | 6 | 7 |
8 | 12 | 13 | 17 |
18 | 19 |
20 | 24 | 25 | 29 |
30 |
31 |
-------------------------------------------------------------------------------- /assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require sprockets/commonjs 2 | //= require jquery 3 | //= require spine 4 | //= require spine/manager 5 | //= require spine/route 6 | //= require spine/relation 7 | //= require gfx 8 | //= require gfx/effects 9 | 10 | //= require_tree ./lib 11 | //= require_tree ./app -------------------------------------------------------------------------------- /assets/javascripts/lib/collection.module.coffee: -------------------------------------------------------------------------------- 1 | class Collection extends Array 2 | # Include events 3 | for k,v of Spine.Events 4 | @::[k] = v 5 | 6 | constructor: (value) -> 7 | if Array.isArray(value) 8 | super() 9 | @push(value...) 10 | else 11 | super 12 | 13 | refresh: (records) -> 14 | @splice(0, @length) 15 | @push(r) for r in records 16 | @trigger 'refresh' 17 | @change() 18 | 19 | push: -> 20 | res = super 21 | @trigger 'append' 22 | @change() 23 | res 24 | 25 | remove: (record) -> 26 | index = @indexOf(record) 27 | @splice(index, 1) 28 | @trigger 'remove' 29 | @change() 30 | 31 | change: (func) -> 32 | if typeof func is 'function' 33 | @bind 'change', arguments... 34 | else 35 | @trigger 'change', arguments... 36 | this 37 | 38 | include: (value) -> 39 | @indexOf(value) isnt -1 40 | 41 | first: -> 42 | @[0] 43 | 44 | last: -> 45 | @[@length - 1] 46 | 47 | valueOf: -> 48 | @slice(0) 49 | 50 | module.exports = Collection -------------------------------------------------------------------------------- /assets/javascripts/lib/ext/jquery.coffee: -------------------------------------------------------------------------------- 1 | # We want 'px' to be added to lineHeight, 2 | # for example in properties/button.module 3 | delete jQuery.cssNumber.lineHeight -------------------------------------------------------------------------------- /assets/javascripts/lib/gradient_picker.module.coffee: -------------------------------------------------------------------------------- 1 | Popup = require('./popup') 2 | ColorPicker = require('./color_picker') 3 | Color = ColorPicker.Color 4 | 5 | # TODO - abstract from properties, remove assets/grid.png dependency 6 | Background = require('app/models/properties/background') 7 | LinearGradient = Background.LinearGradient 8 | ColorStop = Background.ColorStop 9 | 10 | class Slider extends Spine.Controller 11 | className: 'slider' 12 | 13 | events: 14 | 'mousedown': 'listen' 15 | 'mouseup': 'openColorPicker' 16 | 17 | constructor: (@colorStop = new ColorStop) -> 18 | super() 19 | 20 | @inner = $('
').addClass('inner') 21 | @append @inner 22 | @render() 23 | 24 | render: -> 25 | @move(@colorStop.length) 26 | @inner.css(background: @colorStop.color) 27 | 28 | listen: (e) => 29 | e.preventDefault() 30 | e.stopPropagation() 31 | 32 | @width = @el.parent().width() 33 | @offset = @el.parent().offset() 34 | @remove = false 35 | @moved = false 36 | 37 | $(document).mousemove(@drag) 38 | $(document).mouseup(@drop) 39 | 40 | drag: (e) => 41 | @moved = true 42 | @picker?.close?() 43 | @picker = false 44 | 45 | top = e.pageY - @offset.top 46 | @remove = top > 100 or top < -100 47 | @el.toggleClass('remove', @remove) 48 | 49 | left = e.pageX - @offset.left 50 | length = (left / @width) * 100 51 | @move(length) 52 | 53 | drop: (e) => 54 | $(document).unbind('mousemove', @drag) 55 | $(document).unbind('mouseup', @drop) 56 | @release() if @remove 57 | 58 | move: (@length = 0) -> 59 | @length = Math.max(Math.min(@length, 100), 0) 60 | @length = Math.round(@length) 61 | @colorStop.length = @length 62 | 63 | @el.css(left: "#{@length}%") 64 | @el.trigger('change', [this]) 65 | 66 | release: -> 67 | @el.trigger('removed', [this]) 68 | @el.trigger('change', [this]) 69 | super 70 | 71 | openColorPicker: -> 72 | if @moved 73 | return 74 | 75 | @picker = new ColorPicker(color: @colorStop.color) 76 | 77 | @picker.bind 'change', (color) => 78 | @colorStop.color = color 79 | @el.trigger('change', [this]) 80 | @render() 81 | 82 | @picker.open(@el.offset()) 83 | 84 | class GradientPicker extends Spine.Controller 85 | className: 'gradientPicker' 86 | 87 | events: 88 | 'removed': 'removeSlider' 89 | 'change': 'set' 90 | 'click': 'createSlider' 91 | 92 | constructor: -> 93 | super 94 | 95 | @gradient or= new LinearGradient 96 | @append(new Slider(stop)) for stop in @gradient.stops 97 | 98 | # Add default stops 99 | unless @gradient.stops.length 100 | @addSlider(new ColorStop(new Color.White, 0)) 101 | @addSlider(new ColorStop(new Color.Black, 100)) 102 | 103 | @el.css(background: "#{@gradient.toString()}, url(assets/grid.png) repeat") 104 | 105 | addSlider: (colorStop = new ColorStop) -> 106 | @gradient.addStop(colorStop) 107 | @append(new Slider(colorStop)) 108 | @set() 109 | 110 | removeSlider: (e, slider) -> 111 | @gradient.removeStop(slider.colorStop) 112 | 113 | set: -> 114 | @el.css(background: "#{@gradient.toString()}, url(assets/grid.png) repeat") 115 | @trigger('change', @gradient) 116 | 117 | createSlider: (e) -> 118 | # Only create sliders for clicks directly on the picker 119 | return unless e.target is e.currentTarget 120 | 121 | left = e.pageX - @el.offset().left 122 | length = (left / @el.width()) * 100 123 | 124 | @addSlider(new ColorStop(new Color.White, length)) 125 | 126 | module.exports = GradientPicker -------------------------------------------------------------------------------- /assets/javascripts/lib/popup.module.coffee: -------------------------------------------------------------------------------- 1 | class Popup extends Spine.Controller 2 | @open: -> 3 | (new this).open(arguments...) 4 | 5 | width: 400 6 | 7 | popupEvents: 8 | 'click .close': 'close' 9 | 'mousedown': 'cancelEvent' 10 | 11 | constructor: -> 12 | super 13 | @delegateEvents(@popupEvents) 14 | @el.addClass('popup') 15 | @el.css(position: 'absolute') 16 | 17 | open: (position = {left: 0, top: 0}) => 18 | left = position.left or position.clientX 19 | top = position.top or position.clientY 20 | 21 | left -= @width + 17 22 | top -= 5 23 | 24 | @el.css(left: left, top: top).hide() 25 | $('body').append(@el) 26 | $('body').bind('mousedown', @close) 27 | 28 | @el.gfxRaisedIn() 29 | 30 | close: => 31 | @el.gfxRaisedOut() 32 | @el.queueNext => 33 | @release() 34 | @trigger 'close' 35 | 36 | release: -> 37 | $('body').unbind('mousedown', @close) 38 | super 39 | 40 | isOpen: -> 41 | !!@el.parent().length 42 | 43 | cancelEvent: (e) -> 44 | e.stopPropagation() 45 | 46 | module.exports = Popup -------------------------------------------------------------------------------- /assets/javascripts/lib/position_picker.module.coffee: -------------------------------------------------------------------------------- 1 | class PositionPicker extends Spine.Controller 2 | className: 'positionPicker' 3 | 4 | events: 5 | 'mousedown': 'drag' 6 | 7 | width: 40 8 | height: 40 9 | 10 | constructor: -> 11 | super 12 | 13 | @el.css 14 | width: @width 15 | height: @height 16 | 17 | @ball = $('
').addClass('ball') 18 | @append @ball 19 | 20 | @ball.css 21 | left: '50%' 22 | top: '50%' 23 | 24 | change: (position) -> 25 | # Offset to center 26 | left = position.left + @width / 2 27 | left = Math.max(Math.min(left, @width), 0) 28 | 29 | top = position.top + @height / 2 30 | top = Math.max(Math.min(top, @height), 0) 31 | 32 | @ball.css(left: left, top: top) 33 | 34 | drag: (e) -> 35 | return if @disabled 36 | 37 | # Center of picker on page 38 | @offset = $(@el).offset() 39 | 40 | $(document).mousemove(@over) 41 | $(document).mouseup(@drop) 42 | @over(e) 43 | 44 | over: (e) => 45 | e.preventDefault() 46 | 47 | difference = 48 | left: e.pageX - @offset.left - (@width / 2) 49 | top: e.pageY - @offset.top - (@height / 2) 50 | 51 | @change difference 52 | @trigger('change', difference) 53 | 54 | drop: => 55 | $(document).unbind('mousemove', @over) 56 | $(document).unbind('mouseup', @drop) 57 | 58 | module.exports = PositionPicker -------------------------------------------------------------------------------- /assets/javascripts/lib/utils.module.coffee: -------------------------------------------------------------------------------- 1 | dasherize = (str) -> 2 | str.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2') 3 | .replace(/([a-z\d])([A-Z])/g, '$1-$2') 4 | .toLowerCase() 5 | 6 | # Differentiate Safari from Chrome 7 | $.browser.chrome = /chrome/.test( 8 | navigator.userAgent.toLowerCase() 9 | ) 10 | 11 | requestAnimationFrame = do -> 12 | request = 13 | window.requestAnimationFrame or 14 | window.webkitRequestAnimationFrame or 15 | window.mozRequestAnimationFrame or 16 | window.oRequestAnimationFrame or 17 | window.msRequestAnimationFrame or 18 | (callback) -> 19 | window.setTimeout(callback, 1000 / 60) 20 | 21 | (callback) -> 22 | request.call(window, callback) 23 | 24 | module.exports = 25 | dasherize: dasherize 26 | browser: $.browser 27 | requestAnimationFrame: requestAnimationFrame -------------------------------------------------------------------------------- /assets/javascripts/lib/views/color_picker.jst.eco: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 | 8 | 12 | 13 | 17 | 18 | 22 | 23 | 27 | 28 |
29 |
30 |   31 |
32 |
33 |   34 |
35 |
36 | 37 | 38 | 39 |
40 |
-------------------------------------------------------------------------------- /assets/stylesheets/app/color_picker.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | .colorPicker { 4 | -webkit-user-select: none 5 | overflow: hidden 6 | hbox() 7 | padding: 6px 8 | width: 425px 9 | 10 | canvas { 11 | display: block 12 | } 13 | 14 | label { 15 | display: block 16 | overflow: hidden 17 | 18 | span { 19 | display: block 20 | float: left 21 | width: 30px 22 | line-height: 25px 23 | 24 | &:after { 25 | content: ':' 26 | } 27 | } 28 | 29 | input { 30 | width: 60px 31 | } 32 | } 33 | 34 | .spectrum, .gradient, .alpha { 35 | cursor: pointer 36 | } 37 | 38 | .preview { 39 | background: url(grid.png) repeat 40 | } 41 | 42 | .alpha { 43 | background: url(grid.png) repeat 44 | margin: 0 6px 0 0 45 | } 46 | 47 | .preview { 48 | margin: 5px 0 10px 49 | } 50 | 51 | .preview, .preview .inner { 52 | height: 55px 53 | } 54 | 55 | .original { 56 | height: 5px 57 | } 58 | 59 | button.save { 60 | float: right 61 | } 62 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/context_menu.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | .contextMenu { 4 | position: absolute 5 | min-width: 150px 6 | background: rgba(255, 255, 255, 0.95) 7 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) 8 | border-radius: 3px 9 | border: 1px solid rgba(0, 0, 0, 0.2) 10 | border-bottom-color: rgba(0, 0, 0, 0.25) 11 | 12 | color: #000 13 | font-size: 13px 14 | z-index: 10002 15 | overflow: hidden 16 | 17 | div { 18 | padding: 8px 10px 19 | border-bottom: 1px solid rgba(0, 0, 0, 0.1) 20 | cursor: pointer 21 | 22 | &:last-child { 23 | border-bottom: 0 24 | } 25 | 26 | &.disabled { 27 | color: rgba(0, 0, 0, 0.3) 28 | } 29 | 30 | &:not(.disabled):hover { 31 | color: #FFF 32 | text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.2) 33 | vbg-gradient(#BAC4D8, #9AA8C7) 34 | 35 | &:active { 36 | vbg-gradient(#9AA8C7, #BAC4D8) 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/gradient_picker.css.styl: -------------------------------------------------------------------------------- 1 | .gradientPicker { 2 | position: relative 3 | height: 20px 4 | border-radius: 3px 5 | border: 1px solid darken(#CCC, 10%) 6 | box-shadow: 0 1px 1px #FFF 7 | 8 | .slider { 9 | position: absolute 10 | width: 10px 11 | height: 16px 12 | bottom: 0 13 | 14 | margin: 0 0 -8px -5px 15 | border-radius: 3px 16 | border: 1px solid rgba(0, 0, 0, 0.3) 17 | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2) 18 | 19 | background: url(grid.png) repeat 20 | cursor: pointer 21 | 22 | .inner { 23 | border-radius: 3px 24 | height: 16px 25 | } 26 | } 27 | 28 | .colorPreview { 29 | border: 0 30 | width: auto 31 | margin: 0 32 | border: 0 33 | 34 | .inner { 35 | width: auto 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/header.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app .header { 4 | position: absolute 5 | top: 0 6 | bottom: 0 7 | left: 0 8 | width: 45px 9 | 10 | // hbg-gradient(#5D646D, #8F9398) 11 | // hbg-gradient(#DDDDDD, #CCCCCC) 12 | hbg-gradient(#F1F1F1, #DDDDDD) 13 | 14 | box-shadow: inset -1px 0 2px rgba(0, 0, 0, 0.2) 15 | vbox() 16 | -webkit-user-select: none 17 | 18 | h1 { 19 | display: block 20 | margin: 0 10px 21 | font: 23px arial,lucida,helvetica,arial,sans-serif 22 | font-weight: normal 23 | color: #FFF 24 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.3) 25 | line-height: 33px 26 | box-flex(1) 27 | } 28 | 29 | nav { 30 | vbox() 31 | 32 | div { 33 | cursor: pointer 34 | padding: 8px 12px 35 | border-bottom: 1px solid rgba(0, 0, 0, 0.2) 36 | box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4) 37 | 38 | &:active { 39 | box-shadow: inset 0 0 20px rgba(0, 0, 0, 0.3) 40 | } 41 | } 42 | 43 | span { 44 | height: 20px 45 | width: 20px 46 | line-height: 20px 47 | 48 | display: block 49 | background: rgba(0, 0, 0, 0.2) 50 | box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.35), 0 1px 0 rgba(255, 255, 255, 0.2) 51 | 52 | color: rgba(0, 0, 0, 0.4) 53 | text-shadow: 0 1px 0 #FFF 54 | font-weight: bold 55 | text-transform: uppercase 56 | font-size: 20px 57 | text-align: center 58 | 59 | border-box() 60 | } 61 | 62 | .ellipsis span { 63 | border-radius: 50% 64 | } 65 | 66 | .text span { 67 | background: none 68 | box-shadow: none 69 | } 70 | 71 | .textInput span { 72 | background: #FFF 73 | text-shadow: none 74 | font-size: 8px 75 | text-transform: none 76 | padding-left: 1px 77 | } 78 | 79 | .button span { 80 | border-radius: 5px 81 | border: 1px solid #A6A6A6; 82 | box-shadow: none 83 | background: -webkit-linear-gradient(270deg, #FFF, #FFF 30%, #F2F2F2 100%) 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/index.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app { 4 | position: absolute 5 | top: 0 6 | right: 0 7 | bottom: 0 8 | left: 0 9 | 10 | .hbox { 11 | box-flex(1) 12 | hbox() 13 | } 14 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/background.css.styl: -------------------------------------------------------------------------------- 1 | #app .inspector .background { 2 | .edit { 3 | margin: 10px 4 | } 5 | 6 | .gradientPicker { 7 | margin: 0px 5px 5px 5px 8 | } 9 | 10 | &.disabled { 11 | .list { 12 | opacity: 0.6 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/border.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app .inspector .border { 4 | label { 5 | margin-bottom: 3px 6 | 7 | span { 8 | width: 35px 9 | } 10 | 11 | input { 12 | margin-right: 10px 13 | } 14 | 15 | .colorPreview { 16 | margin: 3px 0 -5px 0 17 | } 18 | 19 | select { 20 | width: 130px 21 | } 22 | } 23 | 24 | .hbox label { 25 | margin-bottom: 0px 26 | } 27 | 28 | .borders { 29 | hbox() 30 | margin: 2px auto 10px auto 31 | vbg-gradient(lighten(rgba(238, 238, 238, 1), 80%), rgba(238, 238, 238, 0.9)) 32 | border: 1px solid #CCC 33 | border-radius: 5px 34 | box-shadow: 0 1px 0 #FFF 35 | -webkit-box-pack: center 36 | width: 185px 37 | 38 | div { 39 | overflow: hidden 40 | border-right: 1px solid #CCC 41 | 42 | &:last-child { 43 | border-right: none 44 | } 45 | 46 | &:active, &.active { 47 | box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2) 48 | } 49 | } 50 | 51 | span { 52 | display: block 53 | border-box() 54 | width: 12px 55 | height: 12px 56 | margin: 5px 12px 57 | border: 1px solid #777 58 | cursor: pointer 59 | } 60 | 61 | .borderTop span { 62 | border-top-width: 3px 63 | } 64 | 65 | .borderRight span { 66 | border-right-width: 3px 67 | } 68 | 69 | .borderBottom span { 70 | border-bottom-width: 3px 71 | } 72 | 73 | .borderLeft span { 74 | border-left-width: 3px 75 | } 76 | } 77 | 78 | &.disabled { 79 | .borders, input, select { 80 | opacity: 0.6 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/border_radius.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app .inspector .borderRadius { 4 | .borders { 5 | hbox() 6 | margin: 3px auto 10px auto 7 | vbg-gradient(lighten(rgba(238, 238, 238, 1), 80%), rgba(238, 238, 238, 0.9)) 8 | border: 1px solid #CCC 9 | border-radius: 5px 10 | box-shadow: 0 1px 0 #FFF 11 | -webkit-box-pack: center 12 | width: 185px 13 | 14 | div { 15 | overflow: hidden 16 | border-right: 1px solid #CCC 17 | 18 | &:last-child { 19 | border-right: none 20 | } 21 | 22 | &:active, &.active { 23 | box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.2) 24 | } 25 | } 26 | 27 | span { 28 | display: block 29 | border-box() 30 | width: 12px 31 | height: 12px 32 | margin: 5px 12px 33 | border: 1px solid #777 34 | cursor: pointer 35 | } 36 | 37 | .borderRadius span { 38 | border-radius: 4px 39 | } 40 | 41 | .borderRadiusTopLeft span { 42 | border-top-left-radius: 4px 43 | } 44 | 45 | .borderRadiusTopRight span { 46 | border-top-right-radius: 4px 47 | } 48 | 49 | .borderRadiusBottomRight span { 50 | border-bottom-right-radius: 4px 51 | } 52 | 53 | .borderRadiusBottomLeft span { 54 | border-bottom-left-radius: 4px 55 | } 56 | } 57 | 58 | label { 59 | hbox() 60 | 61 | input { 62 | display: block 63 | } 64 | 65 | input[type=range] { 66 | margin: 5px 67 | box-flex(1) 68 | } 69 | 70 | input[type=number] { 71 | width: 40px 72 | } 73 | } 74 | 75 | &.disabled { 76 | .borders, input { 77 | opacity: 0.6 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/box_shadow.css.styl: -------------------------------------------------------------------------------- 1 | #app .inspector .boxShadow { 2 | .positionPicker { 3 | margin: 3px 5px 3px 0 4 | } 5 | 6 | label { 7 | span { 8 | margin-left: 3px 9 | width: auto 10 | } 11 | } 12 | 13 | .blur span, .color span { 14 | width: 33px 15 | } 16 | 17 | .color span { 18 | position relative 19 | top: -6px 20 | } 21 | 22 | &.disabled { 23 | .list { 24 | opacity: 0.6 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/dimensions.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app .inspector .dimensions { 4 | label { 5 | margin-right: 10px 6 | 7 | span { 8 | width: 38px 9 | } 10 | 11 | input { 12 | width: 45px 13 | } 14 | 15 | &:last-child span { 16 | width: 15px 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/font.css.styl: -------------------------------------------------------------------------------- 1 | #app .inspector .font { 2 | label span { 3 | width: 45px 4 | } 5 | 6 | select { 7 | width: 120px 8 | } 9 | 10 | .color span { 11 | position relative 12 | top: -6px 13 | } 14 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/opacity.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app .inspector .opacity { 4 | label { 5 | hbox() 6 | 7 | input { 8 | display: block 9 | } 10 | 11 | input[type=range] { 12 | width: 135px 13 | margin: 5px 14 | box-flex(1) 15 | } 16 | 17 | input[type=number] { 18 | width: 40px 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/popup_menu.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | .popupMenu { 4 | -webkit-user-select: none 5 | 6 | position: absolute 7 | min-width: 130px 8 | background: rgba(255, 255, 255, 0.95) 9 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2) 10 | border-radius: 3px 11 | border: 1px solid rgba(0, 0, 0, 0.2) 12 | border-bottom-color: rgba(0, 0, 0, 0.25) 13 | 14 | color: #000 15 | font-size: 13px 16 | z-index: 10002 17 | overflow: hidden 18 | 19 | div { 20 | padding: 8px 10px 21 | border-bottom: 1px solid rgba(0, 0, 0, 0.1) 22 | cursor: pointer 23 | 24 | &:last-child { 25 | border-bottom: 0 26 | } 27 | 28 | &.disabled { 29 | color: rgba(0, 0, 0, 0.3) 30 | } 31 | 32 | &:not(.disabled):hover { 33 | color: #FFF 34 | text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.2) 35 | vbg-gradient(#BAC4D8, #9AA8C7) 36 | 37 | &:active { 38 | vbg-gradient(#9AA8C7, #BAC4D8) 39 | } 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/text_position.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app .inspector .textPosition { 4 | label span { 5 | width: 85px 6 | } 7 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/inspector/text_shadow.css.styl: -------------------------------------------------------------------------------- 1 | #app .inspector .textShadow { 2 | .positionPicker { 3 | margin: 3px 5px 3px 0 4 | } 5 | 6 | label { 7 | span { 8 | margin-left: 3px 9 | width: auto 10 | } 11 | } 12 | 13 | .blur span, .color span { 14 | width: 33px 15 | } 16 | 17 | .color span { 18 | position relative 19 | top: -6px 20 | } 21 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/popup.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | .popup { 4 | position: absolute 5 | 6 | border-radius: 4px 7 | border: 1px solid rgba(181,181,181, 1) 8 | border-bottom-color: rgba(167,167,167,1) 9 | border-top-color: rgba(216,216,216,1) 10 | 11 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.24) 12 | background: #FFF 13 | min-width: 400px 14 | color: #000 15 | z-index: 10000 16 | 17 | article { 18 | padding: 5px 19 | } 20 | 21 | footer { 22 | border-top: 1px solid rgba(0, 0, 0, 0.15) 23 | inset-line(0.8) 24 | vbg-gradient(lighten(rgba(238, 238, 238, 1), 80%), rgba(238, 238, 238, 0.9)) 25 | -webkit-border-radius: 0 0 4px 4px 26 | 27 | button { 28 | margin: 0 29 | 30 | color: #717A81; 31 | font-size: 11px; 32 | font-weight: bold 33 | text-transform: uppercase 34 | text-shadow: 0 1px 1px #FFF; 35 | padding: 8px 13px 36 | box-shadow: inset -1px 0 1px rgba(255, 255, 255, 0.5) 37 | border: none 38 | border-right: 1px solid rgba(0, 0, 0, 0.15) 39 | background: none 40 | } 41 | } 42 | } 43 | 44 | .popup.list { 45 | .items { 46 | min-height: 180px 47 | } 48 | 49 | .item { 50 | padding: 10px 51 | cursor: pointer 52 | 53 | &.selected { 54 | color: #FFF 55 | vbg-gradient(#BAC4D8, #9AA8C7) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /assets/stylesheets/app/stage.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #app .stage { 4 | position: absolute 5 | top: 0 6 | right: 220px 7 | bottom: 0 8 | left: 45px 9 | 10 | // background: url('bg-grid.png') 11 | background: #FFF 12 | overflow: auto 13 | -webkit-user-select: none 14 | 15 | .element { 16 | border-box() 17 | color: #000 18 | outline: none 19 | position: absolute 20 | left: 0 21 | top: 0 22 | } 23 | 24 | .element.editing { 25 | -webkit-user-select: text 26 | } 27 | 28 | .element.text { 29 | white-space: nowrap 30 | padding: 5px 31 | 32 | &.editing, &.selected { 33 | outline: 1px solid rgba(0, 0, 0, 0.2) 34 | } 35 | } 36 | 37 | .resizing .thumb { 38 | position: absolute 39 | width: 6px 40 | height: 6px 41 | background: #FFF 42 | border: 1px solid #000 43 | cursor: pointer 44 | 45 | &.tl { 46 | left: -3px 47 | top: -3px 48 | cursor: nw-resize 49 | } 50 | 51 | &.tt { 52 | margin-left: -3px 53 | left: 50% 54 | top:-3px 55 | cursor: n-resize 56 | } 57 | 58 | &.tr { 59 | right: -3px 60 | top: -3px 61 | cursor: ne-resize 62 | } 63 | 64 | &.rr { 65 | right: -3px 66 | top: 50% 67 | margin-top: -3px 68 | cursor: e-resize 69 | } 70 | 71 | &.br { 72 | right: -3px 73 | bottom: -3px 74 | cursor: se-resize 75 | } 76 | 77 | &.bb { 78 | left: 50% 79 | bottom: -3px 80 | margin-left: -3px 81 | cursor: s-resize 82 | } 83 | 84 | &.bl { 85 | left: -3px 86 | bottom: -3px 87 | cursor: sw-resize 88 | } 89 | 90 | &.ll { 91 | top: 50% 92 | left: -3px 93 | margin-top: -3px 94 | cursor: w-resize 95 | } 96 | } 97 | 98 | .selectArea { 99 | position: absolute 100 | border: 1px solid rgba(0, 0, 0, 0.2) 101 | background: rgba(0, 0, 0, 0.1) 102 | z-index: 1000 103 | } 104 | 105 | .snapLine { 106 | background: #FBFF40 107 | position: absolute 108 | z-index: 9999 109 | 110 | &.vertical { 111 | width: 1px 112 | } 113 | 114 | &.horizontal { 115 | height: 1px 116 | } 117 | } 118 | 119 | .coordTitle, .areaTitle { 120 | color: #000 121 | position: absolute 122 | padding: 3px 123 | background: rgba(255, 249, 215, 0.85) 124 | box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.3) 125 | z-index: 10001 126 | } 127 | } -------------------------------------------------------------------------------- /assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | = require theme 3 | = require_tree ./app 4 | */ -------------------------------------------------------------------------------- /assets/stylesheets/mixins.styl: -------------------------------------------------------------------------------- 1 | border-radius() 2 | -moz-border-radius: arguments 3 | -webkit-border-radius: arguments 4 | border-radius: arguments 5 | 6 | /* Vertical Background Gradient */ 7 | vbg-gradient(fc = #FFF, tc = #FFF) 8 | background: fc 9 | background: -webkit-gradient(linear, left top, left bottom, from(fc), to(tc)) 10 | background: -webkit-linear-gradient(top, fc, tc) 11 | background: -moz-linear-gradient(top, fc, tc) 12 | background: linear-gradient(top, fc, tc) 13 | 14 | /* Horizontal Background Gradient */ 15 | hbg-gradient(fc = #FFF, tc = #FFF) 16 | background: fc 17 | background: -webkit-gradient(linear, left top, right top, from(fc), to(tc)) 18 | background: -webkit-linear-gradient(left, fc, tc) 19 | background: -moz-linear-gradient(left, fc, tc) 20 | background: linear-gradient(left, fc, tc) 21 | 22 | box-shadow() 23 | -moz-box-shadow: arguments 24 | -webkit-box-shadow: arguments 25 | box-shadow: arguments 26 | 27 | inset-box-shadow() 28 | -moz-box-shadow: inset arguments 29 | -webkit-box-shadow: inset arguments 30 | box-shadow: inset arguments 31 | 32 | box-flex(s = 0) 33 | -webkit-box-flex: s 34 | -moz-box-flex: s 35 | box-flex: s 36 | 37 | hbox() 38 | display: -webkit-box 39 | -webkit-box-orient: horizontal 40 | -webkit-box-align: stretch 41 | -webkit-box-pack: start 42 | 43 | display: -moz-box 44 | -moz-box-orient: horizontal 45 | -moz-box-align: stretch 46 | -moz-box-pack: start 47 | 48 | vbox() 49 | display: -webkit-box 50 | -webkit-box-orient: vertical 51 | -webkit-box-align: stretch 52 | 53 | display: -moz-box 54 | -moz-box-orient: vertical 55 | -moz-box-align: stretch 56 | 57 | border-box() 58 | -webkit-box-sizing: border-box 59 | -moz-box-sizing: border-box 60 | box-sizing: border-box 61 | 62 | transition(s = 0.3s, o = opacity, t = linear) 63 | -webkit-transition: s o t 64 | -moz-transition: s o t 65 | transition: s o t 66 | 67 | ellipsis() 68 | text-overflow: ellipsis 69 | overflow: hidden 70 | white-space:nowrap 71 | 72 | inset-line(opacity = 0.4, size = 1px) 73 | inset-box-shadow(0, size, 0, rgba(255, 255, 255, opacity)) 74 | 75 | outset-line(opacity = 0.4, size = 1px) 76 | box-shadow(0, size, 0, rgba(255, 255, 255, opacity)) 77 | 78 | box-pack(type = center) 79 | -webkit-box-pack: type 80 | -moz-box-pack: type 81 | box-pack: type 82 | 83 | transform(tr) 84 | -webkit-transform: tr 85 | -moz-transform: tr 86 | -ms-transform: tr 87 | -o-transform: tr 88 | transform: tr 89 | 90 | hacel() 91 | transform(translate3d(0,0,0)) -------------------------------------------------------------------------------- /assets/stylesheets/theme.css.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | body, html { 4 | height: 100% 5 | width: 100% 6 | margin: 0 7 | padding: 0 8 | overflow: hidden 9 | // -webkit-user-select: none 10 | } 11 | 12 | body { 13 | text-rendering: optimizeLegibility 14 | -webkit-font-smoothing: antialiased 15 | color: #626262; 16 | font-family: 'Helvetica Neue', Helvetica, Arial Geneva, sans-serif; 17 | font-size: 12px; 18 | } 19 | 20 | // * { 21 | // -moz-box-sizing: border-box; 22 | // -webkit-box-sizing: border-box; 23 | // box-sizing: border-box; 24 | // } 25 | // 26 | // p { 27 | // margin: 0 0 10px 0 28 | // line-height: 1.5em 29 | // } 30 | // 31 | // a { 32 | // color: #2D81C5 33 | // text-shadow: 0 1px 0 #FFF 34 | // text-decoration: none 35 | // cursor: pointer 36 | // } 37 | // 38 | // ::selection { 39 | // background: #E0EDF8 40 | // text-shadow: none 41 | // } 42 | // 43 | // hr { 44 | // border: 1px solid lighten(#D1D1D1, 20%) 45 | // border-width: 1px 0 0 0 46 | // margin: 10px 0 20px 0 47 | // } 48 | // 49 | // input[type=text], input[type=url], textarea { 50 | // padding: 3px 51 | // margin: 0 52 | // border: 1px solid rgba(0, 0, 0, 0.2) 53 | // inset-box-shadow(0, 1px, 2px, rgba(0, 0, 0, 0.1)) 54 | // } 55 | // 56 | // input[type=text], input[type=url], textarea, select { 57 | // &:focus { 58 | // outline: none 59 | // border-color: rgba(104, 189, 244, 0.8) 60 | // 61 | // -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(104, 189, 244, 0.6) 62 | // -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(104, 189, 244, 0.6) 63 | // box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(104, 189, 244, 0.6) 64 | // } 65 | // } 66 | // 67 | // textarea { 68 | // padding: 5px 69 | // height: 80px 70 | // } 71 | // 72 | 73 | .right { 74 | float: right 75 | } 76 | 77 | .left { 78 | float: left 79 | } 80 | 81 | .hidden { 82 | display: none !important 83 | } 84 | 85 | .vbox { 86 | vbox() 87 | } 88 | 89 | .hbox { 90 | hbox() 91 | } -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | require './app' 2 | 3 | map "/assets" do 4 | run App.sprockets 5 | end 6 | 7 | map "/" do 8 | run App 9 | end -------------------------------------------------------------------------------- /public/application.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/application.icns -------------------------------------------------------------------------------- /public/application.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/application.png -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/button.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/button":function(exports, require, module){(function() { 62 | var Background, Button, Color, Element, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Element = require('../element'); 67 | 68 | Color = require('app/models/properties/color'); 69 | 70 | Background = require('app/models/properties/background'); 71 | 72 | Button = (function(_super) { 73 | 74 | __extends(Button, _super); 75 | 76 | Button.name = 'Button'; 77 | 78 | function Button() { 79 | return Button.__super__.constructor.apply(this, arguments); 80 | } 81 | 82 | Button.prototype.className = 'button'; 83 | 84 | Button.prototype.id = module.id; 85 | 86 | Button.prototype.events = { 87 | 'resize.element': 'syncLineHeight' 88 | }; 89 | 90 | Button.prototype.defaults = function() { 91 | var result; 92 | return result = { 93 | width: 100, 94 | height: 40, 95 | textAlign: 'center', 96 | lineHeight: 40, 97 | borderRadius: 5, 98 | borderWidth: 1, 99 | borderStyle: 'solid', 100 | borderColor: new Color(166, 166, 166), 101 | backgroundImage: [new Background.LinearGradient(new Background.Position(270), [new Background.ColorStop(new Color.White, 0), new Background.ColorStop(new Color.White, 30), new Background.ColorStop(new Color(242, 242, 242), 100)])] 102 | }; 103 | }; 104 | 105 | Button.prototype.syncLineHeight = function() { 106 | return this.set({ 107 | lineHeight: this.get('height') 108 | }); 109 | }; 110 | 111 | return Button; 112 | 113 | })(Element); 114 | 115 | module.exports = Button; 116 | 117 | }).call(this); 118 | ;}}); 119 | -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/canvas.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/canvas":function(exports, require, module){(function() { 62 | var Canvas, Element, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Element = require('../element'); 67 | 68 | Canvas = (function(_super) { 69 | 70 | __extends(Canvas, _super); 71 | 72 | Canvas.name = 'Canvas'; 73 | 74 | Canvas.prototype.tag = 'canvas'; 75 | 76 | Canvas.prototype.points = []; 77 | 78 | function Canvas() { 79 | Canvas.__super__.constructor.apply(this, arguments); 80 | this.ctx = this.el[0].getContext('2d'); 81 | } 82 | 83 | Canvas.prototype.paint = function() { 84 | var first, point, points, _i, _len, _ref, _ref1, _ref2; 85 | first = this.points[0]; 86 | points = this.points.slice(1, this.points.length); 87 | if (!first) { 88 | return; 89 | } 90 | this.ctx.beginPath(); 91 | (_ref = this.ctx).moveTo.apply(_ref, first); 92 | _ref1 = this.points; 93 | for (_i = 0, _len = _ref1.length; _i < _len; _i++) { 94 | point = _ref1[_i]; 95 | (_ref2 = this.ctx).lineTo.apply(_ref2, point); 96 | } 97 | return this.ctx.fill(); 98 | }; 99 | 100 | Canvas.prototype.width = function(val) {}; 101 | 102 | Canvas.prototype.height = function(val) {}; 103 | 104 | Canvas.prototype.backgroundImage = function(val) {}; 105 | 106 | Canvas.prototype.backgroundColor = function(val) {}; 107 | 108 | Canvas.prototype.borderBottom = function(val) {}; 109 | 110 | Canvas.prototype.boxShadow = function(val) {}; 111 | 112 | Canvas.prototype.borderRadius = function(val) {}; 113 | 114 | return Canvas; 115 | 116 | })(Element); 117 | 118 | module.exports = Canvas; 119 | 120 | }).call(this); 121 | ;}}); 122 | -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/ellipsis.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/ellipsis":function(exports, require, module){(function() { 62 | var Element, Ellipsis, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Element = require('../element'); 67 | 68 | Ellipsis = (function(_super) { 69 | 70 | __extends(Ellipsis, _super); 71 | 72 | Ellipsis.name = 'Ellipsis'; 73 | 74 | Ellipsis.prototype.className = 'ellipsis'; 75 | 76 | Ellipsis.prototype.id = module.id; 77 | 78 | function Ellipsis() { 79 | Ellipsis.__super__.constructor.apply(this, arguments); 80 | this.properties['borderRadius'] = '50%'; 81 | this.paint(); 82 | } 83 | 84 | Ellipsis.prototype.borderRadius = function() { 85 | return false; 86 | }; 87 | 88 | return Ellipsis; 89 | 90 | })(Element); 91 | 92 | module.exports = Ellipsis; 93 | 94 | }).call(this); 95 | ;}}); 96 | -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/image.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/image":function(exports, require, module){(function() { 62 | var Color, Element, Image, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Element = require('../element'); 67 | 68 | Color = require('app/models/properties/color'); 69 | 70 | Image = (function(_super) { 71 | 72 | __extends(Image, _super); 73 | 74 | Image.name = 'Image'; 75 | 76 | Image.prototype.className = 'image'; 77 | 78 | Image.prototype.id = module.id; 79 | 80 | function Image(attrs) { 81 | if (attrs == null) { 82 | attrs = {}; 83 | } 84 | Image.__super__.constructor.apply(this, arguments); 85 | this.setSrc(attrs.src); 86 | } 87 | 88 | Image.prototype.setSrc = function(src) { 89 | this.src = src; 90 | if (this.src) { 91 | return this.set({ 92 | backgroundColor: new Color.Transparent, 93 | backgroundImage: "url(" + this.src + ")", 94 | backgroundSize: '100% 100%', 95 | backgroundRepeat: 'no-repeat', 96 | backgroundPosition: 'center center' 97 | }); 98 | } 99 | }; 100 | 101 | Image.prototype.toValue = function() { 102 | var result; 103 | result = Image.__super__.toValue.apply(this, arguments); 104 | result.src = this.src; 105 | return result; 106 | }; 107 | 108 | return Image; 109 | 110 | })(Element); 111 | 112 | module.exports = Image; 113 | 114 | }).call(this); 115 | ;}}); 116 | -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/input.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/input":function(exports, require, module){(function() { 62 | var CheckBox, Color, Element, Shadow, Text, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Element = require('../element'); 67 | 68 | Color = require('app/models/properties/color'); 69 | 70 | Shadow = require('app/models/properties/shadow'); 71 | 72 | Text = (function(_super) { 73 | 74 | __extends(Text, _super); 75 | 76 | Text.name = 'Text'; 77 | 78 | function Text() { 79 | return Text.__super__.constructor.apply(this, arguments); 80 | } 81 | 82 | Text.prototype.className = 'textInput'; 83 | 84 | Text.prototype.id = module.id + '.Text'; 85 | 86 | Text.prototype.defaults = function() { 87 | var result; 88 | return result = { 89 | width: 125, 90 | height: 20, 91 | padding: 3, 92 | borderWidth: 1, 93 | borderStyle: 'solid', 94 | borderColor: new Color(155, 155, 155), 95 | boxShadow: [ 96 | new Shadow({ 97 | inset: true, 98 | x: 0, 99 | y: 1, 100 | blur: 2, 101 | color: new Color(0, 0, 0, 0.12) 102 | }) 103 | ], 104 | backgroundColor: new Color.White 105 | }; 106 | }; 107 | 108 | return Text; 109 | 110 | })(Element); 111 | 112 | CheckBox = (function(_super) { 113 | 114 | __extends(CheckBox, _super); 115 | 116 | CheckBox.name = 'CheckBox'; 117 | 118 | function CheckBox() { 119 | return CheckBox.__super__.constructor.apply(this, arguments); 120 | } 121 | 122 | return CheckBox; 123 | 124 | })(Element); 125 | 126 | module.exports = { 127 | Text: Text, 128 | CheckBox: CheckBox 129 | }; 130 | 131 | }).call(this); 132 | ;}}); 133 | -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/line.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/line":function(exports, require, module){(function() { 62 | var Element, Line, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Element = require('../element'); 67 | 68 | Line = (function(_super) { 69 | 70 | __extends(Line, _super); 71 | 72 | Line.name = 'Line'; 73 | 74 | function Line() { 75 | return Line.__super__.constructor.apply(this, arguments); 76 | } 77 | 78 | Line.prototype.className = 'line'; 79 | 80 | return Line; 81 | 82 | })(Element); 83 | 84 | module.exports = Line; 85 | 86 | }).call(this); 87 | ;}}); 88 | -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/rectangle.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/rectangle":function(exports, require, module){(function() { 62 | var Element, Rectangle, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Element = require('../element'); 67 | 68 | Rectangle = (function(_super) { 69 | 70 | __extends(Rectangle, _super); 71 | 72 | Rectangle.name = 'Rectangle'; 73 | 74 | function Rectangle() { 75 | return Rectangle.__super__.constructor.apply(this, arguments); 76 | } 77 | 78 | Rectangle.prototype.className = 'rectangle'; 79 | 80 | Rectangle.prototype.id = module.id; 81 | 82 | return Rectangle; 83 | 84 | })(Element); 85 | 86 | module.exports = Rectangle; 87 | 88 | }).call(this); 89 | ;}}); 90 | -------------------------------------------------------------------------------- /public/assets/app/controllers/elements/triangle.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/elements/triangle":function(exports, require, module){(function() { 62 | var Canvas, Triangle, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Canvas = require('./canvas'); 67 | 68 | Triangle = (function(_super) { 69 | 70 | __extends(Triangle, _super); 71 | 72 | Triangle.name = 'Triangle'; 73 | 74 | function Triangle() { 75 | return Triangle.__super__.constructor.apply(this, arguments); 76 | } 77 | 78 | Triangle.prototype.className = 'triangle'; 79 | 80 | Triangle.prototype.points = [1, 2, 3]; 81 | 82 | return Triangle; 83 | 84 | })(Canvas); 85 | 86 | module.exports = Rectangle; 87 | 88 | }).call(this); 89 | ;}}); 90 | -------------------------------------------------------------------------------- /public/assets/app/controllers/inspector/opacity.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/inspector/opacity":function(exports, require, module){(function() { 62 | var Opacity, 63 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 64 | __hasProp = {}.hasOwnProperty, 65 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 66 | 67 | Opacity = (function(_super) { 68 | 69 | __extends(Opacity, _super); 70 | 71 | Opacity.name = 'Opacity'; 72 | 73 | function Opacity() { 74 | this.render = __bind(this.render, this); 75 | return Opacity.__super__.constructor.apply(this, arguments); 76 | } 77 | 78 | Opacity.prototype.className = 'opacity'; 79 | 80 | Opacity.prototype.events = { 81 | 'change input': 'change', 82 | 'focus input': 'inputFocus' 83 | }; 84 | 85 | Opacity.prototype.elements = { 86 | 'input': '$inputs' 87 | }; 88 | 89 | Opacity.prototype.render = function() { 90 | var _ref; 91 | this.disabled = !this.stage.selection.isAny(); 92 | this.opacity = (_ref = this.stage.selection.get('opacity')) != null ? _ref : 1; 93 | return this.html(JST['app/views/inspector/opacity'](this)); 94 | }; 95 | 96 | Opacity.prototype.change = function(e) { 97 | var val; 98 | this.stage.history.record('opacity'); 99 | val = parseFloat($(e.currentTarget).val()); 100 | val = Math.round(val * 100) / 100; 101 | this.stage.history.record('opacity'); 102 | this.stage.selection.set('opacity', val); 103 | return this.$inputs.val(val); 104 | }; 105 | 106 | return Opacity; 107 | 108 | })(Spine.Controller); 109 | 110 | module.exports = Opacity; 111 | 112 | }).call(this); 113 | ;}}); 114 | -------------------------------------------------------------------------------- /public/assets/app/controllers/inspector/popup_menu.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/inspector/popup_menu":function(exports, require, module){(function() { 62 | var PopupMenu, 63 | __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, 64 | __hasProp = {}.hasOwnProperty, 65 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 66 | 67 | PopupMenu = (function(_super) { 68 | 69 | __extends(PopupMenu, _super); 70 | 71 | PopupMenu.name = 'PopupMenu'; 72 | 73 | PopupMenu.open = function(position) { 74 | return (new this).open(position); 75 | }; 76 | 77 | PopupMenu.prototype.popupMenuEvents = { 78 | 'mousedown': 'cancelEvent' 79 | }; 80 | 81 | function PopupMenu() { 82 | this.close = __bind(this.close, this); 83 | PopupMenu.__super__.constructor.apply(this, arguments); 84 | this.delegateEvents(this.popupMenuEvents); 85 | this.el.addClass('popupMenu'); 86 | this.el.css({ 87 | position: 'absolute' 88 | }); 89 | } 90 | 91 | PopupMenu.prototype.open = function(position) { 92 | if (position == null) { 93 | position = {}; 94 | } 95 | this.el.css(position); 96 | $('body').append(this.el); 97 | $('body').bind('mousedown', this.close); 98 | return this; 99 | }; 100 | 101 | PopupMenu.prototype.close = function() { 102 | this.release(); 103 | return this; 104 | }; 105 | 106 | PopupMenu.prototype.release = function() { 107 | $('body').unbind('mousedown', this.close); 108 | return PopupMenu.__super__.release.apply(this, arguments); 109 | }; 110 | 111 | PopupMenu.prototype.cancelEvent = function(e) { 112 | return e.stopPropagation(); 113 | }; 114 | 115 | return PopupMenu; 116 | 117 | })(Spine.Controller); 118 | 119 | module.exports = PopupMenu; 120 | 121 | }).call(this); 122 | ;}}); 123 | -------------------------------------------------------------------------------- /public/assets/app/controllers/inspector/stage.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/inspector/stage":function(exports, require, module){(function() { 62 | var StageInspector, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | StageInspector = (function(_super) { 67 | 68 | __extends(StageInspector, _super); 69 | 70 | StageInspector.name = 'StageInspector'; 71 | 72 | function StageInspector() { 73 | return StageInspector.__super__.constructor.apply(this, arguments); 74 | } 75 | 76 | StageInspector.prototype.className = 'stageInspector'; 77 | 78 | return StageInspector; 79 | 80 | })(Spine.Controller); 81 | 82 | module.exports = StageInspector; 83 | 84 | }).call(this); 85 | ;}}); 86 | -------------------------------------------------------------------------------- /public/assets/app/controllers/inspector/text_style.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/inspector/text_style":function(exports, require, module){(function() { 62 | var TextStyle, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | TextStyle = (function(_super) { 67 | 68 | __extends(TextStyle, _super); 69 | 70 | TextStyle.name = 'TextStyle'; 71 | 72 | function TextStyle() { 73 | return TextStyle.__super__.constructor.apply(this, arguments); 74 | } 75 | 76 | TextStyle.prototype.className = 'textStyle'; 77 | 78 | return TextStyle; 79 | 80 | })(Spine.Controller); 81 | 82 | module.exports = TextStyle; 83 | 84 | }).call(this); 85 | ;}}); 86 | -------------------------------------------------------------------------------- /public/assets/app/controllers/stage/zindex.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/controllers/stage/zindex":function(exports, require, module){(function() { 62 | var Collection, ZIndex; 63 | 64 | Collection = require('lib/collection'); 65 | 66 | ZIndex = (function() { 67 | 68 | ZIndex.name = 'ZIndex'; 69 | 70 | function ZIndex(stage) { 71 | this.stage = stage; 72 | this.order = this.stage.elements; 73 | } 74 | 75 | ZIndex.prototype.bringForward = function(element) { 76 | var index; 77 | index = this.order.indexOf(element); 78 | if (index !== -1 || index !== (this.order.length - 1)) { 79 | this.order[index] = this.order[index + 1]; 80 | this.order[index + 1] = element; 81 | } 82 | return this.set(); 83 | }; 84 | 85 | ZIndex.prototype.bringBack = function(element) { 86 | var index; 87 | index = this.order.indexOf(element); 88 | if (index !== -1 || index !== 0) { 89 | this.order[index] = this.order[index - 1]; 90 | this.order[index - 1] = element; 91 | } 92 | return this.set(); 93 | }; 94 | 95 | ZIndex.prototype.bringToFront = function(element) { 96 | var index; 97 | index = this.order.indexOf(element); 98 | this.order.splice(index, 1); 99 | this.order.push(element); 100 | return this.set(); 101 | }; 102 | 103 | ZIndex.prototype.bringToBack = function(element) { 104 | var index; 105 | index = this.order.indexOf(element); 106 | this.order.splice(index, 1); 107 | this.order.unshift(element); 108 | return this.set(); 109 | }; 110 | 111 | ZIndex.prototype.set = function() { 112 | var element, index, _i, _len, _ref, _results; 113 | _ref = this.order; 114 | _results = []; 115 | for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) { 116 | element = _ref[index]; 117 | _results.push(element.order(index)); 118 | } 119 | return _results; 120 | }; 121 | 122 | return ZIndex; 123 | 124 | })(); 125 | 126 | module.exports = ZIndex; 127 | 128 | }).call(this); 129 | ;}}); 130 | -------------------------------------------------------------------------------- /public/assets/app/index.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/index":function(exports, require, module){(function() { 62 | var App, Header, Inspector, Stage, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Stage = require('./controllers/stage'); 67 | 68 | Header = require('./controllers/header'); 69 | 70 | Inspector = require('./controllers/inspector'); 71 | 72 | App = (function(_super) { 73 | 74 | __extends(App, _super); 75 | 76 | App.name = 'App'; 77 | 78 | App.prototype.className = 'app'; 79 | 80 | function App() { 81 | App.__super__.constructor.apply(this, arguments); 82 | this.stage = new Stage; 83 | this.header = new Header({ 84 | stage: this.stage 85 | }); 86 | this.inspector = new Inspector({ 87 | stage: this.stage 88 | }); 89 | this.append(this.header.render(), this.stage.render(), this.inspector.render()); 90 | } 91 | 92 | return App; 93 | 94 | })(Spine.Controller); 95 | 96 | module.exports = App; 97 | 98 | }).call(this); 99 | ;}}); 100 | -------------------------------------------------------------------------------- /public/assets/app/models/history.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/models/history":function(exports, require, module){(function() { 62 | var History; 63 | 64 | History = (function() { 65 | 66 | History.name = 'History'; 67 | 68 | function History() {} 69 | 70 | History.undoStack = []; 71 | 72 | History.redoStack = []; 73 | 74 | History.max = 50; 75 | 76 | History.add = function(action, isUndo) { 77 | var stack; 78 | if (isUndo === true) { 79 | stack = this.undoStack; 80 | } else if (isUndo === false) { 81 | stack = this.redoStack; 82 | } else { 83 | stack = this.undoStack; 84 | if (stack.length >= this.max) { 85 | stack.shift(); 86 | } 87 | this.redoStack = []; 88 | } 89 | return stack.push(action); 90 | }; 91 | 92 | History.undo = function() { 93 | var action; 94 | action = this.undoStack.pop(); 95 | if (action) { 96 | return action.call(this, true); 97 | } else { 98 | return false; 99 | } 100 | }; 101 | 102 | History.redo = function() { 103 | var action; 104 | action = this.redoStack.pop(); 105 | if (action) { 106 | return action.call(this, false); 107 | } else { 108 | return false; 109 | } 110 | }; 111 | 112 | History.clear = function() { 113 | this.undoStack = []; 114 | return this.redoStack = []; 115 | }; 116 | 117 | return History; 118 | 119 | })(); 120 | 121 | module.exports = History; 122 | 123 | }).call(this); 124 | ;}}); 125 | -------------------------------------------------------------------------------- /public/assets/app/models/properties.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/models/properties":function(exports, require, module){(function() { 62 | var Background, Property, Values; 63 | 64 | Property = require('./property'); 65 | 66 | Values = Property.Values; 67 | 68 | Background = require('./properties/background'); 69 | 70 | module.exports = { 71 | Property: Property, 72 | Values: Values, 73 | Background: Background 74 | }; 75 | 76 | }).call(this); 77 | ;}}); 78 | -------------------------------------------------------------------------------- /public/assets/app/models/properties/border.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/models/properties/border":function(exports, require, module){(function() { 62 | var Border, Color, Property, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Property = require('app/models/property'); 67 | 68 | Color = require('./color'); 69 | 70 | Border = (function(_super) { 71 | 72 | __extends(Border, _super); 73 | 74 | Border.name = 'Border'; 75 | 76 | Border.prototype.id = module.id; 77 | 78 | function Border(width, style, color) { 79 | this.width = width != null ? width : 0; 80 | this.style = style != null ? style : 'solid'; 81 | this.color = color != null ? color : new Color.Black; 82 | } 83 | 84 | Border.prototype.toString = function() { 85 | return [this.width + 'px', this.style, this.color].join(' '); 86 | }; 87 | 88 | Border.prototype.toValue = function() { 89 | return [this.width, this.style, this.color]; 90 | }; 91 | 92 | return Border; 93 | 94 | })(Property); 95 | 96 | module.exports = Border; 97 | 98 | }).call(this); 99 | ;}}); 100 | -------------------------------------------------------------------------------- /public/assets/app/models/properties/shadow.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/models/properties/shadow":function(exports, require, module){(function() { 62 | var Color, Property, Shadow, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Property = require('app/models/property'); 67 | 68 | Color = require('./color'); 69 | 70 | Shadow = (function(_super) { 71 | 72 | __extends(Shadow, _super); 73 | 74 | Shadow.name = 'Shadow'; 75 | 76 | Shadow.prototype.id = module.id; 77 | 78 | function Shadow(properties) { 79 | var k, v; 80 | if (properties == null) { 81 | properties = {}; 82 | } 83 | for (k in properties) { 84 | v = properties[k]; 85 | this[k] = v; 86 | } 87 | this.x || (this.x = 0); 88 | this.y || (this.y = 0); 89 | this.color || (this.color = new Color.Black(0.3)); 90 | } 91 | 92 | Shadow.prototype.toString = function() { 93 | var result; 94 | result = []; 95 | if (this.inset) { 96 | result.push('inset'); 97 | } 98 | result.push(this.x + 'px'); 99 | result.push(this.y + 'px'); 100 | if (this.blur != null) { 101 | result.push(this.blur + 'px'); 102 | } 103 | if (this.spread != null) { 104 | result.push(this.spread + 'px'); 105 | } 106 | result.push(this.color.toString()); 107 | return result.join(' '); 108 | }; 109 | 110 | Shadow.prototype.toValue = function() { 111 | var value; 112 | return value = { 113 | inset: this.inset, 114 | x: this.x, 115 | y: this.y, 116 | blur: this.blur, 117 | spread: this.spread, 118 | color: this.color 119 | }; 120 | }; 121 | 122 | return Shadow; 123 | 124 | })(Property); 125 | 126 | module.exports = Shadow; 127 | 128 | }).call(this); 129 | ;}}); 130 | -------------------------------------------------------------------------------- /public/assets/app/models/property.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/models/property":function(exports, require, module){(function() { 62 | var Collection, Property, Serialize, Values, 63 | __hasProp = {}.hasOwnProperty, 64 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; 65 | 66 | Collection = require('lib/collection'); 67 | 68 | Serialize = require('./serialize').Serialize; 69 | 70 | Values = (function(_super) { 71 | 72 | __extends(Values, _super); 73 | 74 | Values.name = 'Values'; 75 | 76 | function Values() { 77 | return Values.__super__.constructor.apply(this, arguments); 78 | } 79 | 80 | Values.prototype.toString = function() { 81 | return this.join(', '); 82 | }; 83 | 84 | return Values; 85 | 86 | })(Collection); 87 | 88 | Property = (function(_super) { 89 | 90 | __extends(Property, _super); 91 | 92 | Property.name = 'Property'; 93 | 94 | function Property() { 95 | return Property.__super__.constructor.apply(this, arguments); 96 | } 97 | 98 | Property.include(Serialize); 99 | 100 | Property.prototype.valueOf = function() { 101 | return this.toString(); 102 | }; 103 | 104 | return Property; 105 | 106 | })(Spine.Module); 107 | 108 | module.exports = Property; 109 | 110 | module.exports.Values = Values; 111 | 112 | }).call(this); 113 | ;}}); 114 | -------------------------------------------------------------------------------- /public/assets/app/parsers/background.pegjs: -------------------------------------------------------------------------------- 1 | URI "uri" 2 | = "url(" value:string / .* ")" 3 | 4 | px 5 | = num "px" 6 | 7 | num 8 | = float 9 | / integer 10 | 11 | integer 12 | = digits:[0-9]+ { return parseInt(digits.join(""), 10); } 13 | 14 | float 15 | = before:[0-9]* "." after:[0-9]+ { 16 | return parseFloat(before.join("") + "." + after.join("")); 17 | } 18 | 19 | string1 20 | = '"' chars:([^\n\r\f\\"] / "\\" nl:nl { return nl } / escape)* '"' { 21 | return chars.join(""); 22 | } 23 | 24 | string2 25 | = "'" chars:([^\n\r\f\\'] / "\\" nl:nl { return nl } / escape)* "'" { 26 | return chars.join(""); 27 | } 28 | 29 | string 30 | = string1 31 | / string2 32 | 33 | _ "whitespace" 34 | = whitespace* 35 | 36 | whitespace 37 | = [ \t\n\r] -------------------------------------------------------------------------------- /public/assets/app/parsers/color.pegjs: -------------------------------------------------------------------------------- 1 | // CSS Color parser 2 | 3 | start 4 | = hexcolor / rgba / shortcut 5 | 6 | hexcolor "hexcolor" 7 | = hex:(hexcolorshort / hexcolorlong) { 8 | var r = parseInt(hex.substring(0, 2), 16); 9 | var g = parseInt(hex.substring(2, 4), 16); 10 | var b = parseInt(hex.substring(4, 6), 16); 11 | 12 | var Color = require("app/models/properties/color"); 13 | return new Color(r, g, b); 14 | } 15 | 16 | hexcolorlong 17 | = "#" hexes:hex* { 18 | return hexes.join(''); 19 | } 20 | 21 | hexcolorshort 22 | = "#" h1:hex h2:hex h3:hex { 23 | return h1 + h1 + h2 + h2 + h3 + h3; 24 | } 25 | 26 | hex 27 | = [0-9a-fA-F] 28 | 29 | rgba "rgba" 30 | = ("rgb(" elements:elements ")" / "rgba(" elements:elements ")") { 31 | elements = elements.map(function(i){ return parseFloat(i) }); 32 | 33 | var Color = require('app/models/properties/color'); 34 | return new Color(elements[0], elements[1], elements[2], elements[3]); 35 | } 36 | 37 | shortcut "shortcut" 38 | = "red" / "tan" / "grey" / "gray" / "lime" / "navy" / "blue" / 39 | "teal" / "aqua" / "cyan" / "gold" / "peru" / "pink" / "plum" / "snow" 40 | 41 | // Utilities 42 | 43 | elements 44 | = head:.* tail:("," .*)* { 45 | var result = [head]; 46 | for (var i = 0; i < tail.length; i++) { 47 | result.push(tail[i][2]); 48 | } 49 | return result; 50 | } -------------------------------------------------------------------------------- /public/assets/app/parsers/import.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"app/parsers/import":function(exports, require, module){(function() { 62 | var importPage; 63 | 64 | importPage = function(tree) {}; 65 | 66 | module.exports = importPage; 67 | 68 | }).call(this); 69 | ;}}); 70 | -------------------------------------------------------------------------------- /public/assets/app/parsers/json.pegjs: -------------------------------------------------------------------------------- 1 | /* JSON parser based on the grammar described at http://json.org/. */ 2 | 3 | /* ===== Syntactical Elements ===== */ 4 | 5 | start 6 | = _ object:object { return object; } 7 | 8 | object 9 | = "{" _ "}" _ { return {}; } 10 | / "{" _ members:members "}" _ { return members; } 11 | 12 | members 13 | = head:pair tail:("," _ pair)* { 14 | var result = {}; 15 | result[head[0]] = head[1]; 16 | for (var i = 0; i < tail.length; i++) { 17 | result[tail[i][2][0]] = tail[i][2][1]; 18 | } 19 | return result; 20 | } 21 | 22 | pair 23 | = name:string ":" _ value:value { return [name, value]; } 24 | 25 | array 26 | = "[" _ "]" _ { return []; } 27 | / "[" _ elements:elements "]" _ { return elements; } 28 | 29 | elements 30 | = head:value tail:("," _ value)* { 31 | var result = [head]; 32 | for (var i = 0; i < tail.length; i++) { 33 | result.push(tail[i][2]); 34 | } 35 | return result; 36 | } 37 | 38 | value 39 | = string 40 | / number 41 | / object 42 | / array 43 | / "true" _ { return true; } 44 | / "false" _ { return false; } 45 | // FIXME: We can't return null here because that would mean parse failure. 46 | / "null" _ { return "null"; } 47 | 48 | /* ===== Lexical Elements ===== */ 49 | 50 | string "string" 51 | = '"' '"' _ { return ""; } 52 | / '"' chars:chars '"' _ { return chars; } 53 | 54 | chars 55 | = chars:char+ { return chars.join(""); } 56 | 57 | char 58 | // In the original JSON grammar: "any-Unicode-character-except-"-or-\-or-control-character" 59 | = [^"\\\0-\x1F\x7f] 60 | / '\\"' { return '"'; } 61 | / "\\\\" { return "\\"; } 62 | / "\\/" { return "/"; } 63 | / "\\b" { return "\b"; } 64 | / "\\f" { return "\f"; } 65 | / "\\n" { return "\n"; } 66 | / "\\r" { return "\r"; } 67 | / "\\t" { return "\t"; } 68 | / "\\u" h1:hexDigit h2:hexDigit h3:hexDigit h4:hexDigit { 69 | return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4)); 70 | } 71 | 72 | number "number" 73 | = int_:int frac:frac exp:exp _ { return parseFloat(int_ + frac + exp); } 74 | / int_:int frac:frac _ { return parseFloat(int_ + frac); } 75 | / int_:int exp:exp _ { return parseFloat(int_ + exp); } 76 | / int_:int _ { return parseFloat(int_); } 77 | 78 | int 79 | = digit19:digit19 digits:digits { return digit19 + digits; } 80 | / digit:digit 81 | / "-" digit19:digit19 digits:digits { return "-" + digit19 + digits; } 82 | / "-" digit:digit { return "-" + digit; } 83 | 84 | frac 85 | = "." digits:digits { return "." + digits; } 86 | 87 | exp 88 | = e:e digits:digits { return e + digits; } 89 | 90 | digits 91 | = digits:digit+ { return digits.join(""); } 92 | 93 | e 94 | = e:[eE] sign:[+-]? { return e + sign; } 95 | 96 | /* 97 | * The following rules are not present in the original JSON gramar, but they are 98 | * assumed to exist implicitly. 99 | * 100 | * FIXME: Define them according to ECMA-262, 5th ed. 101 | */ 102 | 103 | digit 104 | = [0-9] 105 | 106 | digit19 107 | = [1-9] 108 | 109 | hexDigit 110 | = [0-9a-fA-F] 111 | 112 | /* ===== Whitespace ===== */ 113 | 114 | _ "whitespace" 115 | = whitespace* 116 | 117 | // Whitespace is undefined in the original JSON grammar, so I assume a simple 118 | // conventional definition consistent with ECMA-262, 5th ed. 119 | whitespace 120 | = [ \t\n\r] -------------------------------------------------------------------------------- /public/assets/app/parsers/shadow.pegjs: -------------------------------------------------------------------------------- 1 | // CSS Shadow parser 2 | 3 | start 4 | = inset:"inset"? color:color (values:px _ *) { 5 | var Shadow = require('app/models/properties/shadow'); 6 | var props = {}; 7 | props.inset = !!inset; 8 | props.x = values[0]; 9 | props.y = values[1]; 10 | props.blur = values[2]; 11 | props.spread = values[3]; 12 | return new Shadow(options); 13 | } 14 | 15 | px 16 | = num "px" 17 | 18 | num 19 | = float 20 | / integer 21 | 22 | integer 23 | = digits:[0-9]+ { return parseInt(digits.join(""), 10); } 24 | 25 | float 26 | = before:[0-9]* "." after:[0-9]+ { 27 | return parseFloat(before.join("") + "." + after.join("")); 28 | } 29 | 30 | _ "whitespace" 31 | = whitespace* 32 | 33 | whitespace 34 | = [ \t\n\r] -------------------------------------------------------------------------------- /public/assets/bg-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/bg-grid.png -------------------------------------------------------------------------------- /public/assets/blacky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/blacky.png -------------------------------------------------------------------------------- /public/assets/crosshairs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/crosshairs.png -------------------------------------------------------------------------------- /public/assets/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/grid.png -------------------------------------------------------------------------------- /public/assets/lib/utils.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | if (!this.require) { 3 | var modules = {}, cache = {}; 4 | 5 | var require = function(name, root) { 6 | var path = expand(root, name), indexPath = expand(path, './index'), module, fn; 7 | module = cache[path] || cache[indexPath]; 8 | if (module) { 9 | return module; 10 | } else if (fn = modules[path] || modules[path = indexPath]) { 11 | module = {id: path, exports: {}}; 12 | cache[path] = module.exports; 13 | fn(module.exports, function(name) { 14 | return require(name, dirname(path)); 15 | }, module); 16 | return cache[path] = module.exports; 17 | } else { 18 | throw 'module ' + name + ' not found'; 19 | } 20 | }; 21 | 22 | var expand = function(root, name) { 23 | var results = [], parts, part; 24 | // If path is relative 25 | if (/^\.\.?(\/|$)/.test(name)) { 26 | parts = [root, name].join('/').split('/'); 27 | } else { 28 | parts = name.split('/'); 29 | } 30 | for (var i = 0, length = parts.length; i < length; i++) { 31 | part = parts[i]; 32 | if (part == '..') { 33 | results.pop(); 34 | } else if (part != '.' && part != '') { 35 | results.push(part); 36 | } 37 | } 38 | return results.join('/'); 39 | }; 40 | 41 | var dirname = function(path) { 42 | return path.split('/').slice(0, -1).join('/'); 43 | }; 44 | 45 | this.require = function(name) { 46 | return require(name, ''); 47 | }; 48 | 49 | this.require.define = function(bundle) { 50 | for (var key in bundle) { 51 | modules[key] = bundle[key]; 52 | } 53 | }; 54 | 55 | this.require.modules = modules; 56 | this.require.cache = cache; 57 | } 58 | 59 | return this.require; 60 | }).call(this); 61 | this.require.define({"lib/utils":function(exports, require, module){(function() { 62 | var dasherize, requestAnimationFrame; 63 | 64 | dasherize = function(str) { 65 | return str.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2').replace(/([a-z\d])([A-Z])/g, '$1-$2').toLowerCase(); 66 | }; 67 | 68 | $.browser.chrome = /chrome/.test(navigator.userAgent.toLowerCase()); 69 | 70 | requestAnimationFrame = (function() { 71 | var request; 72 | request = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { 73 | return window.setTimeout(callback, 1000 / 60); 74 | }; 75 | return function(callback) { 76 | return request.call(window, callback); 77 | }; 78 | })(); 79 | 80 | module.exports = { 81 | dasherize: dasherize, 82 | browser: $.browser, 83 | requestAnimationFrame: requestAnimationFrame 84 | }; 85 | 86 | }).call(this); 87 | ;}}); 88 | -------------------------------------------------------------------------------- /public/assets/multiple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/multiple.png -------------------------------------------------------------------------------- /public/assets/parsers/background.pegjs: -------------------------------------------------------------------------------- 1 | URI "uri" 2 | = "url(" value:string / .* ")" 3 | 4 | px 5 | = num "px" 6 | 7 | num 8 | = float 9 | / integer 10 | 11 | integer 12 | = digits:[0-9]+ { return parseInt(digits.join(""), 10); } 13 | 14 | float 15 | = before:[0-9]* "." after:[0-9]+ { 16 | return parseFloat(before.join("") + "." + after.join("")); 17 | } 18 | 19 | string1 20 | = '"' chars:([^\n\r\f\\"] / "\\" nl:nl { return nl } / escape)* '"' { 21 | return chars.join(""); 22 | } 23 | 24 | string2 25 | = "'" chars:([^\n\r\f\\'] / "\\" nl:nl { return nl } / escape)* "'" { 26 | return chars.join(""); 27 | } 28 | 29 | string 30 | = string1 31 | / string2 32 | 33 | _ "whitespace" 34 | = whitespace* 35 | 36 | whitespace 37 | = [ \t\n\r] -------------------------------------------------------------------------------- /public/assets/parsers/color.pegjs: -------------------------------------------------------------------------------- 1 | // CSS Color parser 2 | 3 | start 4 | = hexcolor / rgba / shortcut 5 | 6 | hexcolor "hexcolor" 7 | = hex:(hexcolorshort / hexcolorlong) { 8 | var r = parseInt(hex.substring(0, 2), 16); 9 | var g = parseInt(hex.substring(2, 4), 16); 10 | var b = parseInt(hex.substring(4, 6), 16); 11 | 12 | var Color = require("app/models/properties/color"); 13 | return new Color(r, g, b); 14 | } 15 | 16 | hexcolorlong 17 | = "#" hexes:hex* { 18 | return hexes.join(''); 19 | } 20 | 21 | hexcolorshort 22 | = "#" h1:hex h2:hex h3:hex { 23 | return h1 + h1 + h2 + h2 + h3 + h3; 24 | } 25 | 26 | hex 27 | = [0-9a-fA-F] 28 | 29 | rgba "rgba" 30 | = ("rgb(" elements:elements ")" / "rgba(" elements:elements ")") { 31 | elements = elements.map(function(i){ return parseFloat(i) }); 32 | 33 | var Color = require('app/models/properties/color'); 34 | return new Color(elements[0], elements[1], elements[2], elements[3]); 35 | } 36 | 37 | shortcut "shortcut" 38 | = "red" / "tan" / "grey" / "gray" / "lime" / "navy" / "blue" / 39 | "teal" / "aqua" / "cyan" / "gold" / "peru" / "pink" / "plum" / "snow" 40 | 41 | // Utilities 42 | 43 | elements 44 | = head:.* tail:("," .*)* { 45 | var result = [head]; 46 | for (var i = 0; i < tail.length; i++) { 47 | result.push(tail[i][2]); 48 | } 49 | return result; 50 | } -------------------------------------------------------------------------------- /public/assets/parsers/gradient.pegjs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/parsers/gradient.pegjs -------------------------------------------------------------------------------- /public/assets/parsers/json.pegjs: -------------------------------------------------------------------------------- 1 | /* JSON parser based on the grammar described at http://json.org/. */ 2 | 3 | /* ===== Syntactical Elements ===== */ 4 | 5 | start 6 | = _ object:object { return object; } 7 | 8 | object 9 | = "{" _ "}" _ { return {}; } 10 | / "{" _ members:members "}" _ { return members; } 11 | 12 | members 13 | = head:pair tail:("," _ pair)* { 14 | var result = {}; 15 | result[head[0]] = head[1]; 16 | for (var i = 0; i < tail.length; i++) { 17 | result[tail[i][2][0]] = tail[i][2][1]; 18 | } 19 | return result; 20 | } 21 | 22 | pair 23 | = name:string ":" _ value:value { return [name, value]; } 24 | 25 | array 26 | = "[" _ "]" _ { return []; } 27 | / "[" _ elements:elements "]" _ { return elements; } 28 | 29 | elements 30 | = head:value tail:("," _ value)* { 31 | var result = [head]; 32 | for (var i = 0; i < tail.length; i++) { 33 | result.push(tail[i][2]); 34 | } 35 | return result; 36 | } 37 | 38 | value 39 | = string 40 | / number 41 | / object 42 | / array 43 | / "true" _ { return true; } 44 | / "false" _ { return false; } 45 | // FIXME: We can't return null here because that would mean parse failure. 46 | / "null" _ { return "null"; } 47 | 48 | /* ===== Lexical Elements ===== */ 49 | 50 | string "string" 51 | = '"' '"' _ { return ""; } 52 | / '"' chars:chars '"' _ { return chars; } 53 | 54 | chars 55 | = chars:char+ { return chars.join(""); } 56 | 57 | char 58 | // In the original JSON grammar: "any-Unicode-character-except-"-or-\-or-control-character" 59 | = [^"\\\0-\x1F\x7f] 60 | / '\\"' { return '"'; } 61 | / "\\\\" { return "\\"; } 62 | / "\\/" { return "/"; } 63 | / "\\b" { return "\b"; } 64 | / "\\f" { return "\f"; } 65 | / "\\n" { return "\n"; } 66 | / "\\r" { return "\r"; } 67 | / "\\t" { return "\t"; } 68 | / "\\u" h1:hexDigit h2:hexDigit h3:hexDigit h4:hexDigit { 69 | return String.fromCharCode(parseInt("0x" + h1 + h2 + h3 + h4)); 70 | } 71 | 72 | number "number" 73 | = int_:int frac:frac exp:exp _ { return parseFloat(int_ + frac + exp); } 74 | / int_:int frac:frac _ { return parseFloat(int_ + frac); } 75 | / int_:int exp:exp _ { return parseFloat(int_ + exp); } 76 | / int_:int _ { return parseFloat(int_); } 77 | 78 | int 79 | = digit19:digit19 digits:digits { return digit19 + digits; } 80 | / digit:digit 81 | / "-" digit19:digit19 digits:digits { return "-" + digit19 + digits; } 82 | / "-" digit:digit { return "-" + digit; } 83 | 84 | frac 85 | = "." digits:digits { return "." + digits; } 86 | 87 | exp 88 | = e:e digits:digits { return e + digits; } 89 | 90 | digits 91 | = digits:digit+ { return digits.join(""); } 92 | 93 | e 94 | = e:[eE] sign:[+-]? { return e + sign; } 95 | 96 | /* 97 | * The following rules are not present in the original JSON gramar, but they are 98 | * assumed to exist implicitly. 99 | * 100 | * FIXME: Define them according to ECMA-262, 5th ed. 101 | */ 102 | 103 | digit 104 | = [0-9] 105 | 106 | digit19 107 | = [1-9] 108 | 109 | hexDigit 110 | = [0-9a-fA-F] 111 | 112 | /* ===== Whitespace ===== */ 113 | 114 | _ "whitespace" 115 | = whitespace* 116 | 117 | // Whitespace is undefined in the original JSON grammar, so I assume a simple 118 | // conventional definition consistent with ECMA-262, 5th ed. 119 | whitespace 120 | = [ \t\n\r] -------------------------------------------------------------------------------- /public/assets/parsers/shadow.peg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/parsers/shadow.peg -------------------------------------------------------------------------------- /public/assets/parsers/shadow.pegjs: -------------------------------------------------------------------------------- 1 | // CSS Shadow parser 2 | 3 | start 4 | = inset:"inset"? color:color (values:px _ *) { 5 | var Shadow = require('app/models/properties/shadow'); 6 | var props = {}; 7 | props.inset = !!inset; 8 | props.x = values[0]; 9 | props.y = values[1]; 10 | props.blur = values[2]; 11 | props.spread = values[3]; 12 | return new Shadow(options); 13 | } 14 | 15 | px 16 | = num "px" 17 | 18 | num 19 | = float 20 | / integer 21 | 22 | integer 23 | = digits:[0-9]+ { return parseInt(digits.join(""), 10); } 24 | 25 | float 26 | = before:[0-9]* "." after:[0-9]+ { 27 | return parseFloat(before.join("") + "." + after.join("")); 28 | } 29 | 30 | _ "whitespace" 31 | = whitespace* 32 | 33 | whitespace 34 | = [ \t\n\r] -------------------------------------------------------------------------------- /public/assets/sprockets/commonjs.rb: -------------------------------------------------------------------------------- 1 | require 'sprockets' 2 | require 'tilt' 3 | 4 | module Sprockets 5 | class CommonJS < Tilt::Template 6 | self.default_mime_type = 'application/javascript' 7 | 8 | def self.default_namespace 9 | 'this.require' 10 | end 11 | 12 | def prepare 13 | @namespace = self.class.default_namespace 14 | end 15 | 16 | attr_reader :namespace 17 | 18 | def evaluate(scope, locals, &block) 19 | if File.extname(scope.logical_path) == '.module' 20 | path = scope.logical_path.chomp('.module').inspect 21 | 22 | scope.require_asset 'sprockets/commonjs' 23 | 24 | code = '' 25 | code << "#{namespace}.define({#{path}:" 26 | code << 'function(exports, require, module){' 27 | code << data 28 | code << ";}});\n" 29 | code 30 | else 31 | data 32 | end 33 | end 34 | end 35 | 36 | register_postprocessor 'application/javascript', CommonJS 37 | append_path File.expand_path('../..', __FILE__) 38 | end -------------------------------------------------------------------------------- /public/assets/whitey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/stylo/71aecf096e4ebad05404d5a659e2ac9caae94f75/public/assets/whitey.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Stylo 5 | 6 | 7 | 8 | 9 |
10 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/gfx.coffee: -------------------------------------------------------------------------------- 1 | $ = jQuery ? require('jqueryify') 2 | 3 | throw 'jQuery required' unless $ 4 | 5 | $.support.transition or= do -> 6 | style = (new Image).style 7 | 'transition' of style or 8 | 'webkitTransition' of style or 9 | 'MozTransition' of style 10 | 11 | vendor = if $.browser.mozilla then 'moz' 12 | vendor or= 'webkit' 13 | prefix = "-#{vendor}-" 14 | 15 | vendorNames = n = 16 | transition: "#{prefix}transition" 17 | transform: "#{prefix}transform" 18 | transitionEnd: "#{vendor}TransitionEnd" 19 | 20 | defaults = 21 | duration: 400 22 | queue: true 23 | easing: '' 24 | enabled: $.support.transition 25 | 26 | transformTypes = [ 27 | 'scale', 'scaleX', 'scaleY', 'scale3d', 28 | 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'rotate3d', 29 | 'translate', 'translateX', 'translateY', 'translateZ', 'translate3d', 30 | 'skew', 'skewX', 'skewY', 31 | 'matrix', 'matrix3d', 'perspective' 32 | ] 33 | 34 | # Internal helper functions 35 | 36 | $.fn.queueNext = (callback, type) -> 37 | type or= "fx"; 38 | 39 | @queue -> 40 | callback.apply(this, arguments) 41 | redraw = this.offsetHeight 42 | jQuery.dequeue(this, type) 43 | 44 | $.fn.emulateTransitionEnd = (duration) -> 45 | called = false 46 | $(@).one(n.transitionEnd, -> called = true) 47 | callback = => $(@).trigger(n.transitionEnd) unless called 48 | setTimeout(callback, duration) 49 | 50 | # Helper function for easily adding transforms 51 | 52 | $.fn.transform = (properties, options) -> 53 | opts = $.extend({}, defaults, options) 54 | return this unless opts.enabled 55 | 56 | transforms = [] 57 | 58 | for key, value of properties when key in transformTypes 59 | transforms.push("#{key}(#{value})") 60 | delete properties[key] 61 | 62 | if transforms.length 63 | properties[n.transform] = transforms.join(' ') 64 | 65 | if opts.origin 66 | properties["#{prefix}transform-origin"] = opts.origin 67 | 68 | $(@).css(properties) 69 | 70 | $.fn.gfx = (properties, options) -> 71 | opts = $.extend({}, defaults, options) 72 | return this unless opts.enabled 73 | 74 | properties[n.transition] = "all #{opts.duration}ms #{opts.easing}" 75 | 76 | callback = -> 77 | $(@).css(n.transition, '') 78 | opts.complete?.apply(this, arguments) 79 | $(@).dequeue() 80 | 81 | @[ if opts.queue is false then 'each' else 'queue' ] -> 82 | $(@).one(n.transitionEnd, callback) 83 | $(@).transform(properties) 84 | 85 | # Sometimes the event doesn't fire, so we have to fire it manually 86 | $(@).emulateTransitionEnd(opts.duration + 50) -------------------------------------------------------------------------------- /vendor/assets/javascripts/spine/list.coffee: -------------------------------------------------------------------------------- 1 | Spine = @Spine or require('spine') 2 | $ = Spine.$ 3 | 4 | class Spine.List extends Spine.Controller 5 | events: 6 | 'click .item': 'click' 7 | 8 | selectFirst: false 9 | 10 | constructor: -> 11 | super 12 | @bind 'change', @change 13 | 14 | template: -> 15 | throw 'Override template' 16 | 17 | change: (item) => 18 | @current = item 19 | 20 | unless @current 21 | @children().removeClass('active') 22 | return 23 | 24 | @children().removeClass('active') 25 | $(@children().get(@items.indexOf(@current))).addClass('active') 26 | 27 | render: (items) -> 28 | @items = items if items 29 | @html @template(@items) 30 | @change @current 31 | if @selectFirst 32 | unless @children('.active').length 33 | @children(':first').click() 34 | 35 | children: (sel) -> 36 | @el.children(sel) 37 | 38 | click: (e) -> 39 | item = @items[$(e.currentTarget).index()] 40 | @trigger('change', item) 41 | true 42 | 43 | module?.exports = Spine.List -------------------------------------------------------------------------------- /vendor/assets/javascripts/spine/local.coffee: -------------------------------------------------------------------------------- 1 | Spine = @Spine or require('spine') 2 | 3 | Spine.Model.Local = 4 | extended: -> 5 | @change @saveLocal 6 | @fetch @loadLocal 7 | 8 | saveLocal: -> 9 | result = JSON.stringify(@) 10 | localStorage[@className] = result 11 | 12 | loadLocal: -> 13 | result = localStorage[@className] 14 | @refresh(result or [], clear: true) 15 | 16 | module?.exports = Spine.Model.Local -------------------------------------------------------------------------------- /vendor/assets/javascripts/spine/manager.coffee: -------------------------------------------------------------------------------- 1 | Spine = @Spine or require('spine') 2 | $ = Spine.$ 3 | 4 | class Spine.Manager extends Spine.Module 5 | @include Spine.Events 6 | 7 | constructor: -> 8 | @controllers = [] 9 | @bind 'change', @change 10 | @add(arguments...) 11 | 12 | add: (controllers...) -> 13 | @addOne(cont) for cont in controllers 14 | 15 | addOne: (controller) -> 16 | controller.bind 'active', (args...) => 17 | @trigger('change', controller, args...) 18 | controller.bind 'release', => 19 | @controllers.splice(@controllers.indexOf(controller), 1) 20 | 21 | @controllers.push(controller) 22 | 23 | deactivate: -> 24 | @trigger('change', false, arguments...) 25 | 26 | # Private 27 | 28 | change: (@current, args...) -> 29 | for cont in @controllers 30 | if cont is @current 31 | cont.activate(args...) 32 | else 33 | cont.deactivate(args...) 34 | 35 | Spine.Controller.include 36 | active: (args...) -> 37 | if typeof args[0] is 'function' 38 | @bind('active', args[0]) 39 | else 40 | args.unshift('active') 41 | @trigger(args...) 42 | this 43 | 44 | isActive: -> 45 | @el.hasClass('active') 46 | 47 | activate: -> 48 | @el.addClass('active') 49 | this 50 | 51 | deactivate: -> 52 | @el.removeClass('active') 53 | this 54 | 55 | class Spine.Stack extends Spine.Controller 56 | controllers: {} 57 | routes: {} 58 | 59 | className: 'spine stack' 60 | 61 | constructor: -> 62 | super 63 | 64 | @manager = new Spine.Manager 65 | 66 | for key, value of @controllers 67 | @[key] = new value(stack: @) 68 | @add(@[key]) 69 | 70 | for key, value of @routes 71 | do (key, value) => 72 | callback = value if typeof value is 'function' 73 | callback or= => @[value].active(arguments...) 74 | @route(key, callback) 75 | 76 | @[@default].active() if @default 77 | 78 | add: (controller) -> 79 | @manager.add(controller) 80 | @append(controller) 81 | 82 | module?.exports = Spine.Manager -------------------------------------------------------------------------------- /vendor/assets/javascripts/spine/relation.coffee: -------------------------------------------------------------------------------- 1 | Spine = @Spine or require('spine') 2 | isArray = Spine.isArray 3 | require = @require or ((value) -> eval(value)) 4 | 5 | class Collection extends Spine.Module 6 | constructor: (options = {}) -> 7 | for key, value of options 8 | @[key] = value 9 | 10 | all: -> 11 | @model.select (rec) => @associated(rec) 12 | 13 | first: -> 14 | @all()[0] 15 | 16 | last: -> 17 | values = @all() 18 | values[values.length - 1] 19 | 20 | find: (id) -> 21 | records = @select (rec) => 22 | rec.id + '' is id + '' 23 | throw('Unknown record') unless records[0] 24 | records[0] 25 | 26 | findAllByAttribute: (name, value) -> 27 | @model.select (rec) => 28 | @associated(rec) and rec[name] is value 29 | 30 | findByAttribute: (name, value) -> 31 | @findAllByAttribute(name, value)[0] 32 | 33 | select: (cb) -> 34 | @model.select (rec) => 35 | @associated(rec) and cb(rec) 36 | 37 | refresh: (values) -> 38 | delete @model.records[record.id] for record in @all() 39 | records = @model.fromJSON(values) 40 | 41 | records = [records] unless isArray(records) 42 | 43 | for record in records 44 | record.newRecord = false 45 | record[@fkey] = @record.id 46 | @model.records[record.id] = record 47 | 48 | @model.trigger('refresh', @model.cloneArray(records)) 49 | 50 | create: (record) -> 51 | record[@fkey] = @record.id 52 | @model.create(record) 53 | 54 | # Private 55 | 56 | associated: (record) -> 57 | record[@fkey] is @record.id 58 | 59 | class Instance extends Spine.Module 60 | constructor: (options = {}) -> 61 | for key, value of options 62 | @[key] = value 63 | 64 | exists: -> 65 | @record[@fkey] and @model.exists(@record[@fkey]) 66 | 67 | update: (value) -> 68 | unless value instanceof @model 69 | value = new @model(value) 70 | value.save() if value.isNew() 71 | @record[@fkey] = value and value.id 72 | 73 | class Singleton extends Spine.Module 74 | constructor: (options = {}) -> 75 | for key, value of options 76 | @[key] = value 77 | 78 | find: -> 79 | @record.id and @model.findByAttribute(@fkey, @record.id) 80 | 81 | update: (value) -> 82 | unless value instanceof @model 83 | value = @model.fromJSON(value) 84 | 85 | value[@fkey] = @record.id 86 | value.save() 87 | 88 | singularize = (str) -> 89 | str.replace(/s$/, '') 90 | 91 | underscore = (str) -> 92 | str.replace(/::/g, '/') 93 | .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') 94 | .replace(/([a-z\d])([A-Z])/g, '$1_$2') 95 | .replace(/-/g, '_') 96 | .toLowerCase() 97 | 98 | Spine.Model.extend 99 | hasMany: (name, model, fkey) -> 100 | fkey ?= "#{underscore(this.className)}_id" 101 | 102 | association = (record) -> 103 | model = require(model) if typeof model is 'string' 104 | 105 | new Collection( 106 | name: name, model: model, 107 | record: record, fkey: fkey 108 | ) 109 | 110 | @::[name] = (value) -> 111 | association(@).refresh(value) if value? 112 | association(@) 113 | 114 | belongsTo: (name, model, fkey) -> 115 | fkey ?= "#{singularize(name)}_id" 116 | 117 | association = (record) -> 118 | model = require(model) if typeof model is 'string' 119 | 120 | new Instance( 121 | name: name, model: model, 122 | record: record, fkey: fkey 123 | ) 124 | 125 | @::[name] = (value) -> 126 | association(@).update(value) if value? 127 | association(@).exists() 128 | 129 | @attributes.push(fkey) 130 | 131 | hasOne: (name, model, fkey) -> 132 | fkey ?= "#{underscore(@className)}_id" 133 | 134 | association = (record) -> 135 | model = require(model) if typeof model is 'string' 136 | 137 | new Singleton( 138 | name: name, model: model, 139 | record: record, fkey: fkey 140 | ) 141 | 142 | @::[name] = (value) -> 143 | association(@).update(value) if value? 144 | association(@).find() -------------------------------------------------------------------------------- /vendor/assets/javascripts/spine/route.coffee: -------------------------------------------------------------------------------- 1 | Spine = @Spine or require('spine') 2 | $ = Spine.$ 3 | 4 | hashStrip = /^#*/ 5 | namedParam = /:([\w\d]+)/g 6 | splatParam = /\*([\w\d]+)/g 7 | escapeRegExp = /[-[\]{}()+?.,\\^$|#\s]/g 8 | 9 | class Spine.Route extends Spine.Module 10 | @extend Spine.Events 11 | 12 | @historySupport: window.history?.pushState? 13 | 14 | @routes: [] 15 | 16 | @options: 17 | trigger: true 18 | history: false 19 | shim: false 20 | 21 | @add: (path, callback) -> 22 | if (typeof path is 'object' and path not instanceof RegExp) 23 | @add(key, value) for key, value of path 24 | else 25 | @routes.push(new @(path, callback)) 26 | 27 | @setup: (options = {}) -> 28 | @options = $.extend({}, @options, options) 29 | 30 | if (@options.history) 31 | @history = @historySupport && @options.history 32 | 33 | return if @options.shim 34 | 35 | if @history 36 | $(window).bind('popstate', @change) 37 | else 38 | $(window).bind('hashchange', @change) 39 | @change() 40 | 41 | @unbind: -> 42 | if @history 43 | $(window).unbind('popstate', @change) 44 | else 45 | $(window).unbind('hashchange', @change) 46 | 47 | @navigate: (args...) -> 48 | options = {} 49 | 50 | lastArg = args[args.length - 1] 51 | if typeof lastArg is 'object' 52 | options = args.pop() 53 | else if typeof lastArg is 'boolean' 54 | options.trigger = args.pop() 55 | 56 | options = $.extend({}, @options, options) 57 | 58 | path = args.join('/') 59 | return if @path is path 60 | @path = path 61 | 62 | @trigger('navigate', @path) 63 | 64 | @matchRoute(@path, options) if options.trigger 65 | 66 | return if options.shim 67 | 68 | if @history 69 | history.pushState( 70 | {}, 71 | document.title, 72 | @path 73 | ) 74 | else 75 | window.location.hash = @path 76 | 77 | # Private 78 | 79 | @getPath: -> 80 | path = window.location.pathname 81 | if path.substr(0,1) isnt '/' 82 | path = '/' + path 83 | path 84 | 85 | @getHash: -> window.location.hash 86 | 87 | @getFragment: -> @getHash().replace(hashStrip, '') 88 | 89 | @getHost: -> 90 | (document.location + '').replace(@getPath() + @getHash(), '') 91 | 92 | @change: -> 93 | path = if @getFragment() isnt '' then @getFragment() else @getPath() 94 | return if path is @path 95 | @path = path 96 | @matchRoute(@path) 97 | 98 | @matchRoute: (path, options) -> 99 | for route in @routes 100 | if route.match(path, options) 101 | @trigger('change', route, path) 102 | return route 103 | 104 | constructor: (@path, @callback) -> 105 | @names = [] 106 | 107 | if typeof path is 'string' 108 | namedParam.lastIndex = 0 109 | while (match = namedParam.exec(path)) != null 110 | @names.push(match[1]) 111 | 112 | path = path.replace(escapeRegExp, '\\$&') 113 | .replace(namedParam, '([^\/]*)') 114 | .replace(splatParam, '(.*?)') 115 | 116 | @route = new RegExp('^' + path + '$') 117 | else 118 | @route = path 119 | 120 | match: (path, options = {}) -> 121 | match = @route.exec(path) 122 | return false unless match 123 | options.match = match 124 | params = match.slice(1) 125 | 126 | if @names.length 127 | for param, i in params 128 | options[@names[i]] = param 129 | 130 | @callback.call(null, options) isnt false 131 | 132 | # Coffee-script bug 133 | Spine.Route.change = Spine.Route.proxy(Spine.Route.change) 134 | 135 | Spine.Controller.include 136 | route: (path, callback) -> 137 | Spine.Route.add(path, @proxy(callback)) 138 | 139 | routes: (routes) -> 140 | @route(key, value) for key, value of routes 141 | 142 | navigate: -> 143 | Spine.Route.navigate.apply(Spine.Route, arguments) 144 | 145 | module?.exports = Spine.Route -------------------------------------------------------------------------------- /vendor/assets/javascripts/spine/tabs.coffee: -------------------------------------------------------------------------------- 1 | Spine ?= require('spine') 2 | $ = Spine.$ 3 | 4 | class Spine.Tabs extends Spine.Controller 5 | events: 6 | 'click [data-name]': 'click' 7 | 8 | constructor: -> 9 | super 10 | @bind 'change', @change 11 | 12 | change: (name) => 13 | return unless name 14 | @current = name 15 | @children().removeClass('active') 16 | @children("[data-name=#{@current}]").addClass('active') 17 | 18 | render: -> 19 | @change @current 20 | unless @children('.active').length or @current 21 | @children(':first').click() 22 | 23 | children: (sel) -> 24 | @el.children(sel) 25 | 26 | click: (e) -> 27 | name = $(e.currentTarget).attr('data-name') 28 | @trigger('change', name) 29 | 30 | connect: (tabName, controller) -> 31 | @bind 'change', (name) -> 32 | controller.active() if name is tabName 33 | controller.bind 'active', => 34 | @change tabName 35 | 36 | module?.exports = Spine.Tabs 37 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/spine/tmpl.coffee: -------------------------------------------------------------------------------- 1 | # jQuery.tmpl.js utilities 2 | 3 | $ = jQuery ? require("jqueryify") 4 | 5 | $.fn.item = -> 6 | item = $(@) 7 | item = item.data("item") or item.tmplItem?().data 8 | item?.reload?() 9 | item 10 | 11 | $.fn.forItem = (item) -> 12 | @filter -> 13 | compare = $(@).item() 14 | return item.eql?(compare) or item is compare 15 | 16 | --------------------------------------------------------------------------------