xray-rails 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +74 -0
- data/app/assets/javascripts/xray-backbone.js.coffee +20 -0
- data/app/assets/javascripts/xray.js.coffee +282 -0
- data/app/assets/stylesheets/xray.css +349 -0
- data/app/views/_xray_bar.html.erb +46 -0
- data/lib/xray/config.rb +52 -0
- data/lib/xray/engine.rb +88 -0
- data/lib/xray/middleware.rb +88 -0
- data/lib/xray/version.rb +3 -0
- data/lib/xray-rails.rb +106 -0
- metadata +201 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Brent Dillingham
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Xray
|
2
|
+
|
3
|
+
### Reveal the structure of your UI.
|
4
|
+
|
5
|
+
The dev tools available to web developers in modern browsers are great. Many of us can't remember what life was like before "Inspect Element". But what we see in the compiled output sent to our browser is often the wrong level of detail - what about being able to visualize the higher level components of your UI? Controllers, templates, partials, Backbone views, etc.
|
6
|
+
|
7
|
+
Xray is the missing link between the browser and your app code. Press **cmd+ctrl+x** to reveal an overlay of what files are powering your UI - click anything to open the associated file in your editor.
|
8
|
+
|
9
|
+
![Screenshot](http://dl.dropbox.com/u/156655/Screenshots/xgf7ukh3fya-.png)
|
10
|
+
|
11
|
+
### Current Support
|
12
|
+
|
13
|
+
Xray is in early stages and currently supports only Rails 3.1+ with use of the asset pipeline as a requirement.
|
14
|
+
|
15
|
+
So far, Xray can reveal:
|
16
|
+
|
17
|
+
* Rails views and partials
|
18
|
+
* Backbone View instances
|
19
|
+
|
20
|
+
### Installation
|
21
|
+
|
22
|
+
Add to your Gemfile, preferably under your development group:
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
group :development do
|
26
|
+
...
|
27
|
+
gem 'xray-rails', github: 'brentd/xray-rails'
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
Then bundle and delete your cached assets:
|
32
|
+
|
33
|
+
```
|
34
|
+
$ bundle && rm -rf tmp/cache/assets
|
35
|
+
```
|
36
|
+
|
37
|
+
Restart your app, open your browser, and press `cmd+ctrl+x` to see the overlay.
|
38
|
+
|
39
|
+
### Configuration
|
40
|
+
|
41
|
+
By default, Xray will open files with Sublime Text, looking for `/usr/local/bin/subl`.
|
42
|
+
|
43
|
+
You can configure this to be your editor of choice in Xray's UI, or create `~/.xrayconfig`, a YAML file.
|
44
|
+
|
45
|
+
Example `.xrayconfig`:
|
46
|
+
|
47
|
+
```yaml
|
48
|
+
:editor: '/usr/local/bin/mate'
|
49
|
+
```
|
50
|
+
|
51
|
+
Or for something more complex, use the `$file` placeholder.
|
52
|
+
|
53
|
+
```yaml
|
54
|
+
:editor: "/usr/local/bin/tmux split-window -v '/usr/local/bin/vim $file'"
|
55
|
+
```
|
56
|
+
|
57
|
+
### How this works
|
58
|
+
|
59
|
+
* During asset compilation, JS files and templates are modified to contain file path information.
|
60
|
+
* A middleware inserts `xray.js`, `xray.css`, and the Xray bar into all successful HTML response bodies.
|
61
|
+
* When the overlay is shown, `xray.js` examines the file path information inserted during asset compilation.
|
62
|
+
* Another middleware piggybacks the Rails server to respond to requests to open file paths with the user's desired editor.
|
63
|
+
|
64
|
+
### Contributing
|
65
|
+
|
66
|
+
If you have an idea, open an issue and let's talk about it, or fork away and send a pull request.
|
67
|
+
|
68
|
+
A laundry list of things to take on:
|
69
|
+
|
70
|
+
* Reveal views from Ember, Knockout, Angular, etc.
|
71
|
+
* Overlapping boxes are a problem - parent views in real applications will often be obscured by their children.
|
72
|
+
* The current scheme for associating a JS constructor with a filepath is messy and can make stack traces ugly.
|
73
|
+
|
74
|
+
Worth noting is that I have plans to solidify xray.js into an API and specification that could be used to aid development in any framework - not just Rails and the asset pipeline.
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# Xray Backbone integration. This involves hooking into the lifecycle
|
2
|
+
# of Backbone.View by monkey patching its prototype. Would love a cleaner
|
3
|
+
# way of doing this, as nobody wants to this stuff in their stack traces.
|
4
|
+
|
5
|
+
return unless window.Backbone && window.Xray
|
6
|
+
|
7
|
+
# Wrap Backbone.View::_ensureElement to add the view to Xray once
|
8
|
+
# its element has been setup.
|
9
|
+
_ensureElement = Backbone.View::_ensureElement
|
10
|
+
Backbone.View::_ensureElement = ->
|
11
|
+
_.defer =>
|
12
|
+
info = Xray.constructorInfo @constructor
|
13
|
+
Xray.ViewSpecimen.add @el, info
|
14
|
+
_ensureElement.apply(this, arguments)
|
15
|
+
|
16
|
+
# Cleanup when view is removed.
|
17
|
+
_remove = Backbone.View::remove
|
18
|
+
Backbone.View::remove = ->
|
19
|
+
Xray.ViewSpecimen.remove @el
|
20
|
+
_remove.apply(this, arguments)
|
@@ -0,0 +1,282 @@
|
|
1
|
+
window.Xray = {}
|
2
|
+
return unless $ = window.jQuery
|
3
|
+
|
4
|
+
# Max CSS z-index. The overlay and xray bar use this.
|
5
|
+
MAX_ZINDEX = 2147483647
|
6
|
+
|
7
|
+
# Initialize Xray. Called immediately, but some setup is deferred until DOM ready.
|
8
|
+
Xray.init = do ->
|
9
|
+
return if Xray.initialized
|
10
|
+
Xray.initialized = true
|
11
|
+
|
12
|
+
# Register keyboard shortcuts
|
13
|
+
$(document).keydown (e) ->
|
14
|
+
if e.ctrlKey and e.metaKey and e.keyCode is 88 # cmd+ctrl+x
|
15
|
+
if Xray.isShowing then Xray.hide() else Xray.show()
|
16
|
+
if Xray.isShowing and e.keyCode is 27 # esc
|
17
|
+
Xray.hide()
|
18
|
+
|
19
|
+
$ ->
|
20
|
+
# Instantiate the overlay singleton.
|
21
|
+
new Xray.Overlay
|
22
|
+
# Go ahead and do a pass on the DOM to find templates.
|
23
|
+
Xray.findTemplates()
|
24
|
+
# Ready to rock.
|
25
|
+
console?.log "Ready to Xray. Press cmd+ctrl+x to scan your UI."
|
26
|
+
|
27
|
+
# Returns all currently created Xray.Specimen objects.
|
28
|
+
Xray.specimens = ->
|
29
|
+
Xray.ViewSpecimen.all.concat Xray.TemplateSpecimen.all
|
30
|
+
|
31
|
+
# Looks up the stored constructor info
|
32
|
+
Xray.constructorInfo = (constructor) ->
|
33
|
+
if window.XrayPaths
|
34
|
+
for own info, func of window.XrayPaths
|
35
|
+
return JSON.parse(info) if func == constructor
|
36
|
+
null
|
37
|
+
|
38
|
+
# Scans the document for templates, creating Xray.TemplateSpecimens for them.
|
39
|
+
Xray.findTemplates = -> util.bm 'findTemplates', ->
|
40
|
+
# Find all <!-- XRAY START ... --> comments
|
41
|
+
comments = $('*:not(iframe,script)').contents().filter ->
|
42
|
+
this.nodeType == 8 and this.data[0..9] == "XRAY START"
|
43
|
+
|
44
|
+
# Find the <!-- XRAY END ... --> comment for each. Everything between the
|
45
|
+
# start and end comment becomes the contents of an Xray.TemplateSpecimen.
|
46
|
+
for comment in comments
|
47
|
+
[_, id, path] = comment.data.match(/^XRAY START (\d+) (.*)$/)
|
48
|
+
$templateContents = new jQuery
|
49
|
+
el = comment.nextSibling
|
50
|
+
until !el or (el.nodeType == 8 and el.data == "XRAY END #{id}")
|
51
|
+
if el.nodeType == 1 and el.tagName != 'SCRIPT'
|
52
|
+
$templateContents.push el
|
53
|
+
el = el.nextSibling
|
54
|
+
# Remove XRAY template comments from the DOM.
|
55
|
+
el.parentNode.removeChild(el) if el?.nodeType == 8
|
56
|
+
comment.parentNode.removeChild(comment)
|
57
|
+
# Add the template specimen
|
58
|
+
Xray.TemplateSpecimen.add $templateContents,
|
59
|
+
name: path.split('/').slice(-1)[0]
|
60
|
+
path: path
|
61
|
+
|
62
|
+
# Open the given filesystem path by calling out to Xray's server.
|
63
|
+
Xray.open = (path) ->
|
64
|
+
$.ajax(url: "/_xray/open?path=#{path}")
|
65
|
+
|
66
|
+
# Show the Xray overlay
|
67
|
+
Xray.show = (type = null) ->
|
68
|
+
Xray.Overlay.instance().show(type)
|
69
|
+
|
70
|
+
# Hide the Xray overlay
|
71
|
+
Xray.hide = ->
|
72
|
+
Xray.Overlay.instance().hide()
|
73
|
+
|
74
|
+
Xray.toggleSettings = ->
|
75
|
+
Xray.Overlay.instance().settings.toggle()
|
76
|
+
|
77
|
+
# Wraps a DOM element that Xray is tracking. This is subclassed by
|
78
|
+
# Xray.TemplateSpecimen and Xray.ViewSpecimen.
|
79
|
+
class Xray.Specimen
|
80
|
+
@add: (el, info = {}) ->
|
81
|
+
@all.push new this(el, info)
|
82
|
+
|
83
|
+
@remove: (el) ->
|
84
|
+
@find(el)?.remove()
|
85
|
+
|
86
|
+
@find: (el) ->
|
87
|
+
el = el[0] if el instanceof jQuery
|
88
|
+
for specimen in @all
|
89
|
+
return specimen if specimen.el == el
|
90
|
+
null
|
91
|
+
|
92
|
+
@reset: ->
|
93
|
+
@all = []
|
94
|
+
|
95
|
+
constructor: (contents, info = {}) ->
|
96
|
+
@el = if contents instanceof jQuery then contents[0] else contents
|
97
|
+
@$contents = $(contents)
|
98
|
+
@name = info.name
|
99
|
+
@path = info.path
|
100
|
+
|
101
|
+
remove: ->
|
102
|
+
idx = @constructor.all.indexOf(this)
|
103
|
+
@constructor.all.splice(idx, 1) unless idx == -1
|
104
|
+
|
105
|
+
isVisible: ->
|
106
|
+
@$contents.length and @$contents.is(':visible')
|
107
|
+
|
108
|
+
makeBox: ->
|
109
|
+
@bounds = util.computeBoundingBox(@$contents)
|
110
|
+
@$box = $("<div class='xray-specimen #{@constructor.name}'>").css(@bounds)
|
111
|
+
|
112
|
+
# If the element is fixed, override the computed position with the fixed one.
|
113
|
+
if @$contents.css('position') == 'fixed'
|
114
|
+
@$box.css
|
115
|
+
position : 'fixed'
|
116
|
+
top : @$contents.css('top')
|
117
|
+
left : @$contents.css('left')
|
118
|
+
|
119
|
+
@$box.click => Xray.open @path
|
120
|
+
@$box.append @makeLabel
|
121
|
+
|
122
|
+
makeLabel: =>
|
123
|
+
$("<div class='xray-specimen-handle #{@constructor.name}'>").append(@name)
|
124
|
+
|
125
|
+
|
126
|
+
# Wraps elements that constitute a Javascript "view" object, e.g.
|
127
|
+
# Backbone.View.
|
128
|
+
class Xray.ViewSpecimen extends Xray.Specimen
|
129
|
+
@all = []
|
130
|
+
|
131
|
+
|
132
|
+
# Wraps elements that were rendered by a template, e.g. a Rails partial or
|
133
|
+
# a client-side rendered JS template.
|
134
|
+
class Xray.TemplateSpecimen extends Xray.Specimen
|
135
|
+
@all = []
|
136
|
+
|
137
|
+
|
138
|
+
# Singleton class for the Xray "overlay" invoked by the keyboard shortcut
|
139
|
+
class Xray.Overlay
|
140
|
+
@instance: ->
|
141
|
+
@singletonInstance ||= new this
|
142
|
+
|
143
|
+
constructor: ->
|
144
|
+
Xray.Overlay.singletonInstance = this
|
145
|
+
@bar = new Xray.Bar('#xray-bar')
|
146
|
+
@settings = new Xray.Settings('#xray-settings')
|
147
|
+
@shownBoxes = []
|
148
|
+
@$overlay = $('<div id="xray-overlay">')
|
149
|
+
@$overlay.click => @hide()
|
150
|
+
|
151
|
+
show: (type = null) ->
|
152
|
+
@reset()
|
153
|
+
Xray.isShowing = true
|
154
|
+
util.bm 'show', =>
|
155
|
+
@bar.$el.find('#xray-bar-togglers .xray-bar-btn').removeClass('active')
|
156
|
+
unless @$overlay.is(':visible')
|
157
|
+
$('body').append @$overlay
|
158
|
+
@bar.show()
|
159
|
+
switch type
|
160
|
+
when 'templates'
|
161
|
+
Xray.findTemplates()
|
162
|
+
specimens = Xray.TemplateSpecimen.all
|
163
|
+
@bar.$el.find('.xray-bar-templates-toggler').addClass('active')
|
164
|
+
when 'views'
|
165
|
+
specimens = Xray.ViewSpecimen.all
|
166
|
+
@bar.$el.find('.xray-bar-views-toggler').addClass('active')
|
167
|
+
else
|
168
|
+
Xray.findTemplates()
|
169
|
+
specimens = Xray.specimens()
|
170
|
+
@bar.$el.find('.xray-bar-all-toggler').addClass('active')
|
171
|
+
for element in specimens
|
172
|
+
continue unless element.isVisible()
|
173
|
+
element.makeBox()
|
174
|
+
# A cheap way to "order" the boxes, where boxes positioned closer to the
|
175
|
+
# bottom right of the document have a higher z-index.
|
176
|
+
element.$box.css
|
177
|
+
zIndex: Math.ceil(MAX_ZINDEX*0.9 + element.bounds.top + element.bounds.left)
|
178
|
+
@shownBoxes.push element.$box
|
179
|
+
$('body').append element.$box
|
180
|
+
|
181
|
+
reset: ->
|
182
|
+
$box.remove() for $box in @shownBoxes
|
183
|
+
@shownBoxes = []
|
184
|
+
|
185
|
+
hide: ->
|
186
|
+
Xray.isShowing = false
|
187
|
+
@$overlay.detach()
|
188
|
+
@reset()
|
189
|
+
@bar.hide()
|
190
|
+
|
191
|
+
|
192
|
+
# The Xray bar shows controller, action, and view information, and has
|
193
|
+
# toggle buttons for showing the different types of specimens in the overlay.
|
194
|
+
class Xray.Bar
|
195
|
+
constructor: (el) ->
|
196
|
+
@$el = $(el)
|
197
|
+
@$el.css(zIndex: MAX_ZINDEX)
|
198
|
+
@$el.find('#xray-bar-controller-path .xray-bar-btn').click ->
|
199
|
+
Xray.open($(this).attr('data-path'))
|
200
|
+
@$el.find('.xray-bar-all-toggler').click -> Xray.show()
|
201
|
+
@$el.find('.xray-bar-templates-toggler').click -> Xray.show('templates')
|
202
|
+
@$el.find('.xray-bar-views-toggler').click -> Xray.show('views')
|
203
|
+
@$el.find('.xray-bar-settings-btn').click -> Xray.toggleSettings()
|
204
|
+
|
205
|
+
show: ->
|
206
|
+
@$el.show()
|
207
|
+
@originalPadding = parseInt $('html').css('padding-bottom')
|
208
|
+
if @originalPadding < 40
|
209
|
+
$('html').css paddingBottom: 40
|
210
|
+
|
211
|
+
hide: ->
|
212
|
+
@$el.hide()
|
213
|
+
$('html').css paddingBottom: @originalPadding
|
214
|
+
|
215
|
+
|
216
|
+
class Xray.Settings
|
217
|
+
constructor: (el) ->
|
218
|
+
@$el = $(el)
|
219
|
+
@$el.find('form').submit @save
|
220
|
+
|
221
|
+
toggle: =>
|
222
|
+
@$el.toggle()
|
223
|
+
|
224
|
+
save: (e) =>
|
225
|
+
e.preventDefault()
|
226
|
+
editor = @$el.find('#xray-editor-input').val()
|
227
|
+
$.ajax
|
228
|
+
url: '/_xray/config'
|
229
|
+
type: 'POST'
|
230
|
+
data: {editor: editor}
|
231
|
+
success: => @displayUpdateMsg(true)
|
232
|
+
error: => @displayUpdateMsg(false)
|
233
|
+
|
234
|
+
displayUpdateMsg: (success) =>
|
235
|
+
if success
|
236
|
+
$msg = $("<span class='xray-settings-success xray-settings-update-msg'>Success!</span>")
|
237
|
+
else
|
238
|
+
$msg = $("<span class='xray-settings-error xray-settings-update-msg'>Uh oh, something went wrong!</span>")
|
239
|
+
@$el.append($msg)
|
240
|
+
$msg.delay(2000).fadeOut(500, => $msg.remove(); @toggle())
|
241
|
+
|
242
|
+
|
243
|
+
# Utility methods.
|
244
|
+
util =
|
245
|
+
# Benchmark a piece of code
|
246
|
+
bm: (name, fn) ->
|
247
|
+
time = new Date
|
248
|
+
result = fn()
|
249
|
+
# console.log "#{name} : #{new Date() - time}ms"
|
250
|
+
result
|
251
|
+
|
252
|
+
# Computes the bounding box of a jQuery set, which may be many sibling
|
253
|
+
# elements with no parent in the set.
|
254
|
+
computeBoundingBox: ($contents) ->
|
255
|
+
# Edge case: the container may not physically wrap its children, for
|
256
|
+
# example if they are floated and no clearfix is present.
|
257
|
+
if $contents.length == 1 and $contents.height() <= 0
|
258
|
+
return util.computeBoundingBox($contents.children())
|
259
|
+
|
260
|
+
boxFrame =
|
261
|
+
top : Number.POSITIVE_INFINITY
|
262
|
+
left : Number.POSITIVE_INFINITY
|
263
|
+
right : Number.NEGATIVE_INFINITY
|
264
|
+
bottom : Number.NEGATIVE_INFINITY
|
265
|
+
|
266
|
+
for el in $contents
|
267
|
+
$el = $(el)
|
268
|
+
continue unless $el.is(':visible')
|
269
|
+
frame = $el.offset()
|
270
|
+
frame.right = frame.left + $el.outerWidth()
|
271
|
+
frame.bottom = frame.top + $el.outerHeight()
|
272
|
+
boxFrame.top = frame.top if frame.top < boxFrame.top
|
273
|
+
boxFrame.left = frame.left if frame.left < boxFrame.left
|
274
|
+
boxFrame.right = frame.right if frame.right > boxFrame.right
|
275
|
+
boxFrame.bottom = frame.bottom if frame.bottom > boxFrame.bottom
|
276
|
+
|
277
|
+
return {
|
278
|
+
left : boxFrame.left
|
279
|
+
top : boxFrame.top
|
280
|
+
width : boxFrame.right - boxFrame.left
|
281
|
+
height : boxFrame.bottom - boxFrame.top
|
282
|
+
}
|
@@ -0,0 +1,349 @@
|
|
1
|
+
@charset "UTF-8";
|
2
|
+
|
3
|
+
/* selector for element and children */
|
4
|
+
#xray-overlay, #xray-overlay *, #xray-overlay a:hover, #xray-overlay a:visited, #xray-overlay a:active,
|
5
|
+
#xray-bar, #xray-bar *, #xray-bar a:hover, #xray-bar a:visited, #xray-bar a:active {
|
6
|
+
background:none;
|
7
|
+
border:none;
|
8
|
+
bottom:auto;
|
9
|
+
clear:none;
|
10
|
+
cursor:default;
|
11
|
+
float:none;
|
12
|
+
font-family:Arial, Helvetica, sans-serif;
|
13
|
+
font-size:medium;
|
14
|
+
font-style:normal;
|
15
|
+
font-weight:normal;
|
16
|
+
height:auto;
|
17
|
+
left:auto;
|
18
|
+
letter-spacing:normal;
|
19
|
+
line-height:normal;
|
20
|
+
max-height:none;
|
21
|
+
max-width:none;
|
22
|
+
min-height:0;
|
23
|
+
min-width:0;
|
24
|
+
overflow:visible;
|
25
|
+
position:static;
|
26
|
+
right:auto;
|
27
|
+
text-align:left;
|
28
|
+
text-decoration:none;
|
29
|
+
text-indent:0;
|
30
|
+
text-transform:none;
|
31
|
+
top:auto;
|
32
|
+
visibility:visible;
|
33
|
+
white-space:normal;
|
34
|
+
width:auto;
|
35
|
+
z-index:auto;
|
36
|
+
}
|
37
|
+
|
38
|
+
#xray-overlay {
|
39
|
+
position: fixed; left: 0; top: 0; bottom: 0; right: 0;
|
40
|
+
background: rgba(0,0,0,0.7);
|
41
|
+
background: -webkit-radial-gradient(center, ellipse cover, rgba(0,0,0,0.4) 10%, rgba(0,0,0,0.8) 100%);
|
42
|
+
z-index: 9000;
|
43
|
+
}
|
44
|
+
|
45
|
+
.xray-specimen {
|
46
|
+
position: absolute;
|
47
|
+
background: rgba(255,255,255,0.15);
|
48
|
+
outline: 1px solid rgba(255,255,255,0.8);
|
49
|
+
outline-offset: -1px;
|
50
|
+
color: #666;
|
51
|
+
font-family: "Helvetica Neue", sans-serif;
|
52
|
+
font-size: 13px;
|
53
|
+
box-shadow: 0 1px 3px rgba(0,0,0,0.7);
|
54
|
+
}
|
55
|
+
|
56
|
+
.xray-specimen:hover {
|
57
|
+
cursor: pointer;
|
58
|
+
background: rgba(255,255,255,0.4);
|
59
|
+
}
|
60
|
+
|
61
|
+
.xray-specimen.TemplateSpecimen {
|
62
|
+
outline: 1px solid rgba(255,50,50,0.8);
|
63
|
+
background: rgba(255,50,50,0.1);
|
64
|
+
}
|
65
|
+
|
66
|
+
.xray-specimen.TemplateSpecimen:hover {
|
67
|
+
background: rgba(255,50,50,0.4);
|
68
|
+
}
|
69
|
+
|
70
|
+
.xray-specimen-handle {
|
71
|
+
float:left;
|
72
|
+
background: #fff;
|
73
|
+
padding: 0 3px;
|
74
|
+
color: #333;
|
75
|
+
font-size: 10px;
|
76
|
+
}
|
77
|
+
|
78
|
+
.xray-specimen-handle.TemplateSpecimen {
|
79
|
+
background: rgba(255,50,50,0.8);
|
80
|
+
color: #fff;
|
81
|
+
}
|
82
|
+
|
83
|
+
#xray-bar {
|
84
|
+
position: fixed;
|
85
|
+
left: 0;
|
86
|
+
right: 0;
|
87
|
+
bottom: 0;
|
88
|
+
height: 40px;
|
89
|
+
padding: 0 8px;
|
90
|
+
background: #222;
|
91
|
+
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
92
|
+
font-weight: 200;
|
93
|
+
color: #fff;
|
94
|
+
z-index: 10000;
|
95
|
+
box-shadow: 0 -1px 0 rgba(255,255,255,0.1), inset 0 2px 6px rgba(0,0,0,0.8);
|
96
|
+
background-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3)),
|
97
|
+
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAIAAAACUFjqAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpDRkNBMTUwNzdGRTIxMUUyQjBGQ0NBRTc5RDQ3MEJFNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpDRkNBMTUwODdGRTIxMUUyQjBGQ0NBRTc5RDQ3MEJFNSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkNGQ0ExNTA1N0ZFMjExRTJCMEZDQ0FFNzlENDcwQkU1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkNGQ0ExNTA2N0ZFMjExRTJCMEZDQ0FFNzlENDcwQkU1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+aIv7XwAAAEVJREFUeNpiFBMTY2BgEBISYsAGWICAATdgkZeXB1Lv37/HLo1LAgKYGPACdOl3YIAwHNOpKFw0aTSXokujuZSA0wACDABh2BIyJ1wQkwAAAABJRU5ErkJggg==);
|
98
|
+
}
|
99
|
+
|
100
|
+
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
|
101
|
+
#xray-bar {
|
102
|
+
background-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.3)),
|
103
|
+
url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo0Q0ZGQkRGRTdGRTMxMUUyQjBGQ0NBRTc5RDQ3MEJFNSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo0Q0ZGQkRGRjdGRTMxMUUyQjBGQ0NBRTc5RDQ3MEJFNSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOkNGQ0ExNTA5N0ZFMjExRTJCMEZDQ0FFNzlENDcwQkU1IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkNGQ0ExNTBBN0ZFMjExRTJCMEZDQ0FFNzlENDcwQkU1Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+T6y4zwAAAIZJREFUeNrkkjEKwCAMRWMbERw8gE5O3v9q6lIEbbDQMZJ2Kv0gBOSR8HkqxphzBgDnnDEGJNkRsfc+xmitWWtF8KaUuqZ7EMDee1qutQ4hSGGkl1Kis2utYviYgUfZ4EU+CiP/TV0y/i02l1L6DA3is3n/FjDvHy5bYfxbF8b490fDTgEGAJveOCvuYEabAAAAAElFTkSuQmCC);
|
104
|
+
background-size: auto, 10px 10px;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
#xray-bar .xray-bar-btn {
|
109
|
+
position: relative;
|
110
|
+
color: #fff;
|
111
|
+
margin: 8px 1px;
|
112
|
+
height: 24px;
|
113
|
+
line-height: 24px;
|
114
|
+
padding: 0 8px;
|
115
|
+
float: left;
|
116
|
+
font-size: 14px;
|
117
|
+
cursor: pointer;
|
118
|
+
vertical-align: middle;
|
119
|
+
background-color: #444;
|
120
|
+
background-image: linear-gradient(rgba(0,0,0,0), rgba(0,0,0,0.2));
|
121
|
+
border-radius: 2px;
|
122
|
+
box-shadow: 1px 1px 1px rgba(0,0,0,0.5),
|
123
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.2),
|
124
|
+
inset 0 0 2px rgba(255, 255, 255, 0.2);
|
125
|
+
text-shadow: 0 -1px 0 rgba(0,0,0,0.4);
|
126
|
+
transition: background-color 0.1s;
|
127
|
+
}
|
128
|
+
|
129
|
+
#xray-bar .xray-bar-btn b {
|
130
|
+
position: absolute;
|
131
|
+
display: block;
|
132
|
+
right: -19px;
|
133
|
+
top: 0;
|
134
|
+
width: 20px;
|
135
|
+
height: 24px;
|
136
|
+
z-index: 10;
|
137
|
+
overflow: hidden;
|
138
|
+
font-size: 44px;
|
139
|
+
line-height: 19px;
|
140
|
+
text-indent: -7px;
|
141
|
+
}
|
142
|
+
|
143
|
+
#xray-bar .xray-bar-btn b:before {
|
144
|
+
content: "";
|
145
|
+
width: 18px;
|
146
|
+
height: 18px;
|
147
|
+
display: block;
|
148
|
+
position: absolute;
|
149
|
+
left: -9px;
|
150
|
+
top: 3px;
|
151
|
+
border-radius: 2px;
|
152
|
+
box-shadow: 1px -1px 1px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255, 255, 255, 0.2), inset 0 0 2px rgba(255, 255, 255, 0.2);
|
153
|
+
background-image: linear-gradient(135deg, rgba(0,0,0,0), rgba(0,0,0,0.2));
|
154
|
+
-webkit-transform: rotate(45deg);
|
155
|
+
transform: rotate(45deg);
|
156
|
+
transition: background-color 0.1s;
|
157
|
+
}
|
158
|
+
|
159
|
+
#xray-bar .xray-bar-btn:hover {
|
160
|
+
background-color: #555;
|
161
|
+
}
|
162
|
+
|
163
|
+
#xray-bar #xray-bar-controller-path {
|
164
|
+
margin-right: 20px;
|
165
|
+
}
|
166
|
+
|
167
|
+
#xray-bar #xray-bar-controller-path .xray-bar-btn {
|
168
|
+
border-radius: 2px 0 0 2px;
|
169
|
+
padding: 0 6px 0 16px;
|
170
|
+
margin: 8px 1px 8px 0;
|
171
|
+
}
|
172
|
+
|
173
|
+
#xray-bar #xray-bar-controller-path .xray-bar-btn:first-child {
|
174
|
+
padding-left: 8px;
|
175
|
+
}
|
176
|
+
|
177
|
+
#xray-bar #xray-bar-controller-path .xray-bar-btn:last-child {
|
178
|
+
border-radius: 2px;
|
179
|
+
padding-right: 10px;
|
180
|
+
}
|
181
|
+
|
182
|
+
#xray-bar-controller-path .xray-bar-controller { padding-left: 6px; }
|
183
|
+
#xray-bar-controller-path .xray-bar-controller,
|
184
|
+
#xray-bar-controller-path .xray-bar-controller b:before { background-color: #444; }
|
185
|
+
#xray-bar-controller-path .xray-bar-controller:hover,
|
186
|
+
#xray-bar-controller-path .xray-bar-controller:hover b:before { background-color: #555; }
|
187
|
+
#xray-bar-controller-path .xray-bar-controller-action { color: #ddd; }
|
188
|
+
#xray-bar-controller-path .xray-bar-layout,
|
189
|
+
#xray-bar-controller-path .xray-bar-layout b:before { background-color: #c12e27; }
|
190
|
+
#xray-bar-controller-path .xray-bar-layout:hover,
|
191
|
+
#xray-bar-controller-path .xray-bar-layout:hover b:before { background-color: #de362d; }
|
192
|
+
#xray-bar-controller-path .xray-bar-view,
|
193
|
+
#xray-bar-controller-path .xray-bar-view b:before { background-color: #ff2c1e; }
|
194
|
+
#xray-bar-controller-path .xray-bar-view:hover,
|
195
|
+
#xray-bar-controller-path .xray-bar-view:hover b:before { background-color: #ff4c36; }
|
196
|
+
|
197
|
+
#xray-bar #xray-bar-togglers {
|
198
|
+
float: left;
|
199
|
+
margin-left: 20px;
|
200
|
+
}
|
201
|
+
|
202
|
+
#xray-bar #xray-bar-togglers .xray-bar-btn {
|
203
|
+
border-radius: 0;
|
204
|
+
margin-right: 0;
|
205
|
+
color: #999;
|
206
|
+
}
|
207
|
+
|
208
|
+
#xray-bar #xray-bar-togglers .xray-bar-btn:first-child {
|
209
|
+
border-radius: 2px 0 0 2px;
|
210
|
+
}
|
211
|
+
|
212
|
+
#xray-bar #xray-bar-togglers .xray-bar-btn:last-child {
|
213
|
+
border-radius: 0 2px 2px 0;
|
214
|
+
}
|
215
|
+
|
216
|
+
#xray-bar #xray-bar-togglers .xray-bar-btn:before {
|
217
|
+
font-size: 9px;
|
218
|
+
vertical-align: middle;
|
219
|
+
margin-bottom: 1px;
|
220
|
+
margin-right: 5px;
|
221
|
+
background: rgba(255,255,255,0.2);
|
222
|
+
color: #eee;
|
223
|
+
padding: 2px 4px;
|
224
|
+
text-shadow: none;
|
225
|
+
}
|
226
|
+
|
227
|
+
#xray-bar #xray-bar-togglers .xray-bar-btn.active {
|
228
|
+
background: #555;
|
229
|
+
color: #fff;
|
230
|
+
}
|
231
|
+
|
232
|
+
#xray-bar #xray-bar-togglers .xray-bar-templates-toggler:before { content: 'HTML'; }
|
233
|
+
#xray-bar #xray-bar-togglers .xray-bar-templates-toggler.active:before { background: red; }
|
234
|
+
#xray-bar #xray-bar-togglers .xray-bar-views-toggler:before { content: 'JS'; }
|
235
|
+
#xray-bar #xray-bar-togglers .xray-bar-views-toggler.active:before { background: #fff; color: #333; }
|
236
|
+
#xray-bar #xray-bar-togglers .xray-bar-styles-toggler:before { content: 'CSS'; }
|
237
|
+
|
238
|
+
#xray-bar #xray-bar-togglers .xray-icon-search:before {
|
239
|
+
font-size: 16px;
|
240
|
+
background: none;
|
241
|
+
padding: 0;
|
242
|
+
margin: 0;
|
243
|
+
}
|
244
|
+
|
245
|
+
#xray-bar .xray-bar-settings-btn {
|
246
|
+
position: absolute;
|
247
|
+
right: 10px;
|
248
|
+
top: 10px;
|
249
|
+
color: #666;
|
250
|
+
cursor: pointer;
|
251
|
+
text-shadow: 0 1px 0 #000;
|
252
|
+
font-size: 16px;
|
253
|
+
-webkit-touch-callout: none;
|
254
|
+
-webkit-user-select: none;
|
255
|
+
-khtml-user-select: none;
|
256
|
+
-moz-user-select: none;
|
257
|
+
-ms-user-select: none;
|
258
|
+
user-select: none;
|
259
|
+
}
|
260
|
+
|
261
|
+
#xray-bar .xray-bar-settings-btn:hover {
|
262
|
+
color: #fff;
|
263
|
+
}
|
264
|
+
|
265
|
+
#xray-settings {
|
266
|
+
position: absolute;
|
267
|
+
right: 0;
|
268
|
+
bottom: 40px;
|
269
|
+
width: 300px;
|
270
|
+
height: 100px;
|
271
|
+
background: rgba(0,0,0,0.9);
|
272
|
+
padding: 10px;
|
273
|
+
font-size: 14px
|
274
|
+
}
|
275
|
+
|
276
|
+
#xray-settings label {
|
277
|
+
display: inline;
|
278
|
+
margin: 2px 10px;
|
279
|
+
padding: 0;
|
280
|
+
}
|
281
|
+
|
282
|
+
#xray-settings input {
|
283
|
+
padding: 5px;
|
284
|
+
display: inline;
|
285
|
+
background: #333;
|
286
|
+
border: 1px solid #666;
|
287
|
+
color: #fff;
|
288
|
+
width: 200px;
|
289
|
+
margin: 0;
|
290
|
+
font-size: 13px;
|
291
|
+
line-height: 13px;
|
292
|
+
border-radius: 3px;
|
293
|
+
vertical-align: middle;
|
294
|
+
}
|
295
|
+
|
296
|
+
#xray-settings button {
|
297
|
+
position: absolute;
|
298
|
+
right: 18px;
|
299
|
+
left: 18px;
|
300
|
+
bottom: 10px;
|
301
|
+
padding: 7px;
|
302
|
+
color: #fff;
|
303
|
+
background: #04be00;
|
304
|
+
text-align: center;
|
305
|
+
cursor: pointer;
|
306
|
+
}
|
307
|
+
|
308
|
+
#xray-settings button:hover {
|
309
|
+
background: #049d00;
|
310
|
+
}
|
311
|
+
|
312
|
+
#xray-settings p {
|
313
|
+
font-size: 12px;
|
314
|
+
color: #666;
|
315
|
+
text-align: center;
|
316
|
+
margin: 10px 0 0 0;
|
317
|
+
}
|
318
|
+
|
319
|
+
#xray-settings .xray-settings-update-msg {
|
320
|
+
margin-left: 12px;
|
321
|
+
}
|
322
|
+
|
323
|
+
|
324
|
+
@font-face {
|
325
|
+
font-family: 'xray-icons';
|
326
|
+
src: url("data:application/octet-stream;base64,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") format('woff'), url("data:application/octet-stream;base64,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JWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbAwLLAAFiAgILAFJiAuRyNHI2EjPDgtsDEssAAWILAJI0IgICBGI0ewACsjYTgtsDIssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbABRWMjIFhiGyFZY7ABRWJgIy4jICA8ijgjIVktsDMssAAWILAJQyAuRyNHI2EgYLAgYGawgGIjICA8ijgtsDQsIyAuRrACJUZSWCA8WS6xJAEUKy2wNSwjIC5GsAIlRlBYIDxZLrEkARQrLbA2LCMgLkawAiVGUlggPFkjIC5GsAIlRlBYIDxZLrEkARQrLbA3LLAuKyMgLkawAiVGUlggPFkusSQBFCstsDgssC8riiAgPLAEI0KKOCMgLkawAiVGUlggPFkusSQBFCuwBEMusCQrLbA5LLAAFrAEJbAEJiAuRyNHI2GwBkUrIyA8IC4jOLEkARQrLbA6LLEJBCVCsAAWsAQlsAQlIC5HI0cjYSCwBCNCsAZFKyCwYFBYILBAUVizAiADIBuzAiYDGllCQiMgR7AEQ7CAYmAgsAArIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbCAYmGwAiVGYTgjIDwjOBshICBGI0ewACsjYTghWbEkARQrLbA7LLAuKy6xJAEUKy2wPCywLyshIyAgPLAEI0IjOLEkARQrsARDLrAkKy2wPSywABUgR7AAI0KyAAEBFRQTLrAqKi2wPiywABUgR7AAI0KyAAEBFRQTLrAqKi2wPyyxAAEUE7ArKi2wQCywLSotsEEssAAWRSMgLiBGiiNhOLEkARQrLbBCLLAJI0KwQSstsEMssgAAOistsEQssgABOistsEUssgEAOistsEYssgEBOistsEcssgAAOystsEgssgABOystsEkssgEAOystsEossgEBOystsEsssgAANystsEwssgABNystsE0ssgEANystsE4ssgEBNystsE8ssgAAOSstsFAssgABOSstsFEssgEAOSstsFIssgEBOSstsFMssgAAPCstsFQssgABPCstsFUssgEAPCstsFYssgEBPCstsFcssgAAOCstsFgssgABOCstsFkssgEAOCstsFossgEBOCstsFsssDArLrEkARQrLbBcLLAwK7A0Ky2wXSywMCuwNSstsF4ssAAWsDArsDYrLbBfLLAxKy6xJAEUKy2wYCywMSuwNCstsGEssDErsDUrLbBiLLAxK7A2Ky2wYyywMisusSQBFCstsGQssDIrsDQrLbBlLLAyK7A1Ky2wZiywMiuwNistsGcssDMrLrEkARQrLbBoLLAzK7A0Ky2waSywMyuwNSstsGossDMrsDYrLbBrLCuwCGWwAyRQeLABFTAtAABLuADIUlixAQGOWbkIAAgAYyCwASNEILADI3CyBCgJRVJEswoLBgQrsQYBRLEkAYhRWLBAiFixBgNEsSYBiFFYuAQAiFixBgFEWVlZWbgB/4WwBI2xBQBE") format('truetype');
|
327
|
+
}
|
328
|
+
|
329
|
+
[class^="xray-icon-"]:before,
|
330
|
+
[class*=" xray-icon-"]:before {
|
331
|
+
font-family: 'xray-icons';
|
332
|
+
font-style: normal;
|
333
|
+
font-weight: normal;
|
334
|
+
speak: none;
|
335
|
+
display: inline-block;
|
336
|
+
text-decoration: inherit;
|
337
|
+
width: 1em;
|
338
|
+
margin-right: 0.1em;
|
339
|
+
text-align: center;
|
340
|
+
line-height: 1em;
|
341
|
+
}
|
342
|
+
|
343
|
+
.xray-icon-cog:before { content: '\e800'; } /* '' */
|
344
|
+
.xray-icon-flash:before { content: '\26a1'; } /* '⚡' */
|
345
|
+
.xray-icon-cancel:before { content: '\2715'; } /* '✕' */
|
346
|
+
.xray-icon-github:before { content: '\f056'; } /* '' */
|
347
|
+
.xray-icon-columns:before { content: '\f0db'; } /* '' */
|
348
|
+
.xray-icon-doc:before { content: '📄'; } /* '\1f4c4' */
|
349
|
+
.xray-icon-search:before { content: '🔍'; } /* '\1f50d' */
|
@@ -0,0 +1,46 @@
|
|
1
|
+
|
2
|
+
<!-- XRAY BAR -->
|
3
|
+
|
4
|
+
<div id="xray-bar" style="display:none">
|
5
|
+
<% if Xray.request_info.present? %>
|
6
|
+
<div id="xray-bar-controller-path">
|
7
|
+
<% if Xray.request_info[:controller] %>
|
8
|
+
<span class="xray-bar-btn xray-bar-controller xray-icon-flash" data-path="<%= Xray.request_info[:controller][:path] %>">
|
9
|
+
<b></b>
|
10
|
+
<%= Xray.request_info[:controller][:name] %><span class="xray-bar-controller-action">#<%= Xray.request_info[:controller][:action] %></span>
|
11
|
+
</span>
|
12
|
+
<% end %>
|
13
|
+
<% if Xray.request_info[:view] %>
|
14
|
+
<% layout_path = lookup_context.find(Xray.request_info[:view][:layout]).identifier %>
|
15
|
+
<span class="xray-bar-btn xray-bar-layout xray-icon-columns" data-path="<%= layout_path %>">
|
16
|
+
<b></b>
|
17
|
+
<%= layout_path.split('/').last %>
|
18
|
+
</span>
|
19
|
+
<span class="xray-bar-btn xray-bar-view xray-icon-doc" data-path="<%= Xray.request_info[:view][:path] %>">
|
20
|
+
<%= Xray.request_info[:view][:path].split('/').last %>
|
21
|
+
</span>
|
22
|
+
<% end %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
25
|
+
<div id="xray-bar-togglers">
|
26
|
+
<span class="xray-bar-btn xray-bar-all-toggler xray-icon-search"></span>
|
27
|
+
<span class="xray-bar-btn xray-bar-templates-toggler">
|
28
|
+
templates
|
29
|
+
</span>
|
30
|
+
<span class="xray-bar-btn xray-bar-views-toggler">
|
31
|
+
views
|
32
|
+
</span>
|
33
|
+
</div>
|
34
|
+
<span class="xray-bar-settings-btn xray-icon-cog"></span>
|
35
|
+
<div id="xray-settings" style="display:none">
|
36
|
+
<form>
|
37
|
+
<label for="xray-editor-input">Editor</label>
|
38
|
+
<input id="xray-editor-input" type="text" value="<%= Xray.config.editor %>">
|
39
|
+
<button type="submit">Save</button>
|
40
|
+
<p>Settings are saved to ~/.xrayconfig</p>
|
41
|
+
</form>
|
42
|
+
</div>
|
43
|
+
<%= stylesheet_link_tag :xray %>
|
44
|
+
</div>
|
45
|
+
|
46
|
+
<!-- /XRAY BAR -->
|
data/lib/xray/config.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Xray
|
2
|
+
|
3
|
+
def self.config
|
4
|
+
@@config ||= Config.new
|
5
|
+
end
|
6
|
+
|
7
|
+
class Config
|
8
|
+
attr_accessor :editor
|
9
|
+
|
10
|
+
CONFIG_FILE = "#{Dir.home}/.xrayconfig"
|
11
|
+
DEFAULT_EDITOR = '/usr/local/bin/subl'
|
12
|
+
|
13
|
+
def editor
|
14
|
+
load_config[:editor]
|
15
|
+
end
|
16
|
+
|
17
|
+
def editor=(new_editor)
|
18
|
+
if new_editor && new_editor != editor
|
19
|
+
write_config(editor: new_editor)
|
20
|
+
true
|
21
|
+
else
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_yaml
|
27
|
+
{editor: editor}.to_yaml
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def write_config(new_config)
|
33
|
+
config = load_config.merge(new_config)
|
34
|
+
File.open(CONFIG_FILE, 'w') { |f| f.write(config.to_yaml) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_config
|
38
|
+
default_config.merge(local_config)
|
39
|
+
end
|
40
|
+
|
41
|
+
def local_config
|
42
|
+
YAML.load_file(CONFIG_FILE)
|
43
|
+
rescue
|
44
|
+
{}
|
45
|
+
end
|
46
|
+
|
47
|
+
def default_config
|
48
|
+
{ editor: DEFAULT_EDITOR }
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
end
|
data/lib/xray/engine.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module Xray
|
2
|
+
|
3
|
+
# This is the main point of integration with Rails. This engine hooks into
|
4
|
+
# Sprockets and monkey patches ActionView in order to augment the app's JS
|
5
|
+
# and HTML templates with filepath information that can be used by xray.js
|
6
|
+
# in the browser. It also hooks in a middleware responsible for injecting
|
7
|
+
# xray.js and the xray bar into the app's response bodies.
|
8
|
+
class Engine < ::Rails::Engine
|
9
|
+
initializer "xray.initialize" do |app|
|
10
|
+
app.middleware.use Xray::Middleware
|
11
|
+
|
12
|
+
# Register as a Sprockets processor to augment JS files, including
|
13
|
+
# compiled coffeescript, with filepath information. See
|
14
|
+
# `Xray.augment_js` for details.
|
15
|
+
app.assets.register_postprocessor 'application/javascript', :xray do |context, data|
|
16
|
+
path = context.pathname.to_s
|
17
|
+
if path =~ /^#{app.root}.+\.(js|coffee)(\.|$)/
|
18
|
+
Xray.augment_js(data, path)
|
19
|
+
else
|
20
|
+
data
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Monkey patch ActionView::Template to augment server-side templates
|
25
|
+
# with filepath information. See `Xray.augment_template` for details.
|
26
|
+
ActionView::Template.class_eval do
|
27
|
+
def render_with_xray(*args, &block)
|
28
|
+
path = identifier
|
29
|
+
source = render_without_xray(*args, &block)
|
30
|
+
if path =~ /\.(html|slim|haml)(\.|$)/ && !path.include?('_xray_bar')
|
31
|
+
Xray.augment_template(source, path)
|
32
|
+
else
|
33
|
+
source
|
34
|
+
end
|
35
|
+
end
|
36
|
+
alias_method_chain :render, :xray
|
37
|
+
end
|
38
|
+
|
39
|
+
# Augment JS templates
|
40
|
+
app.assets.register_preprocessor 'application/javascript', :xray do |context, source|
|
41
|
+
path = context.pathname.to_s
|
42
|
+
if path =~ /^#{app.root}.+\.(jst)(\.|$)/
|
43
|
+
Xray.augment_template(source, path)
|
44
|
+
else
|
45
|
+
source
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# This event is called near the beginning of a request cycle. We use it to
|
50
|
+
# collect information about the controller and action that is responding, for
|
51
|
+
# display in the Xray bar.
|
52
|
+
ActiveSupport::Notifications.subscribe('start_processing.action_controller') do |*args|
|
53
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
54
|
+
controller_name = event.payload[:controller]
|
55
|
+
action_name = event.payload[:action]
|
56
|
+
path = ActiveSupport::Dependencies.search_for_file(controller_name.underscore)
|
57
|
+
|
58
|
+
# Reset the request info hash for this request.
|
59
|
+
# NOTE: Nothing about this is thread-safe. Could this affect anyone in dev mode?
|
60
|
+
Xray.request_info.clear
|
61
|
+
|
62
|
+
Xray.request_info[:controller] = {
|
63
|
+
:path => path,
|
64
|
+
:name => controller_name,
|
65
|
+
:action => action_name
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# This event is called each time during the request cycle that
|
70
|
+
# ActionView renders a template. The first time it's called will most
|
71
|
+
# likely be the view the controller is rendering, which is what we're
|
72
|
+
# interested in.
|
73
|
+
ActiveSupport::Notifications.subscribe('render_template.action_view') do |*args|
|
74
|
+
event = ActiveSupport::Notifications::Event.new(*args)
|
75
|
+
layout = event.payload[:layout]
|
76
|
+
path = event.payload[:identifier]
|
77
|
+
|
78
|
+
# We are only interested in the first notification that has a layout.
|
79
|
+
if layout
|
80
|
+
Xray.request_info[:view] ||= {
|
81
|
+
:path => path,
|
82
|
+
:layout => layout
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "open3"
|
2
|
+
|
3
|
+
module Xray
|
4
|
+
OPEN_PATH = '/_xray/open'
|
5
|
+
UPDATE_CONFIG_PATH = '/_xray/config'
|
6
|
+
|
7
|
+
# This middleware is responsible for injecting xray.js, xray-backbone.js, and
|
8
|
+
# the Xray bar into the app's pages. It also listens for requests to open files
|
9
|
+
# with the user's editor.
|
10
|
+
class Middleware
|
11
|
+
def initialize(app)
|
12
|
+
@app = app
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(env)
|
16
|
+
# Request for opening a file path.
|
17
|
+
if env['PATH_INFO'] == OPEN_PATH
|
18
|
+
req, res = Rack::Request.new(env), Rack::Response.new
|
19
|
+
out, err, status = Xray.open_file(req.GET['path'])
|
20
|
+
if status.success?
|
21
|
+
res.status = 200
|
22
|
+
else
|
23
|
+
res.write out
|
24
|
+
res.status = 500
|
25
|
+
end
|
26
|
+
res.finish
|
27
|
+
elsif env['PATH_INFO'] == UPDATE_CONFIG_PATH
|
28
|
+
req, res = Rack::Request.new(env), Rack::Response.new
|
29
|
+
if req.post? && Xray.config.editor = req.POST['editor']
|
30
|
+
res.status = 200
|
31
|
+
else
|
32
|
+
res.status = 400
|
33
|
+
end
|
34
|
+
res.finish
|
35
|
+
# Inject xray.js and friends if it's a plain ol' successful HTML request.
|
36
|
+
else
|
37
|
+
status, headers, response = @app.call(env)
|
38
|
+
if should_inject_xray?(status, headers, response)
|
39
|
+
body = response.body.sub(/<body[^>]*>/) { "#{$~}\n#{xray_bar}" }
|
40
|
+
append_js!(body, 'jquery', :xray)
|
41
|
+
append_js!(body, 'backbone', :'xray-backbone')
|
42
|
+
headers['Content-Length'] = body.bytesize.to_s
|
43
|
+
end
|
44
|
+
[status, headers, (body ? [body] : response)]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def xray_bar
|
51
|
+
ActionController::Base.new.render_to_string(:partial => '/xray_bar').html_safe
|
52
|
+
end
|
53
|
+
|
54
|
+
# Appends the given `script_name` after the `after_script_name`.
|
55
|
+
def append_js!(html, after_script_name, script_name)
|
56
|
+
# Matches:
|
57
|
+
# <script src="/assets/jquery.js"></script>
|
58
|
+
# <script src="/assets/jquery-min.js"></script>
|
59
|
+
# <script src="/assets/jquery.min.1.9.1.js"></script>
|
60
|
+
html.sub!(/<script[^>]+#{after_script_name}([-.]{1}[\d\.]+)?([-.]{1}min)?\.js[^>]+><\/script>/) do
|
61
|
+
h = ActionController::Base.helpers
|
62
|
+
"#{$~}\n" + h.javascript_include_tag(script_name)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def should_inject_xray?(status, headers, response)
|
67
|
+
status == 200 &&
|
68
|
+
html_request?(headers, response) &&
|
69
|
+
!file?(headers) &&
|
70
|
+
!empty?(response) &&
|
71
|
+
!response.body.frozen?
|
72
|
+
end
|
73
|
+
|
74
|
+
def empty?(response)
|
75
|
+
# response may be ["Not Found"], ["Move Permanently"], etc.
|
76
|
+
(response.is_a?(Array) && response.size <= 1) ||
|
77
|
+
!response.respond_to?(:body) || response.body.empty?
|
78
|
+
end
|
79
|
+
|
80
|
+
def file?(headers)
|
81
|
+
headers["Content-Transfer-Encoding"] == "binary"
|
82
|
+
end
|
83
|
+
|
84
|
+
def html_request?(headers, response)
|
85
|
+
headers['Content-Type'] && headers['Content-Type'].include?('text/html') && response.body.include?("<html")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/xray/version.rb
ADDED
data/lib/xray-rails.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require "json"
|
2
|
+
require "active_support/all"
|
3
|
+
require_relative "xray/version"
|
4
|
+
require_relative "xray/config"
|
5
|
+
require_relative "xray/middleware"
|
6
|
+
|
7
|
+
if defined?(Rails) && Rails.env.development?
|
8
|
+
require "xray/engine"
|
9
|
+
end
|
10
|
+
|
11
|
+
module Xray
|
12
|
+
FILE_PLACEHOLDER = '$file'
|
13
|
+
|
14
|
+
# Used to collect request information during each request cycle for use in
|
15
|
+
# the Xray bar.
|
16
|
+
# TODO: there's nothing thread-safe about this. Not sure how big of a deal that is.
|
17
|
+
def self.request_info
|
18
|
+
@request_info ||= {}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Patterns for the kind of JS constructors Xray is interested in knowing the
|
22
|
+
# filepath of. Unforunately, these patterns will result in a lot of false
|
23
|
+
# positives, because we can't only match direct Backbone.View subclasses -
|
24
|
+
# the app's JS may have a more complex class hierarchy than that.
|
25
|
+
CONSTRUCTOR_PATTERNS = [
|
26
|
+
'(?!jQuery|_)[\w\.]+\.extend\({', # Match uses of extend(), excluding jQuery and underscore
|
27
|
+
'\(function\(_super\) {' # Coffeescript-generated constructors
|
28
|
+
]
|
29
|
+
|
30
|
+
# Example matches:
|
31
|
+
# MyView = Backbone.View.extend({ ...
|
32
|
+
# Foo.MyView = Backbone.View.extend({ ...
|
33
|
+
# MyView = (function(_super) { ...
|
34
|
+
#
|
35
|
+
# Captures:
|
36
|
+
# $1 = space before the constructor
|
37
|
+
# $2 = the constructor's name
|
38
|
+
# $3 = the beginning of the constructor function
|
39
|
+
CONSTRUCTOR_REGEX = /^( *)([\w\.]+) *= *(#{CONSTRUCTOR_PATTERNS.join('|')})/
|
40
|
+
|
41
|
+
# Returns augmented JS source where constructors Xray wants to know the
|
42
|
+
# filepath of are captured in such a way that at runtime, xray.js can look
|
43
|
+
# up a view constructor's filepath and name.
|
44
|
+
#
|
45
|
+
# This:
|
46
|
+
# MyView = Backbone.View.extend({ ...
|
47
|
+
#
|
48
|
+
# Becomes:
|
49
|
+
# MyView = (window.XrayPaths||(window.XrayPaths={}))['{"name":"MyView","path":"/path/to/file.js"}'] = Backbone.View.extend({ ...
|
50
|
+
#
|
51
|
+
# A goal here was to not add any new lines to the source so as not to throw
|
52
|
+
# off line numbers if an exception is thrown, hence the odd pattern of
|
53
|
+
# abusing an object set operation in a multiple assignment.
|
54
|
+
#
|
55
|
+
# TODO: This is simple and gets the job done, but is a bit ridiculous.
|
56
|
+
# I've also seen this appear in stack traces :( Would love to find a
|
57
|
+
# way to do this without actually writing to the files.
|
58
|
+
def self.augment_js(source, path)
|
59
|
+
source.gsub(CONSTRUCTOR_REGEX) do
|
60
|
+
space, class_name, func = $1, $2, $3
|
61
|
+
info = {name: class_name, path: path.to_s}
|
62
|
+
xray = "(window.XrayPaths||(window.XrayPaths={}))['#{info.to_json}']"
|
63
|
+
"#{space}#{class_name} = #{xray} = #{func}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns augmented HTML where the source is simply wrapped in an HTML
|
68
|
+
# comment with filepath info. Xray.js uses these comments to associate
|
69
|
+
# elements with the tempaltes that rendered them.
|
70
|
+
#
|
71
|
+
# This:
|
72
|
+
# <div class=".my-element">
|
73
|
+
# ...
|
74
|
+
# </div>
|
75
|
+
#
|
76
|
+
# Becomes:
|
77
|
+
# <!-- XRAY START 123 /path/to/file.html -->
|
78
|
+
# <div class=".my-element">
|
79
|
+
# ...
|
80
|
+
# </div>
|
81
|
+
# <!-- XRAY END 123 -->
|
82
|
+
def self.augment_template(source, path)
|
83
|
+
id = next_id
|
84
|
+
# skim doesn't allow html comments, so use skim's comment syntax if it's skim
|
85
|
+
if path =~ /\.(skim)(\.|$)/
|
86
|
+
augmented = "/!XRAY START #{id} #{path}\n#{source}\n/!XRAY END #{id}"
|
87
|
+
else
|
88
|
+
augmented = "<!--XRAY START #{id} #{path}-->\n#{source}\n<!--XRAY END #{id}-->"
|
89
|
+
end
|
90
|
+
ActiveSupport::SafeBuffer === source ? ActiveSupport::SafeBuffer.new(augmented) : augmented
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.next_id
|
94
|
+
@id = (@id ||= 0) + 1
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.open_file(file)
|
98
|
+
editor = Xray.config.editor
|
99
|
+
cmd = if editor.include?('$file')
|
100
|
+
editor.gsub '$file', file
|
101
|
+
else
|
102
|
+
"#{editor} #{file}"
|
103
|
+
end
|
104
|
+
Open3.capture3(cmd)
|
105
|
+
end
|
106
|
+
end
|
metadata
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: xray-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Brent Dillingham
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-05-01 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.2.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.2.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: coffee-rails
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec-rails
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: sqlite3
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: jquery-rails
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: backbone-rails
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: sass-rails
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: eco
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: capybara
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - '='
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 2.1.0
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - '='
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 2.1.0
|
158
|
+
description: Provides a dev bar and an overlay in-browser to visualize your UI's rendered
|
159
|
+
partials and Backbone views
|
160
|
+
email:
|
161
|
+
- brentdillingham@gmail.com
|
162
|
+
executables: []
|
163
|
+
extensions: []
|
164
|
+
extra_rdoc_files: []
|
165
|
+
files:
|
166
|
+
- app/assets/javascripts/xray-backbone.js.coffee
|
167
|
+
- app/assets/javascripts/xray.js.coffee
|
168
|
+
- app/assets/stylesheets/xray.css
|
169
|
+
- app/views/_xray_bar.html.erb
|
170
|
+
- lib/xray/config.rb
|
171
|
+
- lib/xray/engine.rb
|
172
|
+
- lib/xray/middleware.rb
|
173
|
+
- lib/xray/version.rb
|
174
|
+
- lib/xray-rails.rb
|
175
|
+
- LICENSE
|
176
|
+
- README.md
|
177
|
+
homepage: https://github.com/brentd/xray-rails
|
178
|
+
licenses: []
|
179
|
+
post_install_message:
|
180
|
+
rdoc_options: []
|
181
|
+
require_paths:
|
182
|
+
- lib
|
183
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
184
|
+
none: false
|
185
|
+
requirements:
|
186
|
+
- - ! '>='
|
187
|
+
- !ruby/object:Gem::Version
|
188
|
+
version: '0'
|
189
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
|
+
none: false
|
191
|
+
requirements:
|
192
|
+
- - ! '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
requirements: []
|
196
|
+
rubyforge_project:
|
197
|
+
rubygems_version: 1.8.23
|
198
|
+
signing_key:
|
199
|
+
specification_version: 3
|
200
|
+
summary: Reveal the structure of your UI
|
201
|
+
test_files: []
|