flipper-ui 0.2.0.beta1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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; }