├── examples
└── simple-test
│ ├── .gitignore
│ ├── packages
│ └── tutorials
│ ├── .meteor
│ ├── release
│ ├── identifier
│ ├── platforms
│ ├── .gitignore
│ ├── .id
│ ├── packages
│ ├── .finished-upgraders
│ └── versions
│ ├── client
│ ├── testtut.css
│ ├── testtut.js
│ └── testtut.html
│ └── readme.md
├── docs
├── highlight_1.png
└── highlight_2.png
├── .gitignore
├── tutorial.styl
├── package.js
├── eventEmitter.coffee
├── LICENSE
├── templates.html
├── helpers.coffee
├── drags.js
├── History.md
├── README.md
└── tutorial.coffee
/examples/simple-test/.gitignore:
--------------------------------------------------------------------------------
1 | private/
2 |
--------------------------------------------------------------------------------
/examples/simple-test/packages/tutorials:
--------------------------------------------------------------------------------
1 | ../..
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.4.4
2 |
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/identifier:
--------------------------------------------------------------------------------
1 | 1w1mmd91be9fkz1cwtb4h
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | dev_bundle
2 | local
3 |
--------------------------------------------------------------------------------
/docs/highlight_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TurkServer/meteor-tutorials/HEAD/docs/highlight_1.png
--------------------------------------------------------------------------------
/docs/highlight_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TurkServer/meteor-tutorials/HEAD/docs/highlight_2.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .versions
2 |
3 | lib-cov
4 | *.seed
5 | *.log
6 | *.csv
7 | *.dat
8 | *.out
9 | *.pid
10 | *.gz
11 |
12 | pids
13 | logs
14 | results
15 |
16 | npm-debug.log
17 | node_modules
18 | .build*
19 |
--------------------------------------------------------------------------------
/examples/simple-test/client/testtut.css:
--------------------------------------------------------------------------------
1 | /* CSS declarations go here */
2 |
3 | body {
4 | padding-top: 40px;
5 | font-family: 'Helvetica Neue', 'Helvetica', 'Lucida Grande', 'Arial', sans-serif !important;
6 | }
7 |
8 | * {
9 | box-sizing: border-box;
10 | }
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 58fgs4176k3cv15hcw3x
8 |
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 |
6 | meteor
7 | webapp
8 | spacebars
9 | check
10 | tracker
11 | templating
12 | underscore
13 | session
14 | standard-minifier-css
15 | standard-minifier-js
16 |
17 | # This pulls in DDP/mongo
18 | # hot-code-push
19 |
20 | mizzao:tutorials
21 | iron:router
22 | twbs:bootstrap
23 |
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/examples/simple-test/readme.md:
--------------------------------------------------------------------------------
1 | testtut
2 | ====
3 | Please pardon the badly named repository. lol. It was a quick decision to quickly build this demo to show [@mizzao](http://github.com/mizzao). Never expected this to be referenced. DEMO: [click here](http://testtut.meteor.com)
4 |
5 |
6 | features
7 | ====
8 | * Hook up with iron-router
9 | * multiple tutorials for different pages
10 | * Use of Session to check if tutorial is enabled
11 | * Hook up 'finish' button on the 'words' page to turn off the tutorial by changing the Session variable
12 |
13 | contributions
14 | ====
15 | * feel free to fork and adjust this tutorial
--------------------------------------------------------------------------------
/tutorial.styl:
--------------------------------------------------------------------------------
1 | @import 'nib'
2 |
3 | // The darkness of the spotlight shadow
4 | opacity = 0.66
5 |
6 | .spotlight
7 | display: block;
8 | position: absolute;
9 | // The position values are animated and moved
10 | top: 0;
11 | left: 0;
12 | // Don't want these so we can have a total spot where needed
13 | // bottom: auto;
14 | // right: auto;
15 | border-radius: 10px
16 | box-shadow: 0px 0px 5px 5px rgba(0,0,0,opacity) inset, 0px 0px 0px 4000px rgba(0,0,0,opacity)
17 | pointer-events: none;
18 | z-index: 1000
19 |
20 | .modal-dialog.positioned
21 | position: absolute
22 | // Ideal value is above OpenLayers' controls of 1000+x, below bootstrap modal dialog of 1050
23 | z-index: 1049
24 |
25 | .modal-footer.compact
26 | margin-top: 0
27 |
28 |
--------------------------------------------------------------------------------
/package.js:
--------------------------------------------------------------------------------
1 | Package.describe({
2 | name: "mizzao:tutorials",
3 | summary: "Create super cool animated tutorials for your Meteor app",
4 | version: "0.6.8",
5 | git: "https://github.com/mizzao/meteor-tutorials.git"
6 | });
7 |
8 | Package.onUse(function (api) {
9 | api.versionsFrom("1.2.0.1");
10 |
11 | api.use(['jquery', 'coagmano:stylus@1.0.0', 'coffeescript'], 'client');
12 | api.use(['ui', 'templating'], 'client');
13 |
14 | // Weak dependencies on the most popular bootstrap packages
15 | api.use("twbs:bootstrap@3.3.5", 'client', {weak: true});
16 | api.use("nemo64:bootstrap@3.3.5_2", 'client', {weak: true});
17 |
18 | api.addFiles('templates.html', 'client');
19 | api.addFiles('tutorial.styl', 'client');
20 |
21 | api.addFiles('eventEmitter.coffee', 'client');
22 | api.addFiles('drags.js', 'client');
23 |
24 | api.addFiles('tutorial.coffee', 'client');
25 | api.addFiles('helpers.coffee', 'client');
26 |
27 | api.export('EventEmitter', 'client');
28 | });
29 |
--------------------------------------------------------------------------------
/eventEmitter.coffee:
--------------------------------------------------------------------------------
1 | # Simple EventEmitter for the client
2 | # Adapted from https://github.com/arunoda/meteor-streams/blob/master/lib/ev.js
3 |
4 | class EventEmitter
5 | constructor: ->
6 | @handlers = {}
7 |
8 | emit: (event) ->
9 | args = Array::slice.call(`arguments`, 1)
10 | handler.apply(@, args) for handler in @handlers[event] if @handlers[event]
11 | return
12 |
13 | on: (event, callback) ->
14 | @handlers[event] = [] unless @handlers[event]
15 | @handlers[event].push(callback)
16 | return
17 |
18 | once: (event, callback) ->
19 | @on event, onetimeCallback = ->
20 | callback.apply(@, `arguments`)
21 | @removeListener(event, onetimeCallback)
22 | return
23 | return
24 |
25 | removeListener: (event, callback) ->
26 | if @handlers[event]
27 | index = @handlers[event].indexOf(callback)
28 | @handlers[event].splice(index, 1)
29 | return
30 |
31 | removeAllListeners: (event) ->
32 | @handlers[event] = `undefined`
33 | return
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Andrew Mao
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/simple-test/.meteor/versions:
--------------------------------------------------------------------------------
1 | babel-compiler@6.8.4
2 | babel-runtime@0.1.9_1
3 | base64@1.0.9
4 | blaze@2.1.8
5 | blaze-tools@1.0.9
6 | boilerplate-generator@1.0.9
7 | caching-compiler@1.0.6
8 | caching-html-compiler@1.0.6
9 | check@1.2.3
10 | coffeescript@1.1.3
11 | deps@1.0.12
12 | diff-sequence@1.0.6
13 | ecmascript@0.4.7
14 | ecmascript-runtime@0.2.12
15 | ejson@1.0.12
16 | html-tools@1.0.10
17 | htmljs@1.0.10
18 | id-map@1.0.8
19 | iron:controller@1.0.12
20 | iron:core@1.0.11
21 | iron:dynamic-template@1.0.12
22 | iron:layout@1.0.12
23 | iron:location@1.0.11
24 | iron:middleware-stack@1.1.0
25 | iron:router@1.0.13
26 | iron:url@1.0.11
27 | jquery@1.11.9
28 | logging@1.0.14
29 | meteor@1.1.16
30 | minifier-css@1.1.13
31 | minifier-js@1.1.13
32 | mizzao:tutorials@0.6.7
33 | modules@0.6.5
34 | modules-runtime@0.6.5
35 | mongo-id@1.0.5
36 | observe-sequence@1.0.12
37 | promise@0.7.3
38 | random@1.0.10
39 | reactive-dict@1.1.8
40 | reactive-var@1.0.10
41 | routepolicy@1.0.11
42 | session@1.1.6
43 | spacebars@1.0.12
44 | spacebars-compiler@1.0.12
45 | standard-minifier-css@1.0.8
46 | standard-minifier-js@1.0.8
47 | stylus@2.512.4
48 | templating@1.1.13
49 | templating-tools@1.0.4
50 | tracker@1.0.14
51 | twbs:bootstrap@3.3.6
52 | ui@1.0.11
53 | underscore@1.0.9
54 | webapp@1.2.10
55 | webapp-hashing@1.0.9
56 |
--------------------------------------------------------------------------------
/templates.html:
--------------------------------------------------------------------------------
1 |
2 | {{#with tutorialManager}}
3 |
4 | {{! Note the canonical BS3 div.modal wrapper is omitted here because it's actually NOT modal }}
5 |
This page is a demo of the meteor-tutorials package
28 |39 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Quisque ornare turpis tortor, ut fringilla quam sodales at. Morbi suscipit magna pulvinar, volutpat mauris sit amet, molestie quam. Morbi molestie enim lacus, eu euismod metus placerat eget. Curabitur condimentum elit egestas, viverra leo vitae, vehicula leo. Aenean eu tristique massa, ut iaculis dolor. Curabitur pellentesque dignissim neque lacinia lacinia. Mauris iaculis sit amet enim a vestibulum. Duis malesuada metus eget convallis rutrum. Morbi id condimentum dui, eu laoreet urna. In semper massa ut feugiat venenatis. Donec bibendum varius consequat. Nunc vitae justo metus. Morbi faucibus justo cursus, luctus quam nec, condimentum diam. Nam fringilla fringilla mi quis interdum. Phasellus tincidunt elit et mauris volutpat consequat. Vivamus congue consectetur dolor pharetra tincidunt. 40 |
41 |I made this demo as a simple tutorial of how to use the meteor-tutorials package. Click next.
61 | 62 | 63 | 64 |This demo is used with other packages to test drive how the package would play well. I added iron-router to the mix so you can navigate this demo.
67 | 68 |Try resizing your window. The positions of the spotlight and this dialog should update automatically.
69 | 70 |Click on 'Words' or the finish button to navigate to the 'Words page'
71 | 72 | 73 | 74 |Two different tutorials. Each working on a separate page. This one works on the words page.
76 | 77 | {{#unless stepCompleted}} 78 | Click the words to enable the next step. 79 | {{else}} 80 | Great, you clicked the words! 81 | {{/unless}} 82 | 83 | 84 | 85 |Click 'Finish' to end all the tutorials.
86 | 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | meteor-tutorials 2 | ================ 3 | 4 | ## What's this do? 5 | 6 | Easily create super cool animated tutorials for your Meteor app. This package gives you a dirt easy way to make multi-step tutorials that spotlight multiple parts of your user interface. What a great way to show off how awesome your Meteor app is! 7 | 8 | [](https://www.youtube.com/watch?v=smax46TNPPk) 9 | 10 | [See a demo](http://testtut.meteor.com/). 11 | 12 | Here's one of my apps. Among other things, it has a list of online users (provided by the [user-status package](https://github.com/mizzao/meteor-user-status)) and a chat room. As users go through the tutorial, which has several steps, the tutorial explains different parts of the user interface to them. 13 | 14 |  15 | 16 | Later on, the chat room is shown: 17 | 18 |  19 | 20 | The awesomeness of this tutorial is that it's completely autogenerated and moves with your code. All you have to do is specify the templates for each step and a selector for what should be spotlighted, and the spotlight and modal positions are computed automatically. This means that you won't have to hardcode anything or do much maintenance when your app changes. Best of all, it's animated and looks great! 21 | 22 | ## Install 23 | 24 | Install with Meteorite: 25 | 26 | ``` 27 | meteor add mizzao:tutorials 28 | ``` 29 | 30 | ## Usage 31 | 32 | First, specify some templates for your tutorial. Easy as pie: 33 | 34 | ```html 35 | 36 | This is step 1 of the tutorial. 37 | 38 | 39 | 40 | This is step 2 of the tutorial. 41 | 42 | ``` 43 | 44 | Next, define the steps of your tutorial in a helper accessible by whatever template is drawing the tutorial. 45 | 46 | ```js 47 | tutorialSteps = [ 48 | { 49 | template: Template.tutorial_step1, 50 | onLoad: function() { console.log("The tutorial has started!"); } 51 | }, 52 | { 53 | template: Template.tutorial_step2, 54 | spot: ".myElement, .otherElement", 55 | require: { 56 | event: "something-emitted", 57 | validator: function(args) { ... } 58 | } 59 | } 60 | ]; 61 | 62 | Template.foo.helpers({ 63 | options: { 64 | id: "myCoolTutorial", 65 | steps: tutorialSteps, 66 | emitter: new EventEmitter(), 67 | onFinish: function() { /* make the tutorial disappear */ } 68 | } 69 | }); 70 | ``` 71 | 72 | The `id` field of the options is optional. If provided, it preserves the current step of the tutorial across a hot code reload by saving it in a `Session` variable. You will probably find this very useful when testing your tutorial. 73 | 74 | The steps of the tutorial should be an array of objects, which take the following form: 75 | 76 | - `template`: (**required**) The template that should be displayed in the modal for this step. You can specify this either directly as a `Template.foo` object, or as a string like `"foo"`. 77 | - `spot`: (*optional*) jQuery selector of elements to highlight (can be a single selector or separated by commas). If multiple elements are selected, the tutorial automatically calculates a box that will fit around all of them. 78 | - `onLoad`: (*optional*) a function that will run whenever the tutorial hits this step. Helpful if you need to make sure your interface is in a certain state before displaying the tutorial contents. 79 | - `require`: (*optional*) an object with an `event` argument and an optional `validator` function to listen for a required user or other action to happen before the **Next** or **Finish** buttons appear. Used in conjunction with an `EventEmitter` instance, as below. 80 | 81 | Now, just call the `tutorial` helper with your `steps` from a template whose [offset parent](http://api.jquery.com/offsetParent/) is the same size as the body. This is necessary because the tutorial content is absolutely positioned relative to the window. 82 | 83 | ```html 84 | 85 | {{! My cool user interface}} 86 | 87 | {{#if tutorialEnabled}} 88 | {{> tutorial options}} 89 | {{/if}} 90 | 91 | ``` 92 | 93 | To require users to do certain actions to proceed through the tutorial, pass an object that satisfies the Node [`EventEmitter`](http://nodejs.org/api/events.html) signature via the `emitter` field in the options. The tutorial will bind listeners to all the events specified in `require` fields and check them with the `optional` validator. The event will need to be fired before the the tutorial can be continued, but they don't need to happen on the same step that it is shown, and the tutorial will remember the state of the events across steps. Use the `stepCompleted` helper in a tutorial template to render different text depending on whether the user has completed the required action (see examples). 94 | 95 | Enjoy as your users learn how to use your app much quicker! 96 | 97 | ### Examples 98 | 99 | - [Example code](examples) is available. Thanks [geekyme](https://github.com/geekyme) for getting it started. 100 | - Check out the tutorial code for Crowdmapper: [code](https://github.com/mizzao/CrowdMapper/blob/master/client/tutorial/tutorial.coffee) and [templates](https://github.com/mizzao/CrowdMapper/blob/master/client/tutorial/tutorial.html). 101 | 102 | ### Notes 103 | 104 | - I didn't know about it when I conceived this, but this is very similar to [intro.js](http://usablica.github.io/intro.js/). However, it uses Meteor's templating and reactive capabilities, so in my opinion it's much more powerful. 105 | -------------------------------------------------------------------------------- /tutorial.coffee: -------------------------------------------------------------------------------- 1 | defaultSpot = -> 2 | top: 0 3 | left: 0 4 | bottom: $(window).height() 5 | right: $(window).width() 6 | 7 | defaultModal = -> 8 | # ensure the modal still fits on small screens 9 | width = Math.min( $(window).width(), 560) 10 | return { 11 | top: "10%" 12 | left: "50%" 13 | width: width 14 | "margin-left": -width / 2 # keep the modal centered 15 | } 16 | 17 | spotPadding = 10 # How much to expand the spotlight on all sides 18 | modalBuffer = 20 # How much to separate the modal from the spotlight 19 | 20 | _sessionKeyPrefix = "_tutorial_step_" 21 | 22 | class @TutorialManager 23 | constructor: (options) -> 24 | check(options.steps, Array) 25 | 26 | @steps = options.steps 27 | @onFinish = options.onFinish || null 28 | @emitter = options.emitter 29 | 30 | # Grab existing step if it exists - but don't grab it reactively, 31 | # or this template will keep reloading 32 | if options.id? 33 | @sessionKey = _sessionKeyPrefix + options.id 34 | @step = Deps.nonreactive => Session.get(@sessionKey) 35 | 36 | @step ?= 0 37 | @stepDep = new Deps.Dependency 38 | @finishedDep = new Deps.Dependency 39 | 40 | # Build array of reactive dependencies for events 41 | return unless @emitter 42 | @buildActionDeps() 43 | 44 | buildActionDeps: -> 45 | @actionDeps = [] 46 | for i, step of @steps 47 | if step.require 48 | check(step.require.event, String) 49 | dep = new Deps.Dependency 50 | validator = step.require.validator 51 | check(validator, Function) if validator 52 | dep.completed = false 53 | @actionDeps.push(dep) 54 | 55 | # Bind a function to watch for this event 56 | checker = (-> 57 | # Bind validator dep in closure 58 | val = validator 59 | d = dep 60 | return -> 61 | actionCompleted = if val then val.apply(this, arguments) else true 62 | if actionCompleted 63 | d.completed = true 64 | d.changed() 65 | )() 66 | @emitter.on step.require.event, checker 67 | 68 | else 69 | @actionDeps.push(null) 70 | 71 | # Store steps in Session variable when they change 72 | prev: -> 73 | return if @step is 0 74 | @step-- 75 | @stepDep.changed() 76 | Session.set(@sessionKey, @step) if @sessionKey? 77 | 78 | next: -> 79 | return if @step is (@steps.length - 1) 80 | @step++ 81 | @stepDep.changed() 82 | Session.set(@sessionKey, @step) if @sessionKey? 83 | 84 | # Process finish click. If there is a function to call, only call it once. 85 | finish: -> 86 | if @onFinish? 87 | @finishedDep.finished = true 88 | @finishedDep.changed() 89 | @onFinish() 90 | 91 | prevEnabled: -> 92 | @stepDep.depend() 93 | return @step > 0 94 | 95 | nextEnabled: -> 96 | @stepDep.depend() 97 | return @step < (@steps.length - 1) and @stepCompleted() 98 | 99 | stepCompleted: -> 100 | @stepDep.depend() 101 | actionDep = @actionDeps?[@step] 102 | return true unless actionDep 103 | 104 | actionDep.depend() 105 | return actionDep.completed 106 | 107 | finishEnabled: -> 108 | @stepDep.depend() 109 | return @step is @steps.length - 1 and @stepCompleted() 110 | 111 | # Debounce for finish button 112 | finishPending: -> 113 | @finishedDep.depend() 114 | return @finishedDep.finished 115 | 116 | currentTemplate: -> 117 | @stepDep.depend() 118 | template = @steps[@step].template 119 | # Support both string and direct references. 120 | return if _.isString(template) then Template[template] else template 121 | 122 | # Stuff below is currently not reactive 123 | currentLoadFunc: -> 124 | return @steps[@step].onLoad 125 | 126 | getPositions: -> 127 | # @stepDep.depend() if we want reactivity 128 | selector = @steps[@step].spot 129 | return [ defaultSpot(), defaultModal() ] unless selector? 130 | 131 | items = $(selector) 132 | if items.length is 0 133 | console.log "Tutorial error: couldn't find spot for " + selector 134 | return [ defaultSpot(), defaultModal() ] 135 | 136 | # Compute spot and modal positions 137 | hull = 138 | top: 5000 139 | left: 5000 140 | bottom: 5000 141 | right: 5000 142 | 143 | items.each (i) -> 144 | $el = $(this) 145 | # outer height/width used here: http://api.jquery.com/outerHeight/ 146 | # Second computation adds support for *SOME* SVG elements 147 | elWidth = $el.outerWidth() || parseInt($el.attr("width")) 148 | elHeight = $el.outerHeight() || parseInt($el.attr("height")) 149 | offset = $el.offset() 150 | 151 | hull.top = Math.min(hull.top, offset.top) 152 | hull.left = Math.min(hull.left, offset.left) 153 | hull.bottom = Math.min(hull.bottom, $(window).height() - offset.top - elHeight) 154 | hull.right = Math.min(hull.right, $(window).width() - offset.left - elWidth) 155 | 156 | # enlarge spotlight slightly and find largest side 157 | maxKey = null 158 | maxVal = 0 159 | for k,v of hull 160 | if v > maxVal 161 | maxKey = k 162 | maxVal = v 163 | hull[k] = Math.max(0, v - spotPadding) 164 | 165 | modalStyle = defaultModal() 166 | 167 | modal = switch 168 | # When the spotlight is very large, stick the modal in the center and let the user deal with it 169 | when maxVal < 200 then modalStyle 170 | # Otherwise put modal on the side with the most space 171 | when maxKey is "top" # go as close to top as possible 172 | $.extend {}, modalStyle, { top: "5%" } 173 | when maxKey is "bottom" # start from bottom of spot 174 | $.extend {}, modalStyle, 175 | top: $(window).height() - hull.bottom + modalBuffer 176 | when maxKey is "left" 177 | width = Math.min(hull.left - 2*modalBuffer, modalStyle.width) 178 | $.extend {}, modalStyle, 179 | left: hull.left / 2 180 | width: width 181 | "margin-left": -width/2 182 | when maxKey is "right" 183 | width = Math.min(hull.right - 2*modalBuffer, modalStyle.width) 184 | $.extend {}, modalStyle, 185 | left: $(window).width() - hull.right / 2 186 | width: width 187 | "margin-left": -width/2 188 | return [ hull, modal ] 189 | --------------------------------------------------------------------------------