light_switch 0.1.0
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.
- checksums.yaml +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +209 -0
- data/Rakefile +7 -0
- data/app/assets/config/light_switch_manifest.js +2 -0
- data/app/assets/images/light_switch/light-switch.svg +1 -0
- data/app/assets/javascripts/light_switch/application.js +1 -0
- data/app/assets/stylesheets/light_switch/application.css +97 -0
- data/app/assets/stylesheets/light_switch/normalize.css +427 -0
- data/app/assets/stylesheets/light_switch/skeleton.css +418 -0
- data/app/controllers/light_switch/application_controller.rb +4 -0
- data/app/controllers/light_switch/switches_controller.rb +52 -0
- data/app/models/concerns/light_switch/switch/notifications_concern.rb +23 -0
- data/app/models/light_switch/application_record.rb +5 -0
- data/app/models/light_switch/switch.rb +29 -0
- data/app/views/layouts/light_switch/application.html.erb +30 -0
- data/app/views/light_switch/switches/_form.html.erb +6 -0
- data/app/views/light_switch/switches/_form_errors.html.erb +5 -0
- data/app/views/light_switch/switches/_switch.html.erb +23 -0
- data/app/views/light_switch/switches/index.html.erb +22 -0
- data/config/locales/light_switch.en.yml +13 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20231116015256_create_light_switch_switches.rb +11 -0
- data/lib/light_switch/configuration.rb +5 -0
- data/lib/light_switch/engine.rb +27 -0
- data/lib/light_switch/null_cache.rb +10 -0
- data/lib/light_switch/version.rb +3 -0
- data/lib/light_switch.rb +41 -0
- data/lib/tasks/light_switch_tasks.rake +4 -0
- metadata +223 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Skeleton V2.0.4
|
|
3
|
+
* Copyright 2014, Dave Gamache
|
|
4
|
+
* www.getskeleton.com
|
|
5
|
+
* Free to use under the MIT license.
|
|
6
|
+
* http://www.opensource.org/licenses/mit-license.php
|
|
7
|
+
* 12/29/2014
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/* Table of contents
|
|
12
|
+
––––––––––––––––––––––––––––––––––––––––––––––––––
|
|
13
|
+
- Grid
|
|
14
|
+
- Base Styles
|
|
15
|
+
- Typography
|
|
16
|
+
- Links
|
|
17
|
+
- Buttons
|
|
18
|
+
- Forms
|
|
19
|
+
- Lists
|
|
20
|
+
- Code
|
|
21
|
+
- Tables
|
|
22
|
+
- Spacing
|
|
23
|
+
- Utilities
|
|
24
|
+
- Clearing
|
|
25
|
+
- Media Queries
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
/* Grid
|
|
30
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
31
|
+
.container {
|
|
32
|
+
position: relative;
|
|
33
|
+
width: 100%;
|
|
34
|
+
max-width: 960px;
|
|
35
|
+
margin: 0 auto;
|
|
36
|
+
padding: 0 20px;
|
|
37
|
+
box-sizing: border-box; }
|
|
38
|
+
.column,
|
|
39
|
+
.columns {
|
|
40
|
+
width: 100%;
|
|
41
|
+
float: left;
|
|
42
|
+
box-sizing: border-box; }
|
|
43
|
+
|
|
44
|
+
/* For devices larger than 400px */
|
|
45
|
+
@media (min-width: 400px) {
|
|
46
|
+
.container {
|
|
47
|
+
width: 85%;
|
|
48
|
+
padding: 0; }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* For devices larger than 550px */
|
|
52
|
+
@media (min-width: 550px) {
|
|
53
|
+
.container {
|
|
54
|
+
width: 80%; }
|
|
55
|
+
.column,
|
|
56
|
+
.columns {
|
|
57
|
+
margin-left: 4%; }
|
|
58
|
+
.column:first-child,
|
|
59
|
+
.columns:first-child {
|
|
60
|
+
margin-left: 0; }
|
|
61
|
+
|
|
62
|
+
.one.column,
|
|
63
|
+
.one.columns { width: 4.66666666667%; }
|
|
64
|
+
.two.columns { width: 13.3333333333%; }
|
|
65
|
+
.three.columns { width: 22%; }
|
|
66
|
+
.four.columns { width: 30.6666666667%; }
|
|
67
|
+
.five.columns { width: 39.3333333333%; }
|
|
68
|
+
.six.columns { width: 48%; }
|
|
69
|
+
.seven.columns { width: 56.6666666667%; }
|
|
70
|
+
.eight.columns { width: 65.3333333333%; }
|
|
71
|
+
.nine.columns { width: 74.0%; }
|
|
72
|
+
.ten.columns { width: 82.6666666667%; }
|
|
73
|
+
.eleven.columns { width: 91.3333333333%; }
|
|
74
|
+
.twelve.columns { width: 100%; margin-left: 0; }
|
|
75
|
+
|
|
76
|
+
.one-third.column { width: 30.6666666667%; }
|
|
77
|
+
.two-thirds.column { width: 65.3333333333%; }
|
|
78
|
+
|
|
79
|
+
.one-half.column { width: 48%; }
|
|
80
|
+
|
|
81
|
+
/* Offsets */
|
|
82
|
+
.offset-by-one.column,
|
|
83
|
+
.offset-by-one.columns { margin-left: 8.66666666667%; }
|
|
84
|
+
.offset-by-two.column,
|
|
85
|
+
.offset-by-two.columns { margin-left: 17.3333333333%; }
|
|
86
|
+
.offset-by-three.column,
|
|
87
|
+
.offset-by-three.columns { margin-left: 26%; }
|
|
88
|
+
.offset-by-four.column,
|
|
89
|
+
.offset-by-four.columns { margin-left: 34.6666666667%; }
|
|
90
|
+
.offset-by-five.column,
|
|
91
|
+
.offset-by-five.columns { margin-left: 43.3333333333%; }
|
|
92
|
+
.offset-by-six.column,
|
|
93
|
+
.offset-by-six.columns { margin-left: 52%; }
|
|
94
|
+
.offset-by-seven.column,
|
|
95
|
+
.offset-by-seven.columns { margin-left: 60.6666666667%; }
|
|
96
|
+
.offset-by-eight.column,
|
|
97
|
+
.offset-by-eight.columns { margin-left: 69.3333333333%; }
|
|
98
|
+
.offset-by-nine.column,
|
|
99
|
+
.offset-by-nine.columns { margin-left: 78.0%; }
|
|
100
|
+
.offset-by-ten.column,
|
|
101
|
+
.offset-by-ten.columns { margin-left: 86.6666666667%; }
|
|
102
|
+
.offset-by-eleven.column,
|
|
103
|
+
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
|
|
104
|
+
|
|
105
|
+
.offset-by-one-third.column,
|
|
106
|
+
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
|
|
107
|
+
.offset-by-two-thirds.column,
|
|
108
|
+
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
|
|
109
|
+
|
|
110
|
+
.offset-by-one-half.column,
|
|
111
|
+
.offset-by-one-half.columns { margin-left: 52%; }
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
/* Base Styles
|
|
117
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
118
|
+
/* NOTE
|
|
119
|
+
html is set to 62.5% so that all the REM measurements throughout Skeleton
|
|
120
|
+
are based on 10px sizing. So basically 1.5rem = 15px :) */
|
|
121
|
+
html {
|
|
122
|
+
font-size: 62.5%; }
|
|
123
|
+
body {
|
|
124
|
+
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
|
|
125
|
+
line-height: 1.6;
|
|
126
|
+
font-weight: 400;
|
|
127
|
+
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
128
|
+
color: #222; }
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
/* Typography
|
|
132
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
133
|
+
h1, h2, h3, h4, h5, h6 {
|
|
134
|
+
margin-top: 0;
|
|
135
|
+
margin-bottom: 2rem;
|
|
136
|
+
font-weight: 300; }
|
|
137
|
+
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
|
|
138
|
+
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
|
|
139
|
+
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
|
|
140
|
+
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
|
|
141
|
+
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
|
|
142
|
+
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
|
|
143
|
+
|
|
144
|
+
/* Larger than phablet */
|
|
145
|
+
@media (min-width: 550px) {
|
|
146
|
+
h1 { font-size: 5.0rem; }
|
|
147
|
+
h2 { font-size: 4.2rem; }
|
|
148
|
+
h3 { font-size: 3.6rem; }
|
|
149
|
+
h4 { font-size: 3.0rem; }
|
|
150
|
+
h5 { font-size: 2.4rem; }
|
|
151
|
+
h6 { font-size: 1.5rem; }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
p {
|
|
155
|
+
margin-top: 0; }
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
/* Links
|
|
159
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
160
|
+
a {
|
|
161
|
+
color: #1EAEDB; }
|
|
162
|
+
a:hover {
|
|
163
|
+
color: #0FA0CE; }
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
/* Buttons
|
|
167
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
168
|
+
.button,
|
|
169
|
+
button,
|
|
170
|
+
input[type="submit"],
|
|
171
|
+
input[type="reset"],
|
|
172
|
+
input[type="button"] {
|
|
173
|
+
display: inline-block;
|
|
174
|
+
height: 38px;
|
|
175
|
+
padding: 0 30px;
|
|
176
|
+
color: #555;
|
|
177
|
+
text-align: center;
|
|
178
|
+
font-size: 11px;
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
line-height: 38px;
|
|
181
|
+
letter-spacing: .1rem;
|
|
182
|
+
text-transform: uppercase;
|
|
183
|
+
text-decoration: none;
|
|
184
|
+
white-space: nowrap;
|
|
185
|
+
background-color: transparent;
|
|
186
|
+
border-radius: 4px;
|
|
187
|
+
border: 1px solid #bbb;
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
box-sizing: border-box; }
|
|
190
|
+
.button:hover,
|
|
191
|
+
button:hover,
|
|
192
|
+
input[type="submit"]:hover,
|
|
193
|
+
input[type="reset"]:hover,
|
|
194
|
+
input[type="button"]:hover,
|
|
195
|
+
.button:focus,
|
|
196
|
+
button:focus,
|
|
197
|
+
input[type="submit"]:focus,
|
|
198
|
+
input[type="reset"]:focus,
|
|
199
|
+
input[type="button"]:focus {
|
|
200
|
+
color: #333;
|
|
201
|
+
border-color: #888;
|
|
202
|
+
outline: 0; }
|
|
203
|
+
.button.button-primary,
|
|
204
|
+
button.button-primary,
|
|
205
|
+
input[type="submit"].button-primary,
|
|
206
|
+
input[type="reset"].button-primary,
|
|
207
|
+
input[type="button"].button-primary {
|
|
208
|
+
color: #FFF;
|
|
209
|
+
background-color: #33C3F0;
|
|
210
|
+
border-color: #33C3F0; }
|
|
211
|
+
.button.button-primary:hover,
|
|
212
|
+
button.button-primary:hover,
|
|
213
|
+
input[type="submit"].button-primary:hover,
|
|
214
|
+
input[type="reset"].button-primary:hover,
|
|
215
|
+
input[type="button"].button-primary:hover,
|
|
216
|
+
.button.button-primary:focus,
|
|
217
|
+
button.button-primary:focus,
|
|
218
|
+
input[type="submit"].button-primary:focus,
|
|
219
|
+
input[type="reset"].button-primary:focus,
|
|
220
|
+
input[type="button"].button-primary:focus {
|
|
221
|
+
color: #FFF;
|
|
222
|
+
background-color: #1EAEDB;
|
|
223
|
+
border-color: #1EAEDB; }
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
/* Forms
|
|
227
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
228
|
+
input[type="email"],
|
|
229
|
+
input[type="number"],
|
|
230
|
+
input[type="search"],
|
|
231
|
+
input[type="text"],
|
|
232
|
+
input[type="tel"],
|
|
233
|
+
input[type="url"],
|
|
234
|
+
input[type="password"],
|
|
235
|
+
textarea,
|
|
236
|
+
select {
|
|
237
|
+
height: 38px;
|
|
238
|
+
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
|
|
239
|
+
background-color: #fff;
|
|
240
|
+
border: 1px solid #D1D1D1;
|
|
241
|
+
border-radius: 4px;
|
|
242
|
+
box-shadow: none;
|
|
243
|
+
box-sizing: border-box; }
|
|
244
|
+
/* Removes awkward default styles on some inputs for iOS */
|
|
245
|
+
input[type="email"],
|
|
246
|
+
input[type="number"],
|
|
247
|
+
input[type="search"],
|
|
248
|
+
input[type="text"],
|
|
249
|
+
input[type="tel"],
|
|
250
|
+
input[type="url"],
|
|
251
|
+
input[type="password"],
|
|
252
|
+
textarea {
|
|
253
|
+
-webkit-appearance: none;
|
|
254
|
+
-moz-appearance: none;
|
|
255
|
+
appearance: none; }
|
|
256
|
+
textarea {
|
|
257
|
+
min-height: 65px;
|
|
258
|
+
padding-top: 6px;
|
|
259
|
+
padding-bottom: 6px; }
|
|
260
|
+
input[type="email"]:focus,
|
|
261
|
+
input[type="number"]:focus,
|
|
262
|
+
input[type="search"]:focus,
|
|
263
|
+
input[type="text"]:focus,
|
|
264
|
+
input[type="tel"]:focus,
|
|
265
|
+
input[type="url"]:focus,
|
|
266
|
+
input[type="password"]:focus,
|
|
267
|
+
textarea:focus,
|
|
268
|
+
select:focus {
|
|
269
|
+
border: 1px solid #33C3F0;
|
|
270
|
+
outline: 0; }
|
|
271
|
+
label,
|
|
272
|
+
legend {
|
|
273
|
+
display: block;
|
|
274
|
+
margin-bottom: .5rem;
|
|
275
|
+
font-weight: 600; }
|
|
276
|
+
fieldset {
|
|
277
|
+
padding: 0;
|
|
278
|
+
border-width: 0; }
|
|
279
|
+
input[type="checkbox"],
|
|
280
|
+
input[type="radio"] {
|
|
281
|
+
display: inline; }
|
|
282
|
+
label > .label-body {
|
|
283
|
+
display: inline-block;
|
|
284
|
+
margin-left: .5rem;
|
|
285
|
+
font-weight: normal; }
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
/* Lists
|
|
289
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
290
|
+
ul {
|
|
291
|
+
list-style: circle inside; }
|
|
292
|
+
ol {
|
|
293
|
+
list-style: decimal inside; }
|
|
294
|
+
ol, ul {
|
|
295
|
+
padding-left: 0;
|
|
296
|
+
margin-top: 0; }
|
|
297
|
+
ul ul,
|
|
298
|
+
ul ol,
|
|
299
|
+
ol ol,
|
|
300
|
+
ol ul {
|
|
301
|
+
margin: 1.5rem 0 1.5rem 3rem;
|
|
302
|
+
font-size: 90%; }
|
|
303
|
+
li {
|
|
304
|
+
margin-bottom: 1rem; }
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
/* Code
|
|
308
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
309
|
+
code {
|
|
310
|
+
padding: .2rem .5rem;
|
|
311
|
+
margin: 0 .2rem;
|
|
312
|
+
font-size: 90%;
|
|
313
|
+
white-space: nowrap;
|
|
314
|
+
background: #F1F1F1;
|
|
315
|
+
border: 1px solid #E1E1E1;
|
|
316
|
+
border-radius: 4px; }
|
|
317
|
+
pre > code {
|
|
318
|
+
display: block;
|
|
319
|
+
padding: 1rem 1.5rem;
|
|
320
|
+
white-space: pre; }
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
/* Tables
|
|
324
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
325
|
+
th,
|
|
326
|
+
td {
|
|
327
|
+
padding: 12px 15px;
|
|
328
|
+
text-align: left;
|
|
329
|
+
border-bottom: 1px solid #E1E1E1; }
|
|
330
|
+
th:first-child,
|
|
331
|
+
td:first-child {
|
|
332
|
+
padding-left: 0; }
|
|
333
|
+
th:last-child,
|
|
334
|
+
td:last-child {
|
|
335
|
+
padding-right: 0; }
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
/* Spacing
|
|
339
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
340
|
+
button,
|
|
341
|
+
.button {
|
|
342
|
+
margin-bottom: 1rem; }
|
|
343
|
+
input,
|
|
344
|
+
textarea,
|
|
345
|
+
select,
|
|
346
|
+
fieldset {
|
|
347
|
+
margin-bottom: 1.5rem; }
|
|
348
|
+
pre,
|
|
349
|
+
blockquote,
|
|
350
|
+
dl,
|
|
351
|
+
figure,
|
|
352
|
+
table,
|
|
353
|
+
p,
|
|
354
|
+
ul,
|
|
355
|
+
ol,
|
|
356
|
+
form {
|
|
357
|
+
margin-bottom: 2.5rem; }
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
/* Utilities
|
|
361
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
362
|
+
.u-full-width {
|
|
363
|
+
width: 100%;
|
|
364
|
+
box-sizing: border-box; }
|
|
365
|
+
.u-max-full-width {
|
|
366
|
+
max-width: 100%;
|
|
367
|
+
box-sizing: border-box; }
|
|
368
|
+
.u-pull-right {
|
|
369
|
+
float: right; }
|
|
370
|
+
.u-pull-left {
|
|
371
|
+
float: left; }
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
/* Misc
|
|
375
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
376
|
+
hr {
|
|
377
|
+
margin-top: 3rem;
|
|
378
|
+
margin-bottom: 3.5rem;
|
|
379
|
+
border-width: 0;
|
|
380
|
+
border-top: 1px solid #E1E1E1; }
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
/* Clearing
|
|
384
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
385
|
+
|
|
386
|
+
/* Self Clearing Goodness */
|
|
387
|
+
.container:after,
|
|
388
|
+
.row:after,
|
|
389
|
+
.u-cf {
|
|
390
|
+
content: "";
|
|
391
|
+
display: table;
|
|
392
|
+
clear: both; }
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
/* Media Queries
|
|
396
|
+
–––––––––––––––––––––––––––––––––––––––––––––––––– */
|
|
397
|
+
/*
|
|
398
|
+
Note: The best way to structure the use of media queries is to create the queries
|
|
399
|
+
near the relevant code. For example, if you wanted to change the styles for buttons
|
|
400
|
+
on small devices, paste the mobile query code up in the buttons section and style it
|
|
401
|
+
there.
|
|
402
|
+
*/
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
/* Larger than mobile */
|
|
406
|
+
@media (min-width: 400px) {}
|
|
407
|
+
|
|
408
|
+
/* Larger than phablet (also point when grid becomes active) */
|
|
409
|
+
@media (min-width: 550px) {}
|
|
410
|
+
|
|
411
|
+
/* Larger than tablet */
|
|
412
|
+
@media (min-width: 750px) {}
|
|
413
|
+
|
|
414
|
+
/* Larger than desktop */
|
|
415
|
+
@media (min-width: 1000px) {}
|
|
416
|
+
|
|
417
|
+
/* Larger than Desktop HD */
|
|
418
|
+
@media (min-width: 1200px) {}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module LightSwitch
|
|
2
|
+
class SwitchesController < ApplicationController
|
|
3
|
+
before_action :load_switches, on: %i[index create]
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
@switch = Switch.new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create
|
|
10
|
+
@switch = Switch.new(switch_params.slice(:name))
|
|
11
|
+
|
|
12
|
+
if @switch.save
|
|
13
|
+
redirect_to [:switches], notice: "Switch #{@switch.name} was successfully created.", status: :see_other
|
|
14
|
+
else
|
|
15
|
+
render :index, status: :unprocessable_entity
|
|
16
|
+
end
|
|
17
|
+
rescue ActiveRecord::RecordNotUnique
|
|
18
|
+
@switch.errors.add(:name, "already taken")
|
|
19
|
+
render :index, status: :unprocessable_entity
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update
|
|
23
|
+
@switch = Switch.find(params[:id])
|
|
24
|
+
|
|
25
|
+
if @switch.update(switch_params.slice(:state))
|
|
26
|
+
redirect_to [:switches], notice: "Switch #{@switch.name} was successfully updated.", status: :see_other
|
|
27
|
+
else
|
|
28
|
+
errors = @switch.errors.full_messages.to_sentence
|
|
29
|
+
@switch.restore_attributes
|
|
30
|
+
redirect_to [:switches], alert: "Failed to update Switch #{@switch.name}: #{errors}", status: :see_other
|
|
31
|
+
end
|
|
32
|
+
rescue ActiveRecord::RecordNotFound
|
|
33
|
+
redirect_to [:switches], alert: "Failed to update Switch because it has been deleted.", status: :see_other
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def destroy
|
|
37
|
+
switch = Switch.find_by(id: params[:id])
|
|
38
|
+
switch&.destroy!
|
|
39
|
+
redirect_to [:switches], notice: "Switch #{switch.name} was successfully deleted.", status: :see_other
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def load_switches
|
|
45
|
+
@switches = Switch.ordered
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def switch_params
|
|
49
|
+
params.require(:switch).permit(:name, :state)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module LightSwitch::Switch::NotificationsConcern
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
included do
|
|
5
|
+
after_commit :publish_committed, if: :saved_changes?
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def publish_committed
|
|
11
|
+
event = if previously_new_record?
|
|
12
|
+
:create
|
|
13
|
+
elsif destroyed?
|
|
14
|
+
:destroy
|
|
15
|
+
else
|
|
16
|
+
:update
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
ActiveSupport::Notifications.instrument("#{event}_committed.switch.light_switch", switch: self) do
|
|
20
|
+
# Do nothing; just publishing the event for subscribers
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module LightSwitch
|
|
2
|
+
class Switch < ApplicationRecord
|
|
3
|
+
include NotificationsConcern
|
|
4
|
+
|
|
5
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new("7.2")
|
|
6
|
+
enum :state, {on: "on", off: "off"}
|
|
7
|
+
else
|
|
8
|
+
enum state: {on: "on", off: "off"}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
scope :ordered, -> { order(:name) }
|
|
12
|
+
|
|
13
|
+
validates :name, :state, presence: true
|
|
14
|
+
|
|
15
|
+
before_validation :normalize_name
|
|
16
|
+
|
|
17
|
+
after_save :delete_from_cache
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def delete_from_cache
|
|
22
|
+
LightSwitch.config.cache.delete("#{self.class.name.underscore}/#{name}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def normalize_name
|
|
26
|
+
name.tap(&:strip!).downcase! if name.present?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Light Switch</title>
|
|
5
|
+
<%= csrf_meta_tags %>
|
|
6
|
+
<%= csp_meta_tag %>
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
8
|
+
|
|
9
|
+
<link href="//fonts.googleapis.com/css?family=Raleway:400,300,600" rel="stylesheet" type="text/css">
|
|
10
|
+
|
|
11
|
+
<%= stylesheet_link_tag "light_switch/application", media: "all", data: { turbolinks_track: :reload } %>
|
|
12
|
+
<%= javascript_include_tag "light_switch/application", type: :module, defer: true, data: { turbolinks_track: :reload } %>
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<div class="container">
|
|
16
|
+
<div class="site-header">
|
|
17
|
+
<%= inline_svg_tag("light_switch/light-switch.svg", class: "site-logo") %>
|
|
18
|
+
<h2>Light Switch</h2>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<%= yield %>
|
|
22
|
+
|
|
23
|
+
<div class="site-footer u-full-width">
|
|
24
|
+
Light Switch logo was created by
|
|
25
|
+
<a href="https://thenounproject.com/snjeffries1106">Sara Jeffries</a>
|
|
26
|
+
from the Noun Project.
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</body>
|
|
30
|
+
</html>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<tr id="<%= dom_id(switch)%>">
|
|
2
|
+
<td><%= switch.name %></td>
|
|
3
|
+
<td>
|
|
4
|
+
<%= form_with(model: switch, class: "form-inline") do |form| %>
|
|
5
|
+
<span class="mr-1 switch-state <%= switch.on? ? "switch-state-on" : "switch-state-off" %>">
|
|
6
|
+
<%= switch.state.upcase %>
|
|
7
|
+
</span>
|
|
8
|
+
<%= form.hidden_field :state, value: switch.on? ? "off" : "on" %>
|
|
9
|
+
<%=
|
|
10
|
+
form.submit(
|
|
11
|
+
switch.on? ? "Turn off" : "Turn on",
|
|
12
|
+
class: "turn-#{switch.on? ? 'off' : 'on'}",
|
|
13
|
+
data: { turbo_confirm: t(".confirm.turn_#{switch.on? ? "off" : "on"}") }
|
|
14
|
+
)
|
|
15
|
+
%>
|
|
16
|
+
<% end %>
|
|
17
|
+
</td>
|
|
18
|
+
<td><%= l(switch.created_at, format: :long) %></td>
|
|
19
|
+
<td><%= l(switch.updated_at, format: :long) %></td>
|
|
20
|
+
<td>
|
|
21
|
+
<%= button_to "Delete", switch, method: :delete, data: { turbo_confirm: t(".confirm.delete", name: switch.name) }, form_class: "form-inline" %>
|
|
22
|
+
</td>
|
|
23
|
+
</tr>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<h4>Switches</h4>
|
|
2
|
+
|
|
3
|
+
<% flash.each do |key, msg| %>
|
|
4
|
+
<p class="<%= key %>"><%= msg %></p>
|
|
5
|
+
<% end %>
|
|
6
|
+
|
|
7
|
+
<table id="switches" class="u-full-width">
|
|
8
|
+
<thead>
|
|
9
|
+
<th>Name</th>
|
|
10
|
+
<th>State</th>
|
|
11
|
+
<th>Created At</th>
|
|
12
|
+
<th>Updated At</th>
|
|
13
|
+
<th><!-- Actions --></th>
|
|
14
|
+
</thead>
|
|
15
|
+
<tbody>
|
|
16
|
+
<%= render @switches %>
|
|
17
|
+
</tbody>
|
|
18
|
+
</table>
|
|
19
|
+
|
|
20
|
+
<div id="new-switch">
|
|
21
|
+
<%= render "form", switch: @switch %>
|
|
22
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
en:
|
|
2
|
+
light_switch:
|
|
3
|
+
switches:
|
|
4
|
+
switch:
|
|
5
|
+
confirm:
|
|
6
|
+
delete: >
|
|
7
|
+
Are you sure that you want to delete %{name}?
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
If your code still references this switch, it will behave as if it is turned on.
|
|
11
|
+
|
|
12
|
+
turn_off: Are you sure that you want to turn off this switch?
|
|
13
|
+
turn_on: Are you sure that you want to turn on this switch?
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class CreateLightSwitchSwitches < ActiveRecord::Migration["#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}"]
|
|
2
|
+
def change
|
|
3
|
+
create_table :light_switch_switches do |t|
|
|
4
|
+
t.string :name, null: false
|
|
5
|
+
t.string :state, null: false, default: "on"
|
|
6
|
+
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
add_index :light_switch_switches, :name, unique: true
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "inline_svg"
|
|
2
|
+
require "turbo-rails"
|
|
3
|
+
|
|
4
|
+
module LightSwitch
|
|
5
|
+
class Engine < ::Rails::Engine
|
|
6
|
+
isolate_namespace LightSwitch
|
|
7
|
+
|
|
8
|
+
config.before_configuration { LightSwitch.configure_defaults }
|
|
9
|
+
|
|
10
|
+
initializer "light_switch.assets.precompile" do |app|
|
|
11
|
+
app.config.assets.precompile += [
|
|
12
|
+
"light_switch/application.css",
|
|
13
|
+
"light_switch/application.js",
|
|
14
|
+
"light_switch/light-switch.svg"
|
|
15
|
+
]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
initializer "light_switch.create_switches", after: :load_config_initializers do
|
|
19
|
+
ActiveSupport.on_load(:active_record) do
|
|
20
|
+
LightSwitch.config.switches.each do |name|
|
|
21
|
+
Switch.find_or_create_by!(name: name.to_s) if Switch.table_exists?
|
|
22
|
+
rescue ActiveRecord::RecordNotUnique
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|