flipper-ui 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +17 -0
  5. data/Guardfile +26 -0
  6. data/LICENSE +22 -0
  7. data/README.md +101 -0
  8. data/Rakefile +7 -0
  9. data/examples/basic.ru +44 -0
  10. data/examples/flipper.html +14 -0
  11. data/examples/flipper.png +0 -0
  12. data/flipper-ui.gemspec +21 -0
  13. data/lib/flipper-ui.rb +1 -0
  14. data/lib/flipper/ui.rb +23 -0
  15. data/lib/flipper/ui/action.rb +172 -0
  16. data/lib/flipper/ui/action_collection.rb +20 -0
  17. data/lib/flipper/ui/actions/features.rb +21 -0
  18. data/lib/flipper/ui/actions/file.rb +17 -0
  19. data/lib/flipper/ui/actions/gate.rb +143 -0
  20. data/lib/flipper/ui/actions/index.rb +17 -0
  21. data/lib/flipper/ui/assets/javascripts/application.coffee +305 -0
  22. data/lib/flipper/ui/assets/javascripts/spine/ajax.coffee +223 -0
  23. data/lib/flipper/ui/assets/javascripts/spine/list.coffee +43 -0
  24. data/lib/flipper/ui/assets/javascripts/spine/local.coffee +16 -0
  25. data/lib/flipper/ui/assets/javascripts/spine/manager.coffee +83 -0
  26. data/lib/flipper/ui/assets/javascripts/spine/relation.coffee +148 -0
  27. data/lib/flipper/ui/assets/javascripts/spine/route.coffee +146 -0
  28. data/lib/flipper/ui/assets/javascripts/spine/spine.coffee +542 -0
  29. data/lib/flipper/ui/assets/javascripts/spine/version +1 -0
  30. data/lib/flipper/ui/assets/stylesheets/application.scss +237 -0
  31. data/lib/flipper/ui/decorators/feature.rb +37 -0
  32. data/lib/flipper/ui/decorators/gate.rb +36 -0
  33. data/lib/flipper/ui/error.rb +10 -0
  34. data/lib/flipper/ui/eruby.rb +11 -0
  35. data/lib/flipper/ui/middleware.rb +66 -0
  36. data/lib/flipper/ui/public/css/application.css +183 -0
  37. data/lib/flipper/ui/public/css/images/animated-overlay.gif +0 -0
  38. data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  39. data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  40. data/lib/flipper/ui/public/css/images/ui-bg_flat_10_000000_40x100.png +0 -0
  41. data/lib/flipper/ui/public/css/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  42. data/lib/flipper/ui/public/css/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  43. data/lib/flipper/ui/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  44. data/lib/flipper/ui/public/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  45. data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  46. data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  47. data/lib/flipper/ui/public/css/images/ui-icons_222222_256x240.png +0 -0
  48. data/lib/flipper/ui/public/css/images/ui-icons_228ef1_256x240.png +0 -0
  49. data/lib/flipper/ui/public/css/images/ui-icons_ef8c08_256x240.png +0 -0
  50. data/lib/flipper/ui/public/css/images/ui-icons_ffd27a_256x240.png +0 -0
  51. data/lib/flipper/ui/public/css/images/ui-icons_ffffff_256x240.png +0 -0
  52. data/lib/flipper/ui/public/css/jquery-ui-1.10.3.slider.min.css +5 -0
  53. data/lib/flipper/ui/public/images/logo.png +0 -0
  54. data/lib/flipper/ui/public/images/remove.png +0 -0
  55. data/lib/flipper/ui/public/js/application.js +544 -0
  56. data/lib/flipper/ui/public/js/handlebars.js +1992 -0
  57. data/lib/flipper/ui/public/js/jquery-ui-1.10.3.slider.min.js +6 -0
  58. data/lib/flipper/ui/public/js/jquery.js +9555 -0
  59. data/lib/flipper/ui/public/js/jquery.min.js +4 -0
  60. data/lib/flipper/ui/public/js/jquery.min.map +1 -0
  61. data/lib/flipper/ui/public/js/spine/ajax.js +320 -0
  62. data/lib/flipper/ui/public/js/spine/list.js +72 -0
  63. data/lib/flipper/ui/public/js/spine/local.js +29 -0
  64. data/lib/flipper/ui/public/js/spine/manager.js +157 -0
  65. data/lib/flipper/ui/public/js/spine/relation.js +260 -0
  66. data/lib/flipper/ui/public/js/spine/route.js +223 -0
  67. data/lib/flipper/ui/public/js/spine/spine.js +927 -0
  68. data/lib/flipper/ui/util.rb +12 -0
  69. data/lib/flipper/ui/version.rb +5 -0
  70. data/lib/flipper/ui/views/index.erb +9 -0
  71. data/lib/flipper/ui/views/layout.erb +161 -0
  72. data/script/bootstrap +21 -0
  73. data/script/server +19 -0
  74. data/script/test +30 -0
  75. data/spec/flipper/ui/decorators/feature_spec.rb +59 -0
  76. data/spec/flipper/ui/decorators/gate_spec.rb +47 -0
  77. data/spec/flipper/ui/util_spec.rb +18 -0
  78. data/spec/flipper/ui_spec.rb +470 -0
  79. data/spec/helper.rb +35 -0
  80. metadata +168 -0
