修订版 | 233ca9d157561caa2264cd7f1815b8c5e10e4d51 (tree) |
---|---|
时间 | 2017-08-07 12:27:41 |
作者 | HMML <hmml3939@gmai...> |
Commiter | HMML |
Implement theme editor.
@@ -22,6 +22,8 @@ | ||
22 | 22 | |
23 | 23 | /node_modules |
24 | 24 | /yarn-error.log |
25 | +/public/packs | |
26 | +/public/system | |
25 | 27 | |
26 | 28 | /.vagrant |
27 | 29 | /erd.* |
@@ -36,5 +38,3 @@ | ||
36 | 38 | *.orig |
37 | 39 | *.tmp |
38 | 40 | *.old |
39 | -/public/packs | |
40 | -/node_modules |
@@ -42,6 +42,8 @@ gem 'kaminari-i18n' | ||
42 | 42 | gem 'font-awesome-rails' |
43 | 43 | gem 'bootstrap', '~> 4.0.0.alpha6' |
44 | 44 | gem 'popper_js' |
45 | +gem 'tether-rails' | |
46 | +gem 'i18n-js' | |
45 | 47 | gem 'enum_help' |
46 | 48 | gem 'jquery-turbolinks' |
47 | 49 | gem 'yaml_db' |
@@ -53,6 +55,8 @@ gem 'devise' | ||
53 | 55 | gem 'omniauth-twitter' |
54 | 56 | gem 'daemons' |
55 | 57 | gem 'exception_notification', group: :production |
58 | +gem 'paperclip' | |
59 | +gem 'rqrcode', github: 'sugi/rqrcode', branch: 'inline-svg' | |
56 | 60 | |
57 | 61 | group :development, :test do |
58 | 62 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console |
@@ -6,6 +6,14 @@ GIT | ||
6 | 6 | rb-inotify (0.9.7) |
7 | 7 | ffi (>= 0.5.0) |
8 | 8 | |
9 | +GIT | |
10 | + remote: git://github.com/sugi/rqrcode.git | |
11 | + revision: 74692545b8335b5ab9499f24a7e8cfbe4a01892f | |
12 | + branch: inline-svg | |
13 | + specs: | |
14 | + rqrcode (0.10.1) | |
15 | + chunky_png (~> 1.0) | |
16 | + | |
9 | 17 | GEM |
10 | 18 | remote: https://rubygems.org/ |
11 | 19 | specs: |
@@ -86,6 +94,10 @@ GEM | ||
86 | 94 | xpath (~> 2.0) |
87 | 95 | childprocess (0.7.1) |
88 | 96 | ffi (~> 1.0, >= 1.0.11) |
97 | + chunky_png (1.3.8) | |
98 | + climate_control (0.1.0) | |
99 | + cocaine (0.5.8) | |
100 | + climate_control (>= 0.0.3, < 1.0) | |
89 | 101 | coderay (1.1.1) |
90 | 102 | coffee-rails (4.2.2) |
91 | 103 | coffee-script (>= 2.2.0) |
@@ -170,6 +182,8 @@ GEM | ||
170 | 182 | nokogiri (>= 1.6.0) |
171 | 183 | ruby_parser (~> 3.5) |
172 | 184 | i18n (0.8.6) |
185 | + i18n-js (3.0.0.rc16) | |
186 | + i18n (~> 0.6, >= 0.6.6) | |
173 | 187 | jbuilder (2.7.0) |
174 | 188 | activesupport (>= 4.2.0) |
175 | 189 | multi_json (>= 1.2) |
@@ -209,6 +223,7 @@ GEM | ||
209 | 223 | mime-types (3.1) |
210 | 224 | mime-types-data (~> 3.2015) |
211 | 225 | mime-types-data (3.2016.0521) |
226 | + mimemagic (0.3.2) | |
212 | 227 | mini_portile2 (2.2.0) |
213 | 228 | minitest (5.10.3) |
214 | 229 | multi_json (1.12.1) |
@@ -235,6 +250,12 @@ GEM | ||
235 | 250 | omniauth-oauth (~> 1.1) |
236 | 251 | rack |
237 | 252 | orm_adapter (0.5.0) |
253 | + paperclip (5.1.0) | |
254 | + activemodel (>= 4.2.0) | |
255 | + activesupport (>= 4.2.0) | |
256 | + cocaine (~> 0.5.5) | |
257 | + mime-types | |
258 | + mimemagic (~> 0.3.0) | |
238 | 259 | popper_js (1.10.8) |
239 | 260 | pry (0.10.4) |
240 | 261 | coderay (~> 1.1.0) |
@@ -359,6 +380,8 @@ GEM | ||
359 | 380 | sshkit-backend-docker (0.1.2) |
360 | 381 | sshkit (>= 1.9.0) |
361 | 382 | temple (0.8.0) |
383 | + tether-rails (1.4.0) | |
384 | + rails (>= 3.1) | |
362 | 385 | therubyracer (0.12.3) |
363 | 386 | libv8 (~> 3.16.14.15) |
364 | 387 | ref |
@@ -424,6 +447,7 @@ DEPENDENCIES | ||
424 | 447 | guard-bundler |
425 | 448 | guard-rspec |
426 | 449 | haml-rails |
450 | + i18n-js | |
427 | 451 | jbuilder (~> 2.5) |
428 | 452 | jquery-rails |
429 | 453 | jquery-turbolinks |
@@ -433,6 +457,7 @@ DEPENDENCIES | ||
433 | 457 | mysql2 |
434 | 458 | nokogiri |
435 | 459 | omniauth-twitter |
460 | + paperclip | |
436 | 461 | popper_js |
437 | 462 | pry |
438 | 463 | pry-byebug |
@@ -444,6 +469,7 @@ DEPENDENCIES | ||
444 | 469 | rails-i18n |
445 | 470 | rb-inotify! |
446 | 471 | responders |
472 | + rqrcode! | |
447 | 473 | rspec-rails |
448 | 474 | sass-rails (~> 5.0) |
449 | 475 | sdoc (~> 0.4.0) |
@@ -453,6 +479,7 @@ DEPENDENCIES | ||
453 | 479 | spring-commands-rspec |
454 | 480 | spring-watcher-listen (~> 2.0.0) |
455 | 481 | sqlite3 |
482 | + tether-rails | |
456 | 483 | therubyracer |
457 | 484 | turbolinks (~> 5) |
458 | 485 | uglifier (>= 1.3.0) |
@@ -12,8 +12,10 @@ | ||
12 | 12 | // |
13 | 13 | //= require jquery3 |
14 | 14 | //= require popper |
15 | +//= require tether | |
15 | 16 | //= require jquery.turbolinks |
16 | 17 | //= require jquery_ujs |
17 | 18 | //= require bootstrap-sprockets |
18 | 19 | //= require turbolinks |
20 | +//= require i18n/translations | |
19 | 21 | //= require_tree . |
@@ -0,0 +1 @@ | ||
1 | +# console.log 'Hello, this is common coffee.' |
@@ -0,0 +1,3 @@ | ||
1 | +# Place all the behaviors and hooks related to the matching controller here. | |
2 | +# All this logic will automatically be available in application.js. | |
3 | +# You can use CoffeeScript in this file: http://coffeescript.org/ |
@@ -1,4 +1,10 @@ | ||
1 | 1 | @import "bootstrap"; |
2 | 2 | @import "font-awesome"; |
3 | +@import "tether"; | |
4 | +@import "tether-theme-basic"; | |
5 | + | |
6 | +@import "awesome_print"; | |
3 | 7 | @import "common"; |
8 | + | |
4 | 9 | @import "dist_signals"; |
10 | +@import "drafts"; |
@@ -0,0 +1,8 @@ | ||
1 | +.debug_dump { | |
2 | + kbd { | |
3 | + background-color: transparent; | |
4 | + padding-left: 0; | |
5 | + padding-right: 0; | |
6 | + color: black; | |
7 | + } | |
8 | +} |
@@ -0,0 +1,192 @@ | ||
1 | +.draft-editor { | |
2 | + height: auto; | |
3 | + .draft-editor-table { | |
4 | + table-layout: fixed; | |
5 | + width: 100%; | |
6 | + table-collapse: collapse; | |
7 | + th { text-align: center; } | |
8 | + th, td { border: 1px solid #ccc; position: relative; } | |
9 | + td { | |
10 | + background-color: rgba(0, 0, 0, 0.5); | |
11 | + color: white; | |
12 | + } | |
13 | + .corner { | |
14 | + background-image: linear-gradient(35deg, transparent 49.5%, #ccc 49.5%, #ccc 50.5%, transparent 50.5%, transparent); | |
15 | + .first { text-align: right } | |
16 | + .second { text-align: left; } | |
17 | + .first, .second { padding: 0 1rem; } | |
18 | + } | |
19 | + } | |
20 | + .dropzone { | |
21 | + border-width: 4px; | |
22 | + border-color: #ddd; | |
23 | + border-style: dashed; | |
24 | + border-radius: 5px; | |
25 | + width: 100%; | |
26 | + height: 100%; | |
27 | + position: relative; | |
28 | + } | |
29 | + .dropzone-active { | |
30 | + border-style: solid; | |
31 | + } | |
32 | + .dropzone-accept { | |
33 | + border-color: #6c6; | |
34 | + border-style: solid; | |
35 | + } | |
36 | + .dropzone-reject { | |
37 | + border-color: #c66; | |
38 | + border-style: solid; | |
39 | + } | |
40 | + .user-weather-icon { | |
41 | + height: 120px; | |
42 | + width: 120px; | |
43 | + margin: auto; | |
44 | + position: relative; | |
45 | + vertical-align: middle; | |
46 | + text-align: center; | |
47 | + line-height: 120px; | |
48 | + background-image: linear-gradient(to bottom, rgba(34, 34, 34, 0.733), rgba(34, 34, 34, 0.866) 50%); | |
49 | + border-radius: 8px; | |
50 | + img { | |
51 | + display: block; | |
52 | + position: absolute; | |
53 | + margin: auto; | |
54 | + top: 0; | |
55 | + bottom: 0; | |
56 | + left: 0; | |
57 | + right: 0; | |
58 | + max-width: 97%; | |
59 | + max-height: 97%; | |
60 | + } | |
61 | + } | |
62 | + .weather-icon-progress { | |
63 | + margin: auto; | |
64 | + width: 32px; | |
65 | + height: 32px; | |
66 | + position: absolute; | |
67 | + top: 0; | |
68 | + bottom: 0; | |
69 | + left: 0; | |
70 | + right: 0; | |
71 | + background-image: image-url('loading.gif'); | |
72 | + } | |
73 | + .day-frame-white .user-weather-icon { | |
74 | + background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.66666), rgba(255, 255, 255, 0.933333) 50%); | |
75 | + } | |
76 | + .weather-icon-missing { | |
77 | + text-align: center; | |
78 | + line-height: 1; | |
79 | + .fa { | |
80 | + font-size: 120px; | |
81 | + color: #bbb; | |
82 | + } | |
83 | + } | |
84 | +} | |
85 | +@media (max-width: map-get($grid-breakpoints, lg)) { | |
86 | + .draft-editor { | |
87 | + .user-weather-icon { | |
88 | + min-height: 80px; | |
89 | + } | |
90 | + .weather-icon-missing { | |
91 | + .fa { | |
92 | + font-size: 80px; | |
93 | + } | |
94 | + } | |
95 | + } | |
96 | +} | |
97 | +@media (max-width: map-get($grid-breakpoints, md)) { | |
98 | + .draft-editor { | |
99 | + .user-weather-icon { | |
100 | + min-height: 36px; | |
101 | + } | |
102 | + .weather-icon-missing { | |
103 | + .fa { | |
104 | + font-size: 36px; | |
105 | + } | |
106 | + } | |
107 | + } | |
108 | +} | |
109 | + | |
110 | +.preview-info { | |
111 | + .qr-code { | |
112 | + display: inline-block; | |
113 | + width: 300px; | |
114 | + height: 300px; | |
115 | + position: relative; | |
116 | + svg { width: 100%; height: 100%; } | |
117 | + } | |
118 | +} | |
119 | + | |
120 | +.preview-base { | |
121 | + background-image: image-url('preview-base.png'); | |
122 | + background-size: cover; | |
123 | + background-position: top left; | |
124 | + background-repeat: no-repeat; | |
125 | + height: 640px; | |
126 | + width: 360px; | |
127 | + position: relative; | |
128 | + .widget-frame { | |
129 | + position: relative; | |
130 | + } | |
131 | + .widget-frame1 { | |
132 | + top: 88px; | |
133 | + left: 11px; | |
134 | + width: 338px; | |
135 | + height: 154px; | |
136 | + } | |
137 | + .widget-frame2 { | |
138 | + top: 103px; | |
139 | + left: 11px; | |
140 | + width: 338px; | |
141 | + height: 238px; | |
142 | + } | |
143 | + .day-frame { | |
144 | + position: absolute; | |
145 | + top: 20px; | |
146 | + border-radius: 3px; | |
147 | + width: 164px; | |
148 | + background-image: linear-gradient(to bottom, rgba(34, 34, 34, 0.733), rgba(34, 34, 34, 0.866) 50%); | |
149 | + img { | |
150 | + display: block; | |
151 | + position: absolute; | |
152 | + margin: auto; | |
153 | + top: 0; | |
154 | + bottom: 0; | |
155 | + left: 0; | |
156 | + right: 0; | |
157 | + max-width: 97%; | |
158 | + max-height: 97%; | |
159 | + } | |
160 | + } | |
161 | + .widget-frame1 .day-frame { | |
162 | + height: 111px; | |
163 | + } | |
164 | + .widget-frame2 .day-frame { | |
165 | + height: 195px; | |
166 | + } | |
167 | + .day-frame1 { | |
168 | + left: 2px; | |
169 | + } | |
170 | + .day-frame2 { | |
171 | + left: 171.5px; | |
172 | + } | |
173 | + .frame-white .day-frame { | |
174 | + background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0.66666), rgba(255, 255, 255, 0.933333) 50%); | |
175 | + } | |
176 | + .weather-text { | |
177 | + text-align: center; | |
178 | + bottom: 3px; | |
179 | + position: absolute; | |
180 | + width: 100%; | |
181 | + span { | |
182 | + color: white; | |
183 | + font-size: 12px; | |
184 | + text-shadow: 0.5px 0.5px 2px #000; | |
185 | + display: inline-block; | |
186 | + background-color: rgba(0, 0, 0, 0.2); | |
187 | + border-radius: 8px; | |
188 | + font-weight: bold; | |
189 | + padding: 1px 6px; | |
190 | + } | |
191 | + } | |
192 | +} |
@@ -0,0 +1,53 @@ | ||
1 | +class DraftsController < ApplicationController | |
2 | + before_action :set_draft, only: [:show, :edit, :update, :destroy] | |
3 | + | |
4 | + respond_to :html | |
5 | + | |
6 | + def index | |
7 | + if !signed_in? | |
8 | + @drafts = [] | |
9 | + elsif current_user.admin? | |
10 | + @drafts = Draft.all.page(params[:page]) | |
11 | + else # normal user | |
12 | + @drafts = Draft.where(user_id: current_user.id).page(params[:page]) | |
13 | + end | |
14 | + respond_with(@drafts) | |
15 | + end | |
16 | + | |
17 | + def show | |
18 | + respond_with(@draft, layout: 'preview') | |
19 | + end | |
20 | + | |
21 | + def new | |
22 | + @draft = Draft.create user: current_user | |
23 | + redirect_to edit_draft_path(@draft) | |
24 | + end | |
25 | + | |
26 | + def edit | |
27 | + end | |
28 | + | |
29 | + def create | |
30 | + @draft = Draft.new(draft_params) | |
31 | + @draft.save | |
32 | + respond_with(@draft) | |
33 | + end | |
34 | + | |
35 | + def update | |
36 | + @draft.update(draft_params) | |
37 | + respond_with(@draft) | |
38 | + end | |
39 | + | |
40 | + def destroy | |
41 | + @draft.destroy | |
42 | + respond_with(@draft) | |
43 | + end | |
44 | + | |
45 | + private | |
46 | + def set_draft | |
47 | + @draft = Draft.find_by!(key: params[:id]) | |
48 | + end | |
49 | + | |
50 | + def draft_params | |
51 | + params.require(:draft).permit(:title, :url, :day_frame, *Draft::ICONS.map{|i| "wi_#{i}"}) | |
52 | + end | |
53 | +end |
@@ -0,0 +1,2 @@ | ||
1 | +module DraftsHelper | |
2 | +end |
@@ -0,0 +1,192 @@ | ||
1 | +import React from 'react'; | |
2 | +import PropTypes from 'prop-types'; | |
3 | +import { Button } from 'reactstrap'; | |
4 | +import Dropzone from 'react-dropzone' | |
5 | + | |
6 | + | |
7 | +class DraftImageDropper extends React.Component { | |
8 | + static propTypes = { | |
9 | + url: PropTypes.string.isRequired, | |
10 | + wsym: PropTypes.string.isRequired, | |
11 | + curImage: PropTypes.string, | |
12 | + updateImage: PropTypes.func.isRequired, | |
13 | + } | |
14 | + | |
15 | + constructor (props) { | |
16 | + super(props); | |
17 | + this.state = { | |
18 | + //draft: new DraftStore(props.draft), | |
19 | + uploading: false, | |
20 | + }; | |
21 | + } | |
22 | + | |
23 | + render() { | |
24 | + let on_drop = (okFiles, ngFiles) => { | |
25 | + let remove_preview = (file) => window.URL.revokeObjectURL(file.preview); | |
26 | + let wsym = this.props.wsym; | |
27 | + | |
28 | + if (ngFiles) | |
29 | + ngFiles.forEach(remove_preview); | |
30 | + if (!okFiles) | |
31 | + return; | |
32 | + okFiles.forEach(remove_preview); | |
33 | + | |
34 | + let data = new FormData() | |
35 | + data.set(`draft[wi_${wsym}]`, okFiles[0]); | |
36 | + | |
37 | + this.setState({uploading: true}); | |
38 | + jQuery.ajax({ | |
39 | + url: this.props.url, | |
40 | + method: 'PATCH', | |
41 | + contentType: false, | |
42 | + processData: false, | |
43 | + data: data, | |
44 | + success: (data, status, xhr) => { | |
45 | + this.props.updateImage(wsym, data[`wi_${wsym}_url`]); | |
46 | + this.setState({uploading: false}); | |
47 | + }, | |
48 | + error: () => alert(I18n.t('upload_err')), | |
49 | + }); | |
50 | + } | |
51 | + | |
52 | + if (this.state.uploading) { | |
53 | + return( | |
54 | + <div className="theme-image"> | |
55 | + <div className="weather-icon-progress" /> | |
56 | + </div> | |
57 | + ); | |
58 | + } | |
59 | + | |
60 | + var content = <div className="weather-icon-missing"> | |
61 | + <span className="fa fa-plus-circle" /> | |
62 | + </div>; | |
63 | + if (this.props.curImage) { | |
64 | + content = <div className="user-weather-icon"> | |
65 | + <img src={this.props.curImage} /> | |
66 | + </div>; | |
67 | + } | |
68 | + | |
69 | + return ( | |
70 | + <div className="theme-image"> | |
71 | + <Dropzone accept="image/*" minSize={128} | |
72 | + className="dropzone" | |
73 | + activeClassName="dropzone dropzone-active" | |
74 | + acceptClassName="dropzone dropzone-accept" | |
75 | + rejectClassName="dropzone dropzone-reject" | |
76 | + onDrop={on_drop} | |
77 | + > | |
78 | + {content} | |
79 | + <div className="clearfix"/> | |
80 | + </Dropzone> | |
81 | + </div> | |
82 | + ); | |
83 | + } | |
84 | +} | |
85 | + | |
86 | +class DraftEditor extends React.Component { | |
87 | + static propTypes = { | |
88 | + draft: PropTypes.object.isRequired, | |
89 | + wsyms: PropTypes.array.isRequired, | |
90 | + wsym_rejects: PropTypes.array, | |
91 | + } | |
92 | + | |
93 | + constructor (props) { | |
94 | + super(props); | |
95 | + this.state = { | |
96 | + //draft: new DraftStore(props.draft), | |
97 | + draft: Object.assign({}, props.draft), | |
98 | + }; | |
99 | + } | |
100 | + | |
101 | + updateImage (sym, imageUrl) { | |
102 | + let d = Object.assign({}, this.state.draft) | |
103 | + d[`wi_${sym}_url`] = imageUrl; | |
104 | + this.setState({draft: d}) | |
105 | + } | |
106 | + | |
107 | + updateFrame (value) { | |
108 | + let d = Object.assign({}, this.state.draft) | |
109 | + d["day_frame"] = value; | |
110 | + this.setState({draft: d}) | |
111 | + jQuery.ajax({ | |
112 | + url: d.path, | |
113 | + method: 'PATCH', | |
114 | + data: {'draft[day_frame]': value} | |
115 | + }); | |
116 | + } | |
117 | + | |
118 | + render() { | |
119 | + let d = this.state.draft; | |
120 | + | |
121 | + let header_cols = [] | |
122 | + let wsyms = this.props.wsyms | |
123 | + wsyms.forEach((ws) => { | |
124 | + header_cols.push(<th key={`w-h-${ws}`}> | |
125 | + {I18n.t('weather_symbols.'+ws)} | |
126 | + </th>); | |
127 | + }); | |
128 | + let body_rows = [ | |
129 | + <tr key="w-r-none"> | |
130 | + <th key="w-ch-none">{I18n.t('none')}</th> | |
131 | + {wsyms.map((ws) => | |
132 | + <td key={`w-c-none-${ws}`}> | |
133 | + <DraftImageDropper key={`i-${ws}`} wsym={ws} url={d.path} | |
134 | + curImage={d[`wi_${ws}_url`]} updateImage={this.updateImage.bind(this)} /> | |
135 | + </td>)} | |
136 | + </tr> | |
137 | + ]; | |
138 | + wsyms.forEach((ws1) => { | |
139 | + let cols = [<th key={`w-ch-${ws1}`}>{I18n.t(`weather_symbols.${ws1}`)}</th>]; | |
140 | + wsyms.forEach((ws2) => { | |
141 | + var content = null; | |
142 | + if (ws1 != ws2 && this.props.wsym_rejects && this.props.wsym_rejects.indexOf(ws1+ws2) === -1) { | |
143 | + content = <DraftImageDropper key={'i-'+ws1+ws2} wsym={ws1+ws2} url={d.path} | |
144 | + curImage={d[`wi_${ws1}${ws2}_url`]} updateImage={this.updateImage.bind(this)} /> | |
145 | + } | |
146 | + cols.push( | |
147 | + <td key={`w-c-${ws1}-${ws2}`}> | |
148 | + {content} | |
149 | + </td>); | |
150 | + }); | |
151 | + body_rows.push(<tr key={`w-r-${ws1}`}>{cols}</tr>); | |
152 | + }); | |
153 | + | |
154 | + return( | |
155 | + <div className="draft-editor"> | |
156 | + <div> | |
157 | + <b>{I18n.t('day_frame_type')}</b>: | |
158 | + <div className="form-check form-check-inline"> | |
159 | + <label className="form-check-label"> | |
160 | + <input className="form-check-input" type="radio" name="frame" | |
161 | + checked={d.day_frame == null || d.day_frame == "black"} | |
162 | + onClick={() => this.updateFrame('black')}/> | |
163 | + {I18n.t('day_frames.black')} | |
164 | + </label> | |
165 | + </div> | |
166 | + <div className="form-check form-check-inline"> | |
167 | + <label className="form-check-label"> | |
168 | + <input className="form-check-input" type="radio" name="frame" | |
169 | + checked={d.day_frame == "white"} | |
170 | + onClick={(e) => this.updateFrame('white')}/> | |
171 | + {I18n.t('day_frames.white')} | |
172 | + </label> | |
173 | + </div> | |
174 | + </div> | |
175 | + <table className={`draft-editor-table day-frame-${d.day_frame}`}> | |
176 | + <thead> | |
177 | + <tr> | |
178 | + <th className="corner"> | |
179 | + <div className="first">{I18n.t('editor_table_header.first_weather')}</div> | |
180 | + <div className="second">{I18n.t('editor_table_header.second_weather')}</div> | |
181 | + </th> | |
182 | + {header_cols} | |
183 | + </tr> | |
184 | + </thead> | |
185 | + <tbody>{body_rows}</tbody> | |
186 | + </table> | |
187 | + </div> | |
188 | + ); | |
189 | + } | |
190 | +} | |
191 | + | |
192 | +export default DraftEditor; |
@@ -0,0 +1,15 @@ | ||
1 | +import React from 'react'; | |
2 | +import { Button } from 'reactstrap'; | |
3 | + | |
4 | +class Hello extends React.Component { | |
5 | + render() { | |
6 | + return( | |
7 | + <div> | |
8 | + <Button color="danger">ReactStrap!</Button> | |
9 | + Hello, {this.props.name}! | |
10 | + </div> | |
11 | + ); | |
12 | + } | |
13 | +} | |
14 | + | |
15 | +export default Hello; |
@@ -0,0 +1,11 @@ | ||
1 | +import WebpackerReact from 'webpacker-react'; | |
2 | +import Hello from '../components/hello'; | |
3 | +import DraftEditor from '../components/draft_editor'; | |
4 | + | |
5 | +if (typeof(window.Turbolinks) != "undefined") | |
6 | + Turbolinks.start(); | |
7 | + | |
8 | +WebpackerReact.setup({Hello, DraftEditor}); | |
9 | + | |
10 | +class H2 { | |
11 | +} |
@@ -0,0 +1,78 @@ | ||
1 | +class Draft < ApplicationRecord | |
2 | + EXPIRES_IN = 3.days | |
3 | + KEY_MAX_LEN = 12 | |
4 | + | |
5 | + icons = [ | |
6 | + Forecast::WEATHER_SYM_CLEAR_N, | |
7 | + Forecast::WEATHER_SYM_CLEAR_D, | |
8 | + Forecast::WEATHER_SYM_RAIN, | |
9 | + Forecast::WEATHER_SYM_SNOW, | |
10 | + Forecast::WEATHER_SYM_CLOUD, | |
11 | + Forecast::WEATHER_SYM_THUNDER, | |
12 | + ] | |
13 | + icons += icons.map {|i1| icons.map {|i2| i1 == i2 ? nil : i1+i2 }}.flatten.compact | |
14 | + icons -= [ | |
15 | + Forecast::WEATHER_SYM_CLEAR_N+Forecast::WEATHER_SYM_CLEAR_D, | |
16 | + Forecast::WEATHER_SYM_CLEAR_D+Forecast::WEATHER_SYM_CLEAR_N, | |
17 | + ] | |
18 | + ICONS = icons.freeze | |
19 | + | |
20 | + FILENAME_MAP = { | |
21 | + Forecast::WEATHER_SYM_CLEAR_N => 'ClearN', | |
22 | + Forecast::WEATHER_SYM_CLEAR_D => 'ClearD', | |
23 | + Forecast::WEATHER_SYM_RAIN => 'Rain', | |
24 | + Forecast::WEATHER_SYM_SNOW => 'Snow', | |
25 | + Forecast::WEATHER_SYM_CLOUD => 'Cloud', | |
26 | + Forecast::WEATHER_SYM_THUNDER => 'Thunder', | |
27 | + } | |
28 | + FILENAME_MAP.freeze | |
29 | + | |
30 | + enum day_frame: {white: '@builtin/white', black: '@builtin/black'}, _prefix: 'day_frame' | |
31 | + belongs_to :user, required: false | |
32 | + before_save :renew_expire | |
33 | + after_initialize :set_key | |
34 | + validates :title, :url, length: { maximum: 255 } | |
35 | + validates :url, format: { with: %r{\Ahttps?://[a-z0-9-]+(\.[a-z0-9-]+){1,}/}i }, allow_blank: true | |
36 | + validates :key, uniqueness: true | |
37 | + | |
38 | + icon_styles = {} | |
39 | + [500, 250, 120, 64].each do |size| | |
40 | + icon_styles["webp#{size}"] = {geometry: "#{size}x#{size}>", format: "webp", convert_options: "-quality 75"} | |
41 | + icon_styles["png#{size}"] = {geometry: "#{size}x#{size}>", format: "png"} | |
42 | + end | |
43 | + | |
44 | + ICONS.each do |i| | |
45 | + has_attached_file "wi_#{i}", styles: icon_styles.symbolize_keys | |
46 | + validates_attachment "wi_#{i}", | |
47 | + content_type: {content_type: /\Aimage\/.*\z/}, | |
48 | + size: { less_than: 20.megabyte } | |
49 | + end | |
50 | + | |
51 | + def renew_expire | |
52 | + self.expires_at = Time.now + EXPIRES_IN | |
53 | + end | |
54 | + | |
55 | + def set_key(force = false) | |
56 | + return true if key.present? && !force | |
57 | + loop do | |
58 | + self.key = (SecureRandom.random_number(('z'*KEY_MAX_LEN).to_i(36)) + 1).to_s(36).scan(/.{1,4}/).join("-") | |
59 | + check_cond = {key: self.key} | |
60 | + persisted? and check_cond[:id] = self.id | |
61 | + break unless Draft.where(check_cond).exists? | |
62 | + end | |
63 | + end | |
64 | + | |
65 | + def to_param | |
66 | + key | |
67 | + end | |
68 | + | |
69 | + def as_json(opts = {}) | |
70 | + ret = super(opts) | |
71 | + ret["path"] = Rails.application.routes.url_helpers.draft_path(self, format: :json) | |
72 | + ICONS.each do |i| | |
73 | + public_send("wi_#{i}?") or next | |
74 | + ret["wi_#{i}_url"] = public_send("wi_#{i}").url(opts[:weather_image_style] || :png120) | |
75 | + end | |
76 | + ret | |
77 | + end | |
78 | +end |
@@ -3,10 +3,10 @@ class Forecast < ApplicationRecord | ||
3 | 3 | |
4 | 4 | WEATHER_SYM_CLEAR_D = "d"; |
5 | 5 | WEATHER_SYM_CLEAR_N = "n"; |
6 | - WEATHER_SYM_THUNDER = "t"; | |
7 | - WEATHER_SYM_SNOW = "s"; | |
8 | - WEATHER_SYM_RAIN = "r"; | |
9 | 6 | WEATHER_SYM_CLOUD = "c"; |
7 | + WEATHER_SYM_RAIN = "r"; | |
8 | + WEATHER_SYM_SNOW = "s"; | |
9 | + WEATHER_SYM_THUNDER = "t"; | |
10 | 10 | |
11 | 11 | JMA_LABEL_TO_SYMBOL = { |
12 | 12 | } |
@@ -0,0 +1,2 @@ | ||
1 | +json.extract! draft, :id, :key, :title, :url, :day_frame, :expires_at, :user_id, :created_at, :updated_at | |
2 | +json.url draft_url(draft, format: :json) |
@@ -0,0 +1,8 @@ | ||
1 | += simple_form_for(@draft, remote: true) do |f| | |
2 | + .form-inputs | |
3 | + = f.input :key | |
4 | + = f.input :title | |
5 | + = f.input :url | |
6 | + | |
7 | + .form-actions | |
8 | + = f.button :submit |
@@ -0,0 +1,50 @@ | ||
1 | +- content_for(:title) { t('.title') } | |
2 | + | |
3 | +.actions.pull-right | |
4 | + .btn.btn-secondary.btn-lg{data: {toggle: 'modal', target: '.preview-info-modal'}} | |
5 | + = fa_icon :mobile | |
6 | + = t 'device_preview' | |
7 | + .btn.btn-primary.btn-lg{data: {toggle: 'modal', target: '.produce-modal'}} | |
8 | + = fa_icon 'chevron-circle-right' | |
9 | + = t 'finish_edit' | |
10 | + | |
11 | +%h1= t '.title' | |
12 | + | |
13 | +.clearfix | |
14 | + | |
15 | +- wsyms = Forecast.constants.grep(/^WEATHER_SYM_/).map {|n| Forecast.const_get(n)} | |
16 | + | |
17 | += react_component 'DraftEditor', draft: @draft, wsyms: wsyms, | |
18 | + wsym_rejects: [Forecast::WEATHER_SYM_CLEAR_D + Forecast::WEATHER_SYM_CLEAR_N, Forecast::WEATHER_SYM_CLEAR_N + Forecast::WEATHER_SYM_CLEAR_D] | |
19 | + | |
20 | +.preview-info-modal.modal.fade | |
21 | + .modal-dialog.modal-lg | |
22 | + .modal-content | |
23 | + .modal-header | |
24 | + %h5.modal-title= t 'to_display_preview_title' | |
25 | + %button.close{type: 'button', data: {dismiss: 'modal'}} | |
26 | + %span × | |
27 | + .modal-body | |
28 | + .preview-info | |
29 | + .qr-code.pull-right | |
30 | + = raw RQRCode::QRCode.new(draft_url(@draft)).as_svg(xml_decleration: false) | |
31 | + %p= t 'to_display_preview_desc' | |
32 | + %br | |
33 | + %b= draft_url(@draft) | |
34 | + | |
35 | +.produce-modal.modal.fade | |
36 | + .modal-dialog.modal-lg | |
37 | + .modal-content | |
38 | + .modal-header | |
39 | + %h5.modal-title= t 'to_produce_title' | |
40 | + %button.close{type: 'button', data: {dismiss: 'modal'}} | |
41 | + %span × | |
42 | + .modal-body | |
43 | + .produce | |
44 | + %p= t 'to_produce_desc' | |
45 | + = render 'form' | |
46 | + | |
47 | +:coffee | |
48 | + jQuery('.edit_draft').on 'ajax:success', (xhr, data, status) -> | |
49 | + jQuery(this).html(jQuery(data).find('.edit_draft').html()) | |
50 | + |
@@ -0,0 +1,31 @@ | ||
1 | +%h1 Listing drafts | |
2 | + | |
3 | +%table | |
4 | + %thead | |
5 | + %tr | |
6 | + %th Key | |
7 | + %th Title | |
8 | + %th Url | |
9 | + %th Day frame | |
10 | + %th Expires at | |
11 | + %th User | |
12 | + %th | |
13 | + %th | |
14 | + %th | |
15 | + | |
16 | + %tbody | |
17 | + - @drafts.each do |draft| | |
18 | + %tr | |
19 | + %td= draft.key | |
20 | + %td= draft.title | |
21 | + %td= draft.url | |
22 | + %td= draft.day_frame | |
23 | + %td= draft.expires_at | |
24 | + %td= draft.user | |
25 | + %td= link_to 'Show', draft | |
26 | + %td= link_to 'Edit', edit_draft_path(draft) | |
27 | + %td= link_to 'Destroy', draft, method: :delete, data: { confirm: 'Are you sure?' } | |
28 | + | |
29 | +%br | |
30 | + | |
31 | += link_to 'New Draft', new_draft_path |
@@ -0,0 +1 @@ | ||
1 | +json.array! @drafts, partial: 'drafts/draft', as: :draft |
@@ -0,0 +1,5 @@ | ||
1 | +%h1 New draft | |
2 | + | |
3 | += render 'form' | |
4 | + | |
5 | += link_to 'Back', drafts_path |
@@ -0,0 +1,43 @@ | ||
1 | +- content_for(:title) { t('.title')} | |
2 | + | |
3 | +.alert.alert-info | |
4 | + .btn.btn-primary.pull-right.btn-sm{onClick: 'location.reload()'} | |
5 | + = fa_icon(:refresh) | |
6 | + = t 'reload' | |
7 | + = t('preview_desc') | |
8 | + .clearfix | |
9 | + | |
10 | +:ruby | |
11 | + imgs = {} | |
12 | + @draft.as_json(weather_image_style: request.user_agent.to_s =~ /chrome/i ? :webp500 : :png500).each do |key, val| | |
13 | + key =~ /^wi_(.*)_url/ or next | |
14 | + imgs[$1] = val | |
15 | + end | |
16 | + img1_key = imgs.keys[rand(imgs.keys.length)] | |
17 | + img2_key = imgs.keys[rand(imgs.keys.length)] | |
18 | + img3_key = imgs.keys[rand(imgs.keys.length)] | |
19 | + img4_key = imgs.keys[rand(imgs.keys.length)] | |
20 | + | |
21 | +- if imgs.empty? | |
22 | + .alert.alert-danger= t 'draft_has_no_image' | |
23 | +- else | |
24 | + .preview-base | |
25 | + .widget-frame.widget-frame1{class: "frame-#{@draft.day_frame}"} | |
26 | + .day-frame.day-frame1 | |
27 | + = image_tag imgs[img1_key] | |
28 | + .weather-text | |
29 | + %span= t("wheater_label_samples.#{img1_key}") | |
30 | + .day-frame.day-frame2 | |
31 | + = image_tag imgs[img2_key] | |
32 | + .weather-text | |
33 | + %span= t("wheater_label_samples.#{img2_key}") | |
34 | + .widget-frame.widget-frame2{class: "frame-#{@draft.day_frame}"} | |
35 | + .day-frame.day-frame1 | |
36 | + = image_tag imgs[img3_key] | |
37 | + .weather-text | |
38 | + %span= t("wheater_label_samples.#{img3_key}") | |
39 | + .day-frame.day-frame2 | |
40 | + = image_tag imgs[img4_key] | |
41 | + .weather-text | |
42 | + %span= t("wheater_label_samples.#{img4_key}") | |
43 | + |
@@ -0,0 +1 @@ | ||
1 | +json.partial! "drafts/draft", draft: @draft |
@@ -7,6 +7,7 @@ | ||
7 | 7 | %meta{content: content_for?(:description) ? yield(:description) : t('description'), :name => "description"} |
8 | 8 | = stylesheet_link_tag :application, :media => "all" |
9 | 9 | = javascript_include_tag :application |
10 | + = javascript_pack_tag 'main' | |
10 | 11 | = csrf_meta_tags |
11 | 12 | = yield(:head) |
12 | 13 | %body |
@@ -0,0 +1,14 @@ | ||
1 | +!!! | |
2 | +%html#preview{lang: I18n.locale} | |
3 | + %head | |
4 | + %meta{name: "viewport", content: "width=360, user-scalable=no"} | |
5 | + - page_title = content_for?(:title) ? yield(:title) : t("#{controller_name}.#{action_name}.title", default: "") | |
6 | + %title= page_title.present? ? "#{page_title} - #{t('site_title')}" : t('site_title') | |
7 | + %meta{content: content_for?(:description) ? yield(:description) : t('description'), :name => "description"} | |
8 | + = stylesheet_link_tag :application, :media => "all" | |
9 | + = javascript_include_tag :application | |
10 | + = javascript_pack_tag 'main' | |
11 | + = csrf_meta_tags | |
12 | + = yield(:head) | |
13 | + %body#preview | |
14 | + = yield |
@@ -29,9 +29,9 @@ module MmwPsh | ||
29 | 29 | g.fixture_replacement :factory_girl, dir: "spec/factories" |
30 | 30 | end |
31 | 31 | |
32 | - #config.i18n.default_locale = :en | |
33 | - #config.i18n.available_locales = [:en, :ja] | |
34 | - #config.i18n.enforce_available_locales = true | |
32 | + config.i18n.default_locale = :en | |
33 | + config.i18n.available_locales = [:en, :ja] | |
34 | + config.i18n.enforce_available_locales = true | |
35 | 35 | config.encoding = "utf-8" |
36 | 36 | |
37 | 37 | config.hub_secret_default = '' |
@@ -1,6 +1,6 @@ | ||
1 | 1 | set :sshkit_backend, SSHKit::Backend::Docker |
2 | 2 | set :stage, :production |
3 | -set :branch, 'master' | |
3 | +set :branch, 'production' | |
4 | 4 | set :deploy_to, '/app' |
5 | 5 | set :repo_url, '/src' |
6 | 6 | fetch(:default_env).merge!(rails_env: :production, |
@@ -4,7 +4,7 @@ SimpleForm.setup do |config| | ||
4 | 4 | config.button_class = 'btn btn-primary' |
5 | 5 | config.boolean_label_class = nil |
6 | 6 | |
7 | - config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
7 | + config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-danger' do |b| | |
8 | 8 | b.use :html5 |
9 | 9 | b.use :placeholder |
10 | 10 | b.optional :maxlength |
@@ -12,27 +12,27 @@ SimpleForm.setup do |config| | ||
12 | 12 | b.optional :pattern |
13 | 13 | b.optional :min_max |
14 | 14 | b.optional :readonly |
15 | - b.use :label, class: 'control-label' | |
15 | + b.use :label, class: 'form-control-label' | |
16 | 16 | |
17 | 17 | b.use :input, class: 'form-control' |
18 | - b.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
19 | - b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
18 | + b.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
19 | + b.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
20 | 20 | end |
21 | 21 | |
22 | - config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
22 | + config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-danger' do |b| | |
23 | 23 | b.use :html5 |
24 | 24 | b.use :placeholder |
25 | 25 | b.optional :maxlength |
26 | 26 | b.optional :minlength |
27 | 27 | b.optional :readonly |
28 | - b.use :label, class: 'control-label' | |
28 | + b.use :label, class: 'form-control-label' | |
29 | 29 | |
30 | 30 | b.use :input |
31 | - b.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
32 | - b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
31 | + b.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
32 | + b.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
33 | 33 | end |
34 | 34 | |
35 | - config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
35 | + config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-danger' do |b| | |
36 | 36 | b.use :html5 |
37 | 37 | b.optional :readonly |
38 | 38 |
@@ -40,20 +40,20 @@ SimpleForm.setup do |config| | ||
40 | 40 | ba.use :label_input |
41 | 41 | end |
42 | 42 | |
43 | - b.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
44 | - b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
43 | + b.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
44 | + b.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
45 | 45 | end |
46 | 46 | |
47 | - config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
47 | + config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-danger' do |b| | |
48 | 48 | b.use :html5 |
49 | 49 | b.optional :readonly |
50 | - b.use :label, class: 'control-label' | |
50 | + b.use :label, class: 'form-control-label' | |
51 | 51 | b.use :input |
52 | - b.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
53 | - b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
52 | + b.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
53 | + b.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
54 | 54 | end |
55 | 55 | |
56 | - config.wrappers :horizontal_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
56 | + config.wrappers :horizontal_form, tag: 'div', class: 'form-group row', error_class: 'has-danger' do |b| | |
57 | 57 | b.use :html5 |
58 | 58 | b.use :placeholder |
59 | 59 | b.optional :maxlength |
@@ -61,31 +61,31 @@ SimpleForm.setup do |config| | ||
61 | 61 | b.optional :pattern |
62 | 62 | b.optional :min_max |
63 | 63 | b.optional :readonly |
64 | - b.use :label, class: 'col-3 control-label' | |
64 | + b.use :label, class: 'col-3 form-control-label' | |
65 | 65 | |
66 | 66 | b.wrapper tag: 'div', class: 'col-9' do |ba| |
67 | 67 | ba.use :input, class: 'form-control' |
68 | - ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
69 | - ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
68 | + ba.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
69 | + ba.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
70 | 70 | end |
71 | 71 | end |
72 | 72 | |
73 | - config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
73 | + config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group row', error_class: 'has-danger' do |b| | |
74 | 74 | b.use :html5 |
75 | 75 | b.use :placeholder |
76 | 76 | b.optional :maxlength |
77 | 77 | b.optional :minlength |
78 | 78 | b.optional :readonly |
79 | - b.use :label, class: 'col-3 control-label' | |
79 | + b.use :label, class: 'col-3 form-control-label' | |
80 | 80 | |
81 | 81 | b.wrapper tag: 'div', class: 'col-9' do |ba| |
82 | 82 | ba.use :input |
83 | - ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
84 | - ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
83 | + ba.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
84 | + ba.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
85 | 85 | end |
86 | 86 | end |
87 | 87 | |
88 | - config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
88 | + config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group row', error_class: 'has-danger' do |b| | |
89 | 89 | b.use :html5 |
90 | 90 | b.optional :readonly |
91 | 91 |
@@ -94,25 +94,25 @@ SimpleForm.setup do |config| | ||
94 | 94 | ba.use :label_input |
95 | 95 | end |
96 | 96 | |
97 | - wr.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
98 | - wr.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
97 | + wr.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
98 | + wr.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
99 | 99 | end |
100 | 100 | end |
101 | 101 | |
102 | - config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
102 | + config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group row', error_class: 'has-danger' do |b| | |
103 | 103 | b.use :html5 |
104 | 104 | b.optional :readonly |
105 | 105 | |
106 | - b.use :label, class: 'col-3 control-label' | |
106 | + b.use :label, class: 'col-3 form-control-label' | |
107 | 107 | |
108 | 108 | b.wrapper tag: 'div', class: 'col-9' do |ba| |
109 | 109 | ba.use :input |
110 | - ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
111 | - ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
110 | + ba.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
111 | + ba.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
112 | 112 | end |
113 | 113 | end |
114 | 114 | |
115 | - config.wrappers :inline_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
115 | + config.wrappers :inline_form, tag: 'div', class: 'form-group', error_class: 'has-danger' do |b| | |
116 | 116 | b.use :html5 |
117 | 117 | b.use :placeholder |
118 | 118 | b.optional :maxlength |
@@ -123,32 +123,47 @@ SimpleForm.setup do |config| | ||
123 | 123 | b.use :label, class: 'sr-only' |
124 | 124 | |
125 | 125 | b.use :input, class: 'form-control' |
126 | - b.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
127 | - b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
126 | + b.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
127 | + b.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
128 | 128 | end |
129 | 129 | |
130 | - config.wrappers :multi_select, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| | |
130 | + config.wrappers :multi_select, tag: 'div', class: 'form-group', error_class: 'has-danger' do |b| | |
131 | 131 | b.use :html5 |
132 | 132 | b.optional :readonly |
133 | - b.use :label, class: 'control-label' | |
133 | + b.use :label, class: 'form-control-label' | |
134 | 134 | b.wrapper tag: 'div', class: 'form-inline' do |ba| |
135 | 135 | ba.use :input, class: 'form-control' |
136 | - ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } | |
137 | - ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } | |
136 | + ba.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
137 | + ba.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
138 | 138 | end |
139 | 139 | end |
140 | + | |
141 | + config.wrappers :horizontal_multi_select, tag: 'div', class: 'form-group row', error_class: 'has-danger' do |b| | |
142 | + b.use :html5 | |
143 | + b.optional :readonly | |
144 | + b.use :label, class: 'col-3 form-control-label' | |
145 | + | |
146 | + b.wrapper tag: 'div', class: 'col-9' do |ba| | |
147 | + ba.wrapper tag: 'div', class: 'form-inline' do |bb| | |
148 | + bb.use :input, class: 'form-control' | |
149 | + bb.use :error, wrap_with: { tag: 'span', class: 'form-control-feedback' } | |
150 | + bb.use :hint, wrap_with: { tag: 'p', class: 'form-text text-muted' } | |
151 | + end | |
152 | + end | |
153 | + end | |
154 | + | |
140 | 155 | # Wrappers for forms and inputs using the Bootstrap toolkit. |
141 | 156 | # Check the Bootstrap docs (http://getbootstrap.com) |
142 | 157 | # to learn about the different styles for forms and inputs, |
143 | 158 | # buttons and other elements. |
144 | - config.default_wrapper = :vertical_form | |
159 | + config.default_wrapper = :horizontal_form | |
145 | 160 | config.wrapper_mappings = { |
146 | 161 | check_boxes: :vertical_radio_and_checkboxes, |
147 | 162 | radio_buttons: :vertical_radio_and_checkboxes, |
148 | 163 | file: :vertical_file_input, |
149 | 164 | boolean: :vertical_boolean, |
150 | - datetime: :multi_select, | |
151 | - date: :multi_select, | |
152 | - time: :multi_select | |
165 | + datetime: :horizontal_multi_select, | |
166 | + date: :horizontal_multi_select, | |
167 | + time: :horizontal_multi_select | |
153 | 168 | } |
154 | 169 | end |
@@ -1,5 +1,76 @@ | ||
1 | 1 | en: |
2 | + activerecord: | |
3 | + models: | |
4 | + attributes: | |
5 | + | |
6 | + drafts: | |
7 | + show: | |
8 | + title: Theme preview | |
9 | + edit: | |
10 | + title: Theme editor | |
11 | + | |
2 | 12 | site_title: "MikuMikuWeather" |
3 | 13 | login_with_: "Login with %{provider}" |
4 | 14 | providers: |
15 | + twitter: Twitter | |
16 | + google: Google | |
17 | + facebook: Facebook | |
5 | 18 | line: LINE |
19 | + upload_err: "[Upload failed] Error on server. Please contact to administrator if you try again after some while." | |
20 | + weather_symbols: | |
21 | + d: Clear(Day) | |
22 | + n: Clear(Night) | |
23 | + c: Clouds | |
24 | + r: Rain | |
25 | + s: Snow | |
26 | + t: Thunderstorm | |
27 | + wheater_label_samples: | |
28 | + d: Clear | |
29 | + n: Clear | |
30 | + c: Clouds | |
31 | + r: Rain | |
32 | + s: Snow | |
33 | + t: Thunderstorm | |
34 | + nr: Clear - Rain | |
35 | + ns: Clear - Snow | |
36 | + nc: Clear - Clouds | |
37 | + nt: Clear - Thunderstorm | |
38 | + dr: Clear, Rain | |
39 | + ds: Clear, Snow | |
40 | + dc: Clear, Clouds | |
41 | + dt: Clear, Thunderstorm | |
42 | + rn: Rain, Clear | |
43 | + rd: Rain, Clear | |
44 | + rs: Rain, Snow | |
45 | + rc: Rain, Clouds | |
46 | + rt: Rain, Thunderstorm | |
47 | + sn: Snow, Clear | |
48 | + sd: Snow, Clear | |
49 | + sr: Snow, Rain | |
50 | + sc: Snow, Clouds | |
51 | + st: Snow, Thunderstorm | |
52 | + cn: Clouds, Clear | |
53 | + cd: Clouds, Clear | |
54 | + cr: Clouds, Rain | |
55 | + cs: Clouds, Snow | |
56 | + ct: Clouds, Thunderstorm | |
57 | + tn: Thunderstorm, Clear | |
58 | + td: Thunderstorm, Clear | |
59 | + tr: Thunderstorm, Rain | |
60 | + ts: Thunderstorm, Snow | |
61 | + tc: Thunderstorm, Clouds | |
62 | + editor_table_header: | |
63 | + first_weather: First | |
64 | + second_weather: Second | |
65 | + none: (None) | |
66 | + day_frame_type: Frame type | |
67 | + day_frames: | |
68 | + white: White | |
69 | + black: Black | |
70 | + preview_desc: Please reload to change icons. | |
71 | + draft_has_no_image: This theme draft has no image. Please upload your image before preview! | |
72 | + reload: Reload | |
73 | + to_display_preview_desc: To show widget preview with your uploading fiels, please open following URL or scan QR Code by your Android device. | |
74 | + to_display_preview_title: Preview on your device | |
75 | + device_preview: Device preview | |
76 | + finish_edit: Finish edit | |
\ No newline at end of file |
@@ -9,6 +9,7 @@ Rails.application.routes.draw do | ||
9 | 9 | |
10 | 10 | get 'sub', to: 'subscribe#verify' |
11 | 11 | post 'sub', to: 'subscribe#distribute' |
12 | + resources :drafts, except: %i(destroy) | |
12 | 13 | resources :users, only: %i(index show) |
13 | 14 | resources :forecasts, only: %i(index show) |
14 | 15 | resources :areas, only: %i(index show) |
@@ -20,4 +21,6 @@ Rails.application.routes.draw do | ||
20 | 21 | get 'logout', to: 'welcome#logout' |
21 | 22 | |
22 | 23 | root 'welcome#index' |
24 | + | |
25 | + mount ActionCable.server => '/cable' | |
23 | 26 | end |
@@ -0,0 +1,16 @@ | ||
1 | +class CreateDrafts < ActiveRecord::Migration[5.1] | |
2 | + def change | |
3 | + create_table :drafts do |t| | |
4 | + t.string :key, null: false | |
5 | + t.string :title, null: false, default: '' | |
6 | + t.string :url, null: false, default: '' | |
7 | + t.string :day_frame | |
8 | + t.datetime :expires_at, null: false | |
9 | + t.references :user | |
10 | + | |
11 | + t.timestamps | |
12 | + | |
13 | + t.index :key, unique: true | |
14 | + end | |
15 | + end | |
16 | +end |
@@ -0,0 +1,77 @@ | ||
1 | +class AddAttachmentWiImagesToDrafts < ActiveRecord::Migration[5.1] | |
2 | + def self.up | |
3 | + change_table :drafts do |t| | |
4 | + t.attachment :wi_n | |
5 | + t.attachment :wi_d | |
6 | + t.attachment :wi_r | |
7 | + t.attachment :wi_s | |
8 | + t.attachment :wi_c | |
9 | + t.attachment :wi_t | |
10 | + t.attachment :wi_nr | |
11 | + t.attachment :wi_ns | |
12 | + t.attachment :wi_nc | |
13 | + t.attachment :wi_nt | |
14 | + t.attachment :wi_dr | |
15 | + t.attachment :wi_ds | |
16 | + t.attachment :wi_dc | |
17 | + t.attachment :wi_dt | |
18 | + t.attachment :wi_rn | |
19 | + t.attachment :wi_rd | |
20 | + t.attachment :wi_rs | |
21 | + t.attachment :wi_rc | |
22 | + t.attachment :wi_rt | |
23 | + t.attachment :wi_sn | |
24 | + t.attachment :wi_sd | |
25 | + t.attachment :wi_sr | |
26 | + t.attachment :wi_sc | |
27 | + t.attachment :wi_st | |
28 | + t.attachment :wi_cn | |
29 | + t.attachment :wi_cd | |
30 | + t.attachment :wi_cr | |
31 | + t.attachment :wi_cs | |
32 | + t.attachment :wi_ct | |
33 | + t.attachment :wi_tn | |
34 | + t.attachment :wi_td | |
35 | + t.attachment :wi_tr | |
36 | + t.attachment :wi_ts | |
37 | + t.attachment :wi_tc | |
38 | + end | |
39 | + end | |
40 | + | |
41 | + def self.down | |
42 | + remove_attachment :drafts, :wi_n | |
43 | + remove_attachment :drafts, :wi_d | |
44 | + remove_attachment :drafts, :wi_r | |
45 | + remove_attachment :drafts, :wi_s | |
46 | + remove_attachment :drafts, :wi_c | |
47 | + remove_attachment :drafts, :wi_t | |
48 | + remove_attachment :drafts, :wi_nr | |
49 | + remove_attachment :drafts, :wi_ns | |
50 | + remove_attachment :drafts, :wi_nc | |
51 | + remove_attachment :drafts, :wi_nt | |
52 | + remove_attachment :drafts, :wi_dr | |
53 | + remove_attachment :drafts, :wi_ds | |
54 | + remove_attachment :drafts, :wi_dc | |
55 | + remove_attachment :drafts, :wi_dt | |
56 | + remove_attachment :drafts, :wi_rn | |
57 | + remove_attachment :drafts, :wi_rd | |
58 | + remove_attachment :drafts, :wi_rs | |
59 | + remove_attachment :drafts, :wi_rc | |
60 | + remove_attachment :drafts, :wi_rt | |
61 | + remove_attachment :drafts, :wi_sn | |
62 | + remove_attachment :drafts, :wi_sd | |
63 | + remove_attachment :drafts, :wi_sr | |
64 | + remove_attachment :drafts, :wi_sc | |
65 | + remove_attachment :drafts, :wi_st | |
66 | + remove_attachment :drafts, :wi_cn | |
67 | + remove_attachment :drafts, :wi_cd | |
68 | + remove_attachment :drafts, :wi_cr | |
69 | + remove_attachment :drafts, :wi_cs | |
70 | + remove_attachment :drafts, :wi_ct | |
71 | + remove_attachment :drafts, :wi_tn | |
72 | + remove_attachment :drafts, :wi_td | |
73 | + remove_attachment :drafts, :wi_tr | |
74 | + remove_attachment :drafts, :wi_ts | |
75 | + remove_attachment :drafts, :wi_tc | |
76 | + end | |
77 | +end |
@@ -10,7 +10,7 @@ | ||
10 | 10 | # |
11 | 11 | # It's strongly recommended that you check this file into your version control system. |
12 | 12 | |
13 | -ActiveRecord::Schema.define(version: 20170801155404) do | |
13 | +ActiveRecord::Schema.define(version: 20170804183731) do | |
14 | 14 | |
15 | 15 | create_table "areas", force: :cascade do |t| |
16 | 16 | t.string "ns" |
@@ -50,6 +50,155 @@ ActiveRecord::Schema.define(version: 20170801155404) do | ||
50 | 50 | t.index ["status"], name: "index_dist_signals_on_status" |
51 | 51 | end |
52 | 52 | |
53 | + create_table "drafts", force: :cascade do |t| | |
54 | + t.string "key", null: false | |
55 | + t.string "title", default: "", null: false | |
56 | + t.string "url", default: "", null: false | |
57 | + t.string "day_frame" | |
58 | + t.datetime "expires_at", null: false | |
59 | + t.integer "user_id" | |
60 | + t.datetime "created_at", null: false | |
61 | + t.datetime "updated_at", null: false | |
62 | + t.string "wi_n_file_name" | |
63 | + t.string "wi_n_content_type" | |
64 | + t.integer "wi_n_file_size" | |
65 | + t.datetime "wi_n_updated_at" | |
66 | + t.string "wi_d_file_name" | |
67 | + t.string "wi_d_content_type" | |
68 | + t.integer "wi_d_file_size" | |
69 | + t.datetime "wi_d_updated_at" | |
70 | + t.string "wi_r_file_name" | |
71 | + t.string "wi_r_content_type" | |
72 | + t.integer "wi_r_file_size" | |
73 | + t.datetime "wi_r_updated_at" | |
74 | + t.string "wi_s_file_name" | |
75 | + t.string "wi_s_content_type" | |
76 | + t.integer "wi_s_file_size" | |
77 | + t.datetime "wi_s_updated_at" | |
78 | + t.string "wi_c_file_name" | |
79 | + t.string "wi_c_content_type" | |
80 | + t.integer "wi_c_file_size" | |
81 | + t.datetime "wi_c_updated_at" | |
82 | + t.string "wi_t_file_name" | |
83 | + t.string "wi_t_content_type" | |
84 | + t.integer "wi_t_file_size" | |
85 | + t.datetime "wi_t_updated_at" | |
86 | + t.string "wi_nr_file_name" | |
87 | + t.string "wi_nr_content_type" | |
88 | + t.integer "wi_nr_file_size" | |
89 | + t.datetime "wi_nr_updated_at" | |
90 | + t.string "wi_ns_file_name" | |
91 | + t.string "wi_ns_content_type" | |
92 | + t.integer "wi_ns_file_size" | |
93 | + t.datetime "wi_ns_updated_at" | |
94 | + t.string "wi_nc_file_name" | |
95 | + t.string "wi_nc_content_type" | |
96 | + t.integer "wi_nc_file_size" | |
97 | + t.datetime "wi_nc_updated_at" | |
98 | + t.string "wi_nt_file_name" | |
99 | + t.string "wi_nt_content_type" | |
100 | + t.integer "wi_nt_file_size" | |
101 | + t.datetime "wi_nt_updated_at" | |
102 | + t.string "wi_dr_file_name" | |
103 | + t.string "wi_dr_content_type" | |
104 | + t.integer "wi_dr_file_size" | |
105 | + t.datetime "wi_dr_updated_at" | |
106 | + t.string "wi_ds_file_name" | |
107 | + t.string "wi_ds_content_type" | |
108 | + t.integer "wi_ds_file_size" | |
109 | + t.datetime "wi_ds_updated_at" | |
110 | + t.string "wi_dc_file_name" | |
111 | + t.string "wi_dc_content_type" | |
112 | + t.integer "wi_dc_file_size" | |
113 | + t.datetime "wi_dc_updated_at" | |
114 | + t.string "wi_dt_file_name" | |
115 | + t.string "wi_dt_content_type" | |
116 | + t.integer "wi_dt_file_size" | |
117 | + t.datetime "wi_dt_updated_at" | |
118 | + t.string "wi_rn_file_name" | |
119 | + t.string "wi_rn_content_type" | |
120 | + t.integer "wi_rn_file_size" | |
121 | + t.datetime "wi_rn_updated_at" | |
122 | + t.string "wi_rd_file_name" | |
123 | + t.string "wi_rd_content_type" | |
124 | + t.integer "wi_rd_file_size" | |
125 | + t.datetime "wi_rd_updated_at" | |
126 | + t.string "wi_rs_file_name" | |
127 | + t.string "wi_rs_content_type" | |
128 | + t.integer "wi_rs_file_size" | |
129 | + t.datetime "wi_rs_updated_at" | |
130 | + t.string "wi_rc_file_name" | |
131 | + t.string "wi_rc_content_type" | |
132 | + t.integer "wi_rc_file_size" | |
133 | + t.datetime "wi_rc_updated_at" | |
134 | + t.string "wi_rt_file_name" | |
135 | + t.string "wi_rt_content_type" | |
136 | + t.integer "wi_rt_file_size" | |
137 | + t.datetime "wi_rt_updated_at" | |
138 | + t.string "wi_sn_file_name" | |
139 | + t.string "wi_sn_content_type" | |
140 | + t.integer "wi_sn_file_size" | |
141 | + t.datetime "wi_sn_updated_at" | |
142 | + t.string "wi_sd_file_name" | |
143 | + t.string "wi_sd_content_type" | |
144 | + t.integer "wi_sd_file_size" | |
145 | + t.datetime "wi_sd_updated_at" | |
146 | + t.string "wi_sr_file_name" | |
147 | + t.string "wi_sr_content_type" | |
148 | + t.integer "wi_sr_file_size" | |
149 | + t.datetime "wi_sr_updated_at" | |
150 | + t.string "wi_sc_file_name" | |
151 | + t.string "wi_sc_content_type" | |
152 | + t.integer "wi_sc_file_size" | |
153 | + t.datetime "wi_sc_updated_at" | |
154 | + t.string "wi_st_file_name" | |
155 | + t.string "wi_st_content_type" | |
156 | + t.integer "wi_st_file_size" | |
157 | + t.datetime "wi_st_updated_at" | |
158 | + t.string "wi_cn_file_name" | |
159 | + t.string "wi_cn_content_type" | |
160 | + t.integer "wi_cn_file_size" | |
161 | + t.datetime "wi_cn_updated_at" | |
162 | + t.string "wi_cd_file_name" | |
163 | + t.string "wi_cd_content_type" | |
164 | + t.integer "wi_cd_file_size" | |
165 | + t.datetime "wi_cd_updated_at" | |
166 | + t.string "wi_cr_file_name" | |
167 | + t.string "wi_cr_content_type" | |
168 | + t.integer "wi_cr_file_size" | |
169 | + t.datetime "wi_cr_updated_at" | |
170 | + t.string "wi_cs_file_name" | |
171 | + t.string "wi_cs_content_type" | |
172 | + t.integer "wi_cs_file_size" | |
173 | + t.datetime "wi_cs_updated_at" | |
174 | + t.string "wi_ct_file_name" | |
175 | + t.string "wi_ct_content_type" | |
176 | + t.integer "wi_ct_file_size" | |
177 | + t.datetime "wi_ct_updated_at" | |
178 | + t.string "wi_tn_file_name" | |
179 | + t.string "wi_tn_content_type" | |
180 | + t.integer "wi_tn_file_size" | |
181 | + t.datetime "wi_tn_updated_at" | |
182 | + t.string "wi_td_file_name" | |
183 | + t.string "wi_td_content_type" | |
184 | + t.integer "wi_td_file_size" | |
185 | + t.datetime "wi_td_updated_at" | |
186 | + t.string "wi_tr_file_name" | |
187 | + t.string "wi_tr_content_type" | |
188 | + t.integer "wi_tr_file_size" | |
189 | + t.datetime "wi_tr_updated_at" | |
190 | + t.string "wi_ts_file_name" | |
191 | + t.string "wi_ts_content_type" | |
192 | + t.integer "wi_ts_file_size" | |
193 | + t.datetime "wi_ts_updated_at" | |
194 | + t.string "wi_tc_file_name" | |
195 | + t.string "wi_tc_content_type" | |
196 | + t.integer "wi_tc_file_size" | |
197 | + t.datetime "wi_tc_updated_at" | |
198 | + t.index ["key"], name: "index_drafts_on_key", unique: true | |
199 | + t.index ["user_id"], name: "index_drafts_on_user_id" | |
200 | + end | |
201 | + | |
53 | 202 | create_table "forecasts", force: :cascade do |t| |
54 | 203 | t.integer "area_id" |
55 | 204 | t.string "weather_symbol" |
@@ -5,4 +5,14 @@ class ApplicationResponder < ActionController::Responder | ||
5 | 5 | # Redirects resources to the collection path (index action) instead |
6 | 6 | # of the resource path (show action) for POST/PUT/DELETE requests. |
7 | 7 | # include Responders::CollectionResponder |
8 | + | |
9 | + def api_behavior | |
10 | + raise MissingRenderer.new(format) unless has_renderer? | |
11 | + | |
12 | + if put? || patch? | |
13 | + display resource, :status => :ok | |
14 | + else | |
15 | + super | |
16 | + end | |
17 | + end | |
8 | 18 | end |
@@ -30,14 +30,15 @@ | ||
30 | 30 | "react-addons-css-transition-group": "^15.6.0", |
31 | 31 | "react-addons-transition-group": "^15.6.0", |
32 | 32 | "react-dom": "^15.6.1", |
33 | + "react-dropzone": "^4.0.0", | |
33 | 34 | "reactstrap": "^4.8.0", |
34 | 35 | "resolve-url-loader": "^2.1.0", |
35 | 36 | "sass-loader": "^6.0.6", |
36 | 37 | "style-loader": "^0.18.2", |
37 | - "turbolinks": "^5.0.3", | |
38 | 38 | "webpack": "^3.3.0", |
39 | 39 | "webpack-manifest-plugin": "^1.1.2", |
40 | - "webpack-merge": "^4.1.0" | |
40 | + "webpack-merge": "^4.1.0", | |
41 | + "webpacker-react": "^0.3.0" | |
41 | 42 | }, |
42 | 43 | "devDependencies": { |
43 | 44 | "webpack-dev-server": "^2.5.1" |
@@ -0,0 +1,141 @@ | ||
1 | +require 'rails_helper' | |
2 | + | |
3 | +# This spec was generated by rspec-rails when you ran the scaffold generator. | |
4 | +# It demonstrates how one might use RSpec to specify the controller code that | |
5 | +# was generated by Rails when you ran the scaffold generator. | |
6 | +# | |
7 | +# It assumes that the implementation code is generated by the rails scaffold | |
8 | +# generator. If you are using any extension libraries to generate different | |
9 | +# controller code, this generated spec may or may not pass. | |
10 | +# | |
11 | +# It only uses APIs available in rails and/or rspec-rails. There are a number | |
12 | +# of tools you can use to make these specs even more expressive, but we're | |
13 | +# sticking to rails and rspec-rails APIs to keep things simple and stable. | |
14 | +# | |
15 | +# Compared to earlier versions of this generator, there is very limited use of | |
16 | +# stubs and message expectations in this spec. Stubs are only used when there | |
17 | +# is no simpler way to get a handle on the object needed for the example. | |
18 | +# Message expectations are only used when there is no simpler way to specify | |
19 | +# that an instance is receiving a specific message. | |
20 | +# | |
21 | +# Also compared to earlier versions of this generator, there are no longer any | |
22 | +# expectations of assigns and templates rendered. These features have been | |
23 | +# removed from Rails core in Rails 5, but can be added back in via the | |
24 | +# `rails-controller-testing` gem. | |
25 | + | |
26 | +RSpec.describe DraftsController, type: :controller do | |
27 | + | |
28 | + # This should return the minimal set of attributes required to create a valid | |
29 | + # Draft. As you add validations to Draft, be sure to | |
30 | + # adjust the attributes here as well. | |
31 | + let(:valid_attributes) { | |
32 | + skip("Add a hash of attributes valid for your model") | |
33 | + } | |
34 | + | |
35 | + let(:invalid_attributes) { | |
36 | + skip("Add a hash of attributes invalid for your model") | |
37 | + } | |
38 | + | |
39 | + # This should return the minimal set of values that should be in the session | |
40 | + # in order to pass any filters (e.g. authentication) defined in | |
41 | + # DraftsController. Be sure to keep this updated too. | |
42 | + let(:valid_session) { {} } | |
43 | + | |
44 | + describe "GET #index" do | |
45 | + it "returns a success response" do | |
46 | + draft = Draft.create! valid_attributes | |
47 | + get :index, params: {}, session: valid_session | |
48 | + expect(response).to be_success | |
49 | + end | |
50 | + end | |
51 | + | |
52 | + describe "GET #show" do | |
53 | + it "returns a success response" do | |
54 | + draft = Draft.create! valid_attributes | |
55 | + get :show, params: {id: draft.to_param}, session: valid_session | |
56 | + expect(response).to be_success | |
57 | + end | |
58 | + end | |
59 | + | |
60 | + describe "GET #new" do | |
61 | + it "returns a success response" do | |
62 | + get :new, params: {}, session: valid_session | |
63 | + expect(response).to be_success | |
64 | + end | |
65 | + end | |
66 | + | |
67 | + describe "GET #edit" do | |
68 | + it "returns a success response" do | |
69 | + draft = Draft.create! valid_attributes | |
70 | + get :edit, params: {id: draft.to_param}, session: valid_session | |
71 | + expect(response).to be_success | |
72 | + end | |
73 | + end | |
74 | + | |
75 | + describe "POST #create" do | |
76 | + context "with valid params" do | |
77 | + it "creates a new Draft" do | |
78 | + expect { | |
79 | + post :create, params: {draft: valid_attributes}, session: valid_session | |
80 | + }.to change(Draft, :count).by(1) | |
81 | + end | |
82 | + | |
83 | + it "redirects to the created draft" do | |
84 | + post :create, params: {draft: valid_attributes}, session: valid_session | |
85 | + expect(response).to redirect_to(Draft.last) | |
86 | + end | |
87 | + end | |
88 | + | |
89 | + context "with invalid params" do | |
90 | + it "returns a success response (i.e. to display the 'new' template)" do | |
91 | + post :create, params: {draft: invalid_attributes}, session: valid_session | |
92 | + expect(response).to be_success | |
93 | + end | |
94 | + end | |
95 | + end | |
96 | + | |
97 | + describe "PUT #update" do | |
98 | + context "with valid params" do | |
99 | + let(:new_attributes) { | |
100 | + skip("Add a hash of attributes valid for your model") | |
101 | + } | |
102 | + | |
103 | + it "updates the requested draft" do | |
104 | + draft = Draft.create! valid_attributes | |
105 | + put :update, params: {id: draft.to_param, draft: new_attributes}, session: valid_session | |
106 | + draft.reload | |
107 | + skip("Add assertions for updated state") | |
108 | + end | |
109 | + | |
110 | + it "redirects to the draft" do | |
111 | + draft = Draft.create! valid_attributes | |
112 | + put :update, params: {id: draft.to_param, draft: valid_attributes}, session: valid_session | |
113 | + expect(response).to redirect_to(draft) | |
114 | + end | |
115 | + end | |
116 | + | |
117 | + context "with invalid params" do | |
118 | + it "returns a success response (i.e. to display the 'edit' template)" do | |
119 | + draft = Draft.create! valid_attributes | |
120 | + put :update, params: {id: draft.to_param, draft: invalid_attributes}, session: valid_session | |
121 | + expect(response).to be_success | |
122 | + end | |
123 | + end | |
124 | + end | |
125 | + | |
126 | + describe "DELETE #destroy" do | |
127 | + it "destroys the requested draft" do | |
128 | + draft = Draft.create! valid_attributes | |
129 | + expect { | |
130 | + delete :destroy, params: {id: draft.to_param}, session: valid_session | |
131 | + }.to change(Draft, :count).by(-1) | |
132 | + end | |
133 | + | |
134 | + it "redirects to the drafts list" do | |
135 | + draft = Draft.create! valid_attributes | |
136 | + delete :destroy, params: {id: draft.to_param}, session: valid_session | |
137 | + expect(response).to redirect_to(drafts_url) | |
138 | + end | |
139 | + end | |
140 | + | |
141 | +end |
@@ -0,0 +1,10 @@ | ||
1 | +FactoryGirl.define do | |
2 | + factory :draft do | |
3 | + key "MyString" | |
4 | + title "MyString" | |
5 | + url "MyString" | |
6 | + day_frame "MyString" | |
7 | + expires_at "2017-08-05 03:13:45" | |
8 | + user nil | |
9 | + end | |
10 | +end |
@@ -0,0 +1,5 @@ | ||
1 | +require 'rails_helper' | |
2 | + | |
3 | +RSpec.describe Draft, type: :model do | |
4 | + pending "add some examples to (or delete) #{__FILE__}" | |
5 | +end |
@@ -0,0 +1,35 @@ | ||
1 | +require "rails_helper" | |
2 | + | |
3 | +RSpec.describe DraftsController, type: :routing do | |
4 | + describe "routing" do | |
5 | + | |
6 | + it "routes to #index" do | |
7 | + expect(:get => "/drafts").to route_to("drafts#index") | |
8 | + end | |
9 | + | |
10 | + it "routes to #new" do | |
11 | + expect(:get => "/drafts/new").to route_to("drafts#new") | |
12 | + end | |
13 | + | |
14 | + it "routes to #show" do | |
15 | + expect(:get => "/drafts/1").to route_to("drafts#show", :id => "1") | |
16 | + end | |
17 | + | |
18 | + it "routes to #edit" do | |
19 | + expect(:get => "/drafts/1/edit").to route_to("drafts#edit", :id => "1") | |
20 | + end | |
21 | + | |
22 | + it "routes to #create" do | |
23 | + expect(:post => "/drafts").to route_to("drafts#create") | |
24 | + end | |
25 | + | |
26 | + it "routes to #update via PUT" do | |
27 | + expect(:put => "/drafts/1").to route_to("drafts#update", :id => "1") | |
28 | + end | |
29 | + | |
30 | + it "routes to #update via PATCH" do | |
31 | + expect(:patch => "/drafts/1").to route_to("drafts#update", :id => "1") | |
32 | + end | |
33 | + | |
34 | + end | |
35 | +end |
@@ -0,0 +1,5 @@ | ||
1 | +require "test_helper" | |
2 | + | |
3 | +class ApplicationSystemTestCase < ActionDispatch::SystemTestCase | |
4 | + driven_by :selenium, using: :chrome, screen_size: [1400, 1400] | |
5 | +end |
@@ -0,0 +1,9 @@ | ||
1 | +require "application_system_test_case" | |
2 | + | |
3 | +class DraftsTest < ApplicationSystemTestCase | |
4 | + # test "visiting the index" do | |
5 | + # visit drafts_url | |
6 | + # | |
7 | + # assert_selector "h1", text: "Draft" | |
8 | + # end | |
9 | +end |
@@ -223,6 +223,10 @@ atob@~1.1.0: | ||
223 | 223 | version "1.1.3" |
224 | 224 | resolved "https://registry.yarnpkg.com/atob/-/atob-1.1.3.tgz#95f13629b12c3a51a5d215abdce2aa9f32f80773" |
225 | 225 | |
226 | +attr-accept@^1.0.3: | |
227 | + version "1.1.0" | |
228 | + resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.0.tgz#b5cd35227f163935a8f1de10ed3eba16941f6be6" | |
229 | + | |
226 | 230 | autoprefixer@^6.3.1: |
227 | 231 | version "6.7.7" |
228 | 232 | resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-6.7.7.tgz#1dbd1c835658e35ce3f9984099db00585c782014" |
@@ -3946,7 +3950,7 @@ promise@^7.1.1: | ||
3946 | 3950 | dependencies: |
3947 | 3951 | asap "~2.0.3" |
3948 | 3952 | |
3949 | -prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.8: | |
3953 | +prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8: | |
3950 | 3954 | version "15.5.10" |
3951 | 3955 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" |
3952 | 3956 | dependencies: |
@@ -4071,6 +4075,13 @@ react-dom@^15.6.1: | ||
4071 | 4075 | object-assign "^4.1.0" |
4072 | 4076 | prop-types "^15.5.10" |
4073 | 4077 | |
4078 | +react-dropzone@^4.0.0: | |
4079 | + version "4.0.0" | |
4080 | + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-4.0.0.tgz#3ff7fab769ad4fb6206ca4a16fe35eab34297053" | |
4081 | + dependencies: | |
4082 | + attr-accept "^1.0.3" | |
4083 | + prop-types "^15.5.7" | |
4084 | + | |
4074 | 4085 | react-transition-group@^1.2.0: |
4075 | 4086 | version "1.2.0" |
4076 | 4087 | resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-1.2.0.tgz#b51fc921b0c3835a7ef7c571c79fc82c73e9204f" |
@@ -4796,10 +4807,6 @@ tunnel-agent@^0.6.0: | ||
4796 | 4807 | dependencies: |
4797 | 4808 | safe-buffer "^5.0.1" |
4798 | 4809 | |
4799 | -turbolinks@^5.0.3: | |
4800 | - version "5.0.3" | |
4801 | - resolved "https://registry.yarnpkg.com/turbolinks/-/turbolinks-5.0.3.tgz#c8ce128cfc9be1d691a1e41ffa858a5a5ee86a02" | |
4802 | - | |
4803 | 4810 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: |
4804 | 4811 | version "0.14.5" |
4805 | 4812 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" |
@@ -5038,6 +5045,10 @@ webpack@^3.3.0: | ||
5038 | 5045 | webpack-sources "^1.0.1" |
5039 | 5046 | yargs "^6.0.0" |
5040 | 5047 | |
5048 | +webpacker-react@^0.3.0: | |
5049 | + version "0.3.0" | |
5050 | + resolved "https://registry.yarnpkg.com/webpacker-react/-/webpacker-react-0.3.0.tgz#176d4537d997772f66641d565b796f33dfbb0272" | |
5051 | + | |
5041 | 5052 | websocket-driver@>=0.5.1: |
5042 | 5053 | version "0.6.5" |
5043 | 5054 | resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" |