@@ -0,0 +1 @@
1
+ 85bb1f78d698cb293c3107efe595d2f61da312dc
@@ -0,0 +1,237 @@
1
+ /* http://meyerweb.com/eric/tools/css/reset/
2
+ v2.0 | 20110126
3
+ License: none (public domain)
4
+ */
5
+
6
+ html, body, div, span, applet, object, iframe,
7
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8
+ a, abbr, acronym, address, big, cite, code,
9
+ del, dfn, em, img, ins, kbd, q, s, samp,
10
+ small, strike, strong, sub, sup, tt, var,
11
+ b, u, i, center,
12
+ dl, dt, dd, ol, ul, li,
13
+ fieldset, form, label, legend,
14
+ table, caption, tbody, tfoot, thead, tr, th, td,
15
+ article, aside, canvas, details, embed,
16
+ figure, figcaption, footer, header, hgroup,
17
+ menu, nav, output, ruby, section, summary,
18
+ time, mark, audio, video {
19
+ margin: 0;
20
+ padding: 0;
21
+ border: 0;
22
+ font-size: 100%;
23
+ font: inherit;
24
+ vertical-align: baseline;
25
+ }
26
+ /* HTML5 display-role reset for older browsers */
27
+ article, aside, details, figcaption, figure,
28
+ footer, header, hgroup, menu, nav, section {
29
+ display: block;
30
+ }
31
+ body {
32
+ line-height: 1;
33
+ }
34
+ ol, ul {
35
+ list-style: none;
36
+ }
37
+ blockquote, q {
38
+ quotes: none;
39
+ }
40
+ blockquote:before, blockquote:after,
41
+ q:before, q:after {
42
+ content: '';
43
+ content: none;
44
+ }
45
+ table {
46
+ border-collapse: collapse;
47
+ border-spacing: 0;
48
+ }
49
+ /* end of resets */
50
+
51
+ body {
52
+ color:#333;
53
+ font-size:13px;
54
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
55
+ }
56
+
57
+ #header, #content {
58
+ width: 750px;
59
+ margin: 0 auto;
60
+ }
61
+
62
+ #header {
63
+ padding: 29px 0 22px;
64
+ }
65
+
66
+ div#no_features {
67
+ display: none;
68
+ h1 {
69
+ font-size: 2em;
70
+ padding-bottom: 15px;
71
+ }
72
+ p {
73
+ font-size: 1.25em;
74
+ padding-bottom: 5px;
75
+ }
76
+ }
77
+
78
+ div.feature {
79
+ float:left;
80
+ width:723px;
81
+ border:1px solid #e8e8e8;
82
+ -webkit-border-radius:3px;
83
+ margin:0 0 16px;
84
+ padding:12px 10px 12px 17px;
85
+ }
86
+
87
+ div.feature h3 {
88
+ float:left;
89
+ width: 575px;
90
+ font-size: 17px;
91
+ line-height: 17px;
92
+ font-weight: bold;
93
+ margin: 0;
94
+ padding: 5px 0 0;
95
+ }
96
+
97
+ div.feature h3 span.state {
98
+ display: block;
99
+ float: left;
100
+ width: 12px;
101
+ height: 12px;
102
+ margin: 2px 10px 0 0;
103
+ background: #333;
104
+ border: 1px solid #333;
105
+ -webkit-border-radius: 2px;
106
+ }
107
+
108
+ div.feature h3 span.state.on {
109
+ background:#396;
110
+ border-color:#396;
111
+ }
112
+
113
+ div.feature h3 span.state.conditional {
114
+ background:#fc0;
115
+ border-color:#fc0;
116
+ }
117
+
118
+ div.feature form {
119
+ padding: 10px 0;
120
+ }
121
+
122
+ div.feature .slider-range {
123
+ margin: 15px 0;
124
+ }
125
+
126
+ div.feature h3 span.state.off {
127
+ background:#900;
128
+ border-color:#900;
129
+ }
130
+
131
+ div.feature .description {
132
+ font-size: 12px;
133
+ font-weight:normal;
134
+ color: #888;
135
+ }
136
+
137
+ div.feature {
138
+ .edit {
139
+ display:none;
140
+ }
141
+
142
+ &.settings {
143
+ .show {
144
+ display:none;
145
+ }
146
+
147
+ .edit {
148
+ display: block;
149
+ }
150
+ }
151
+
152
+ .stack {
153
+ & > div {
154
+ display:none;
155
+
156
+ &.active {
157
+ display: block;
158
+ }
159
+ }
160
+ }
161
+
162
+ li.member
163
+ {
164
+ line-height: 21px;
165
+ padding: 2px;
166
+ font-weight: bold;
167
+
168
+ &:hover
169
+ {
170
+ background: #f7f7f7;
171
+ }
172
+ }
173
+
174
+ li.member a
175
+ {
176
+ margin-right: 6px;
177
+ cursor: pointer;
178
+ text-decoration: none;
179
+ }
180
+
181
+ div.actions {
182
+ float:left;
183
+ width:148px;
184
+ font-size: 12px;
185
+ }
186
+
187
+ div.actions a, button {
188
+ float:right;
189
+ display: block;
190
+ background: #f8f8f8;
191
+ border: 1px solid #e8e8e8;
192
+ -webkit-border-radius: 2px;
193
+ margin:0 2px 0 0;
194
+ padding: 7px 13px;
195
+ color: #888;
196
+ text-decoration: none;
197
+ font-weight: bold;
198
+ line-height:12px;
199
+ cursor: pointer;
200
+ }
201
+
202
+ div.actions a:hover, button:hover {
203
+ background: #dfdfdf;
204
+ border-color: #d7d7d7;
205
+ }
206
+ }
207
+
208
+ div.tabs ul {
209
+ clear:both;
210
+ margin:0 -10px 0 -17px;
211
+ padding:10px 0 0 17px;
212
+ border-bottom:1px solid #e8e8e8;
213
+
214
+ li {
215
+ display:inline-block;
216
+
217
+ a {
218
+ display: block;
219
+ font-weight:700;
220
+ font-size:12px;
221
+ line-height: 30px;
222
+ color:#999;
223
+ text-decoration: none;
224
+ margin:0 5px -1px 0;
225
+ padding:0 12px 0;
226
+ border:1px solid #e8e8e8;
227
+ -webkit-border-radius:3px 3px 0 0;
228
+ -webkit-font-smoothing: antialiased;
229
+
230
+ &.active {
231
+ color:#555;
232
+ background:#fff;
233
+ border-bottom:1px solid #fff;
234
+ }
235
+ }
236
+ }
237
+ }
@@ -0,0 +1,37 @@
1
+ require 'delegate'
2
+ require 'flipper/ui/decorators/gate'
3
+
4
+ module Flipper
5
+ module UI
6
+ module Decorators
7
+ class Feature < SimpleDelegator
8
+ # Public: The feature being decorated.
9
+ alias_method :feature, :__getobj__
10
+
11
+ # Public: Returns name titleized.
12
+ def pretty_name
13
+ @pretty_name ||= titleize(name)
14
+ end
15
+
16
+ # Public: Returns instance as hash that is ready to be json dumped.
17
+ def as_json
18
+ gate_values = feature.gate_values
19
+ {
20
+ 'id' => name.to_s,
21
+ 'name' => pretty_name,
22
+ 'state' => state.to_s,
23
+ 'description' => description,
24
+ 'gates' => gates.map { |gate|
25
+ Decorators::Gate.new(gate, gate_values[gate.key]).as_json
26
+ },
27
+ }
28
+ end
29
+
30
+ # Private
31
+ def titleize(str)
32
+ str.to_s.split('_').map { |word| word.capitalize }.join(' ')
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ require 'delegate'
2
+
3
+ module Flipper
4
+ module UI
5
+ module Decorators
6
+ class Gate < SimpleDelegator
7
+ # Public: The gate being decorated.
8
+ alias_method :gate, :__getobj__
9
+
10
+ # Public: The value for the gate from the adapter.
11
+ attr_reader :value
12
+
13
+ def initialize(gate, value = nil)
14
+ super gate
15
+ @value = value
16
+ end
17
+
18
+ # Public: Returns instance as hash that is ready to be json dumped.
19
+ def as_json
20
+ value_as_json = case data_type
21
+ when :set
22
+ value.to_a # json doesn't like sets
23
+ else
24
+ value
25
+ end
26
+
27
+ {
28
+ 'key' => gate.key.to_s,
29
+ 'name' => gate.name.to_s,
30
+ 'value' => value_as_json,
31
+ }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,10 @@
1
+ module Flipper
2
+ module UI
3
+ # All flipper ui errors inherit from this.
4
+ Error = Class.new(StandardError)
5
+
6
+ # Raised when a request method (get, post, etc.) is called for an action
7
+ # that does not know how to handle it.
8
+ RequestMethodNotSupported = Class.new(Error)
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ require 'erubis'
2
+
3
+ module Flipper
4
+ module UI
5
+ # Version of erubis just for flipper.
6
+ class Eruby < ::Erubis::Eruby
7
+ # switches '<%= ... %>' to escaped and '<%== ... %>' to unescaped.
8
+ include ::Erubis::EscapeEnhancer
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,66 @@
1
+ require 'rack'
2
+ require 'flipper/ui/action_collection'
3
+
4
+ # Require all actions automatically.
5
+ Flipper::UI.root.join('actions').each_child(false) do |name|
6
+ require "flipper/ui/actions/#{name}"
7
+ end
8
+
9
+ module Flipper
10
+ module UI
11
+ class Middleware
12
+ # Public: Initializes an instance of the UI middleware.
13
+ #
14
+ # app - The app this middleware is included in.
15
+ # flipper_or_block - The Flipper::DSL instance or a block that yields a
16
+ # Flipper::DSL instance to use for all operations.
17
+ #
18
+ # Examples
19
+ #
20
+ # flipper = Flipper.new(...)
21
+ #
22
+ # # using with a normal flipper instance
23
+ # use Flipper::UI::Middleware, flipper
24
+ #
25
+ # # using with a block that yields a flipper instance
26
+ # use Flipper::UI::Middleware, lambda { Flipper.new(...) }
27
+ #
28
+ def initialize(app, flipper_or_block)
29
+ @app = app
30
+
31
+ if flipper_or_block.respond_to?(:call)
32
+ @flipper_block = flipper_or_block
33
+ else
34
+ @flipper = flipper_or_block
35
+ end
36
+
37
+ @action_collection = ActionCollection.new
38
+ @action_collection.add UI::Actions::File
39
+ @action_collection.add UI::Actions::Features
40
+ @action_collection.add UI::Actions::Gate
41
+
42
+ # Catch all, always last.
43
+ @action_collection.add UI::Actions::Index
44
+ end
45
+
46
+ def flipper
47
+ @flipper ||= @flipper_block.call
48
+ end
49
+
50
+ def call(env)
51
+ dup.call!(env)
52
+ end
53
+
54
+ def call!(env)
55
+ request = Rack::Request.new(env)
56
+ action_class = @action_collection.action_for_request(request)
57
+
58
+ if action_class.nil?
59
+ @app.call(env)
60
+ else
61
+ action_class.run(flipper, request)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,183 @@
1
+ /* http://meyerweb.com/eric/tools/css/reset/
2
+ v2.0 | 20110126
3
+ License: none (public domain)
4
+ */
5
+ html, body, div, span, applet, object, iframe,
6
+ h1, h2, h3, h4, h5, h6, p, blockquote, pre,
7
+ a, abbr, acronym, address, big, cite, code,
8
+ del, dfn, em, img, ins, kbd, q, s, samp,
9
+ small, strike, strong, sub, sup, tt, var,
10
+ b, u, i, center,
11
+ dl, dt, dd, ol, ul, li,
12
+ fieldset, form, label, legend,
13
+ table, caption, tbody, tfoot, thead, tr, th, td,
14
+ article, aside, canvas, details, embed,
15
+ figure, figcaption, footer, header, hgroup,
16
+ menu, nav, output, ruby, section, summary,
17
+ time, mark, audio, video {
18
+ margin: 0;
19
+ padding: 0;
20
+ border: 0;
21
+ font-size: 100%;
22
+ font: inherit;
23
+ vertical-align: baseline; }
24
+
25
+ /* HTML5 display-role reset for older browsers */
26
+ article, aside, details, figcaption, figure,
27
+ footer, header, hgroup, menu, nav, section {
28
+ display: block; }
29
+
30
+ body {
31
+ line-height: 1; }
32
+
33
+ ol, ul {
34
+ list-style: none; }
35
+
36
+ blockquote, q {
37
+ quotes: none; }
38
+
39
+ blockquote:before, blockquote:after,
40
+ q:before, q:after {
41
+ content: '';
42
+ content: none; }
43
+
44
+ table {
45
+ border-collapse: collapse;
46
+ border-spacing: 0; }
47
+
48
+ /* end of resets */
49
+ body {
50
+ color: #333;
51
+ font-size: 13px;
52
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; }
53
+
54
+ #header, #content {
55
+ width: 750px;
56
+ margin: 0 auto; }
57
+
58
+ #header {
59
+ padding: 29px 0 22px; }
60
+
61
+ div#no_features {
62
+ display: none; }
63
+ div#no_features h1 {
64
+ font-size: 2em;
65
+ padding-bottom: 15px; }
66
+ div#no_features p {
67
+ font-size: 1.25em;
68
+ padding-bottom: 5px; }
69
+
70
+ div.feature {
71
+ float: left;
72
+ width: 723px;
73
+ border: 1px solid #e8e8e8;
74
+ -webkit-border-radius: 3px;
75
+ margin: 0 0 16px;
76
+ padding: 12px 10px 12px 17px; }
77
+
78
+ div.feature h3 {
79
+ float: left;
80
+ width: 575px;
81
+ font-size: 17px;
82
+ line-height: 17px;
83
+ font-weight: bold;
84
+ margin: 0;
85
+ padding: 5px 0 0; }
86
+
87
+ div.feature h3 span.state {
88
+ display: block;
89
+ float: left;
90
+ width: 12px;
91
+ height: 12px;
92
+ margin: 2px 10px 0 0;
93
+ background: #333;
94
+ border: 1px solid #333;
95
+ -webkit-border-radius: 2px; }
96
+
97
+ div.feature h3 span.state.on {
98
+ background: #396;
99
+ border-color: #396; }
100
+
101
+ div.feature h3 span.state.conditional {
102
+ background: #fc0;
103
+ border-color: #fc0; }
104
+
105
+ div.feature form {
106
+ padding: 10px 0; }
107
+
108
+ div.feature .slider-range {
109
+ margin: 15px 0; }
110
+
111
+ div.feature h3 span.state.off {
112
+ background: #900;
113
+ border-color: #900; }
114
+
115
+ div.feature .description {
116
+ font-size: 12px;
117
+ font-weight: normal;
118
+ color: #888; }
119
+
120
+ div.feature .edit {
121
+ display: none; }
122
+ div.feature.settings .show {
123
+ display: none; }
124
+ div.feature.settings .edit {
125
+ display: block; }
126
+ div.feature .stack > div {
127
+ display: none; }
128
+ div.feature .stack > div.active {
129
+ display: block; }
130
+ div.feature li.member {
131
+ line-height: 21px;
132
+ padding: 2px;
133
+ font-weight: bold; }
134
+ div.feature li.member:hover {
135
+ background: #f7f7f7; }
136
+ div.feature li.member a {
137
+ margin-right: 6px;
138
+ cursor: pointer;
139
+ text-decoration: none; }
140
+ div.feature div.actions {
141
+ float: left;
142
+ width: 148px;
143
+ font-size: 12px; }
144
+ div.feature div.actions a, div.feature button {
145
+ float: right;
146
+ display: block;
147
+ background: #f8f8f8;
148
+ border: 1px solid #e8e8e8;
149
+ -webkit-border-radius: 2px;
150
+ margin: 0 2px 0 0;
151
+ padding: 7px 13px;
152
+ color: #888;
153
+ text-decoration: none;
154
+ font-weight: bold;
155
+ line-height: 12px;
156
+ cursor: pointer; }
157
+ div.feature div.actions a:hover, div.feature button:hover {
158
+ background: #dfdfdf;
159
+ border-color: #d7d7d7; }
160
+
161
+ div.tabs ul {
162
+ clear: both;
163
+ margin: 0 -10px 0 -17px;
164
+ padding: 10px 0 0 17px;
165
+ border-bottom: 1px solid #e8e8e8; }
166
+ div.tabs ul li {
167
+ display: inline-block; }
168
+ div.tabs ul li a {
169
+ display: block;
170
+ font-weight: 700;
171
+ font-size: 12px;
172
+ line-height: 30px;
173
+ color: #999;
174
+ text-decoration: none;
175
+ margin: 0 5px -1px 0;
176
+ padding: 0 12px 0;
177
+ border: 1px solid #e8e8e8;
178
+ -webkit-border-radius: 3px 3px 0 0;
179
+ -webkit-font-smoothing: antialiased; }
180
+ div.tabs ul li a.active {
181
+ color: #555;
182
+ background: #fff;
183
+ border-bottom: 1px solid #fff; }