diff options
author | Stefan Malzner <stefan@adlk.io> | 2018-01-02 22:54:17 +0100 |
---|---|---|
committer | Stefan Malzner <stefan@adlk.io> | 2018-01-02 22:54:47 +0100 |
commit | 6b97e42537879be306cd2aaf95dd8aebf8655fcb (patch) | |
tree | 2c2628d507cbf34c953dc97d9a5fe30d8ffcd5db | |
parent | First working draft of icon upload (diff) | |
download | ferdium-app-6b97e42537879be306cd2aaf95dd8aebf8655fcb.tar.gz ferdium-app-6b97e42537879be306cd2aaf95dd8aebf8655fcb.tar.zst ferdium-app-6b97e42537879be306cd2aaf95dd8aebf8655fcb.zip |
feat(Service): Add custom service icon upload
-rw-r--r-- | src/components/settings/services/EditServiceForm.js | 75 | ||||
-rw-r--r-- | src/components/ui/ImageUpload.js | 93 | ||||
-rw-r--r-- | src/containers/settings/EditServiceScreen.js | 4 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 4 | ||||
-rw-r--r-- | src/models/Service.js | 3 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 10 | ||||
-rw-r--r-- | src/styles/image-upload.scss | 47 | ||||
-rw-r--r-- | src/styles/settings.scss | 11 |
8 files changed, 163 insertions, 84 deletions
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 4f2f98a01..c0a993736 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -84,6 +84,14 @@ const messages = defineMessages({ | |||
84 | id: 'settings.service.form.headlineGeneral', | 84 | id: 'settings.service.form.headlineGeneral', |
85 | defaultMessage: '!!!General', | 85 | defaultMessage: '!!!General', |
86 | }, | 86 | }, |
87 | iconDelete: { | ||
88 | id: 'settings.service.form.iconDelete', | ||
89 | defaultMessage: '!!!Delete', | ||
90 | }, | ||
91 | iconUpload: { | ||
92 | id: 'settings.service.form.iconUpload', | ||
93 | defaultMessage: '!!!Drop your image, or click here', | ||
94 | }, | ||
87 | }); | 95 | }); |
88 | 96 | ||
89 | @observer | 97 | @observer |
@@ -223,14 +231,8 @@ export default class EditServiceForm extends Component { | |||
223 | </div> | 231 | </div> |
224 | <div className="settings__body"> | 232 | <div className="settings__body"> |
225 | <form onSubmit={e => this.submit(e)} id="form"> | 233 | <form onSubmit={e => this.submit(e)} id="form"> |
226 | <div className="service-flex-grid"> | 234 | <div className="service-name"> |
227 | <div className="service-name"> | 235 | <Input field={form.$('name')} focus /> |
228 | <Input field={form.$('name')} focus /> | ||
229 | </div> | ||
230 | <div className="service-icon"> | ||
231 | {/* <Input field={form.$('name')} focus /> */} | ||
232 | <ImageUpload field={form.$('customIcon')} /> | ||
233 | </div> | ||
234 | </div> | 236 | </div> |
235 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( | 237 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( |
236 | <Tabs | 238 | <Tabs |
@@ -275,32 +277,41 @@ export default class EditServiceForm extends Component { | |||
275 | )} | 277 | )} |
276 | </Tabs> | 278 | </Tabs> |
277 | )} | 279 | )} |
278 | <div className="settings__options"> | 280 | <div className="service-flex-grid"> |
279 | <div className="settings__settings-group"> | 281 | <div className="settings__options"> |
280 | <h3>{intl.formatMessage(messages.headlineNotifications)}</h3> | 282 | <div className="settings__settings-group"> |
281 | <Toggle field={form.$('isNotificationEnabled')} /> | 283 | <h3>{intl.formatMessage(messages.headlineNotifications)}</h3> |
282 | <Toggle field={form.$('isMuted')} /> | 284 | <Toggle field={form.$('isNotificationEnabled')} /> |
283 | <p className="settings__help"> | 285 | <Toggle field={form.$('isMuted')} /> |
284 | {intl.formatMessage(messages.isMutedInfo)} | 286 | <p className="settings__help"> |
285 | </p> | 287 | {intl.formatMessage(messages.isMutedInfo)} |
286 | </div> | 288 | </p> |
289 | </div> | ||
287 | 290 | ||
288 | <div className="settings__settings-group"> | 291 | <div className="settings__settings-group"> |
289 | <h3>{intl.formatMessage(messages.headlineBadges)}</h3> | 292 | <h3>{intl.formatMessage(messages.headlineBadges)}</h3> |
290 | <Toggle field={form.$('isBadgeEnabled')} /> | 293 | <Toggle field={form.$('isBadgeEnabled')} /> |
291 | {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( | 294 | {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( |
292 | <div> | 295 | <div> |
293 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> | 296 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> |
294 | <p className="settings__help"> | 297 | <p className="settings__help"> |
295 | {intl.formatMessage(messages.indirectMessageInfo)} | 298 | {intl.formatMessage(messages.indirectMessageInfo)} |
296 | </p> | 299 | </p> |
297 | </div> | 300 | </div> |
298 | )} | 301 | )} |
299 | </div> | 302 | </div> |
300 | 303 | ||
301 | <div className="settings__settings-group"> | 304 | <div className="settings__settings-group"> |
302 | <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> | 305 | <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> |
303 | <Toggle field={form.$('isEnabled')} /> | 306 | <Toggle field={form.$('isEnabled')} /> |
307 | </div> | ||
308 | </div> | ||
309 | <div className="service-icon"> | ||
310 | <ImageUpload | ||
311 | field={form.$('customIcon')} | ||
312 | textDelete={intl.formatMessage(messages.iconDelete)} | ||
313 | textUpload={intl.formatMessage(messages.iconUpload)} | ||
314 | /> | ||
304 | </div> | 315 | </div> |
305 | </div> | 316 | </div> |
306 | {recipe.message && ( | 317 | {recipe.message && ( |
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js index f25d966f4..08809f007 100644 --- a/src/components/ui/ImageUpload.js +++ b/src/components/ui/ImageUpload.js | |||
@@ -12,6 +12,8 @@ export default class ImageUpload extends Component { | |||
12 | field: PropTypes.instanceOf(Field).isRequired, | 12 | field: PropTypes.instanceOf(Field).isRequired, |
13 | className: PropTypes.string, | 13 | className: PropTypes.string, |
14 | multiple: PropTypes.bool, | 14 | multiple: PropTypes.bool, |
15 | textDelete: PropTypes.string.isRequired, | ||
16 | textUpload: PropTypes.string.isRequired, | ||
15 | }; | 17 | }; |
16 | 18 | ||
17 | static defaultProps = { | 19 | static defaultProps = { |
@@ -39,56 +41,63 @@ export default class ImageUpload extends Component { | |||
39 | field, | 41 | field, |
40 | className, | 42 | className, |
41 | multiple, | 43 | multiple, |
44 | textDelete, | ||
45 | textUpload, | ||
42 | } = this.props; | 46 | } = this.props; |
43 | 47 | ||
44 | const cssClasses = classnames({ | 48 | const cssClasses = classnames({ |
45 | 'franz-form__button': true, | 49 | 'image-upload__dropzone': true, |
46 | [`${className}`]: className, | 50 | [`${className}`]: className, |
47 | }); | 51 | }); |
48 | 52 | ||
49 | return ( | 53 | return ( |
50 | <div> | 54 | <div className="image-upload-wrapper"> |
51 | {field.label} | 55 | <label className="franz-form__label" htmlFor="iconUpload">{field.label}</label> |
52 | {(field.value && field.value !== 'delete') || this.state.path ? ( | 56 | <div className="image-upload"> |
53 | <div | 57 | {(field.value && field.value !== 'delete') || this.state.path ? ( |
54 | className="image-upload" | 58 | <div> |
55 | > | 59 | <div |
56 | <div | 60 | className="image-upload__preview" |
57 | className="image-upload__preview" | 61 | style={({ |
58 | style={({ | 62 | backgroundImage: `url("${field.value || this.state.path}")`, |
59 | backgroundImage: `url(${field.value || this.state.path})`, | 63 | })} |
60 | })} | 64 | /> |
61 | /> | 65 | <div className="image-upload__action"> |
62 | <div className="image-upload__action"> | 66 | <button |
63 | <button | 67 | type="button" |
64 | type="button" | 68 | onClick={() => { |
65 | onClick={() => { | 69 | if (field.value) { |
66 | if (field.value) { | 70 | field.set('delete'); |
67 | field.value = 'delete'; | 71 | } else { |
68 | } else { | 72 | this.setState({ |
69 | this.setState({ | 73 | path: null, |
70 | path: null, | 74 | }); |
71 | }); | 75 | } |
72 | } | 76 | }} |
73 | }} | 77 | > |
74 | > | 78 | <i className="mdi mdi-delete" /> |
75 | remove icon | 79 | <p> |
76 | 80 | {textDelete} | |
77 | </button> | 81 | </p> |
78 | <div className="image-upload__action-background" /> | 82 | </button> |
83 | <div className="image-upload__action-background" /> | ||
84 | </div> | ||
79 | </div> | 85 | </div> |
80 | </div> | 86 | ) : ( |
81 | ) : ( | 87 | <Dropzone |
82 | <Dropzone | 88 | ref={(node) => { this.dropzoneRef = node; }} |
83 | ref={(node) => { this.dropzoneRef = node; }} | 89 | onDrop={this.onDrop.bind(this)} |
84 | onDrop={this.onDrop.bind(this)} | 90 | className={cssClasses} |
85 | className={cssClasses} | 91 | multiple={multiple} |
86 | multiple={multiple} | 92 | accept="image/jpeg, image/png" |
87 | accept="image/jpeg, image/png" | 93 | > |
88 | > | 94 | <i className="mdi mdi-file-image" /> |
89 | <p>Try dropping some files here, or click to select files to upload.</p> | 95 | <p> |
90 | </Dropzone> | 96 | {textUpload} |
91 | )} | 97 | </p> |
98 | </Dropzone> | ||
99 | )} | ||
100 | </div> | ||
92 | </div> | 101 | </div> |
93 | ); | 102 | ); |
94 | } | 103 | } |
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index 8d3a268ad..c26195a1e 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js | |||
@@ -48,7 +48,7 @@ const messages = defineMessages({ | |||
48 | }, | 48 | }, |
49 | icon: { | 49 | icon: { |
50 | id: 'settings.service.form.icon', | 50 | id: 'settings.service.form.icon', |
51 | defaultMessage: '!!!Icon', | 51 | defaultMessage: '!!!Custom icon', |
52 | }, | 52 | }, |
53 | }); | 53 | }); |
54 | 54 | ||
@@ -108,7 +108,7 @@ export default class EditServiceScreen extends Component { | |||
108 | }, | 108 | }, |
109 | customIcon: { | 109 | customIcon: { |
110 | label: intl.formatMessage(messages.icon), | 110 | label: intl.formatMessage(messages.icon), |
111 | value: service.hasCustomIcon ? service.icon : false, | 111 | value: service.hasCustomUploadedIcon ? service.icon : false, |
112 | default: null, | 112 | default: null, |
113 | type: 'file', | 113 | type: 'file', |
114 | }, | 114 | }, |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index bfb95da04..f3db20aea 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -127,7 +127,9 @@ | |||
127 | "settings.service.form.headlineNotifications": "Notifications", | 127 | "settings.service.form.headlineNotifications": "Notifications", |
128 | "settings.service.form.headlineBadges": "Unread message badges", | 128 | "settings.service.form.headlineBadges": "Unread message badges", |
129 | "settings.service.form.headlineGeneral": "General", | 129 | "settings.service.form.headlineGeneral": "General", |
130 | "settings.service.form.icon": "Icon", | 130 | "settings.service.form.icon": "Custom icon", |
131 | "settings.service.form.iconDelete": "Delete", | ||
132 | "settings.service.form.iconUpload": "Drop your image, or click here", | ||
131 | "settings.service.error.headline": "Error", | 133 | "settings.service.error.headline": "Error", |
132 | "settings.service.error.goBack": "Back to services", | 134 | "settings.service.error.goBack": "Back to services", |
133 | "settings.service.error.message": "Could not load service recipe.", | 135 | "settings.service.error.message": "Could not load service recipe.", |
diff --git a/src/models/Service.js b/src/models/Service.js index 652d2594c..423510c7d 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -25,6 +25,7 @@ export default class Service { | |||
25 | @observable isBadgeEnabled = true; | 25 | @observable isBadgeEnabled = true; |
26 | @observable isIndirectMessageBadgeEnabled = true; | 26 | @observable isIndirectMessageBadgeEnabled = true; |
27 | @observable iconUrl = ''; | 27 | @observable iconUrl = ''; |
28 | @observable hasCustomUploadedIcon = false; | ||
28 | @observable hasCrashed = false; | 29 | @observable hasCrashed = false; |
29 | 30 | ||
30 | constructor(data, recipe) { | 31 | constructor(data, recipe) { |
@@ -62,6 +63,8 @@ export default class Service { | |||
62 | 63 | ||
63 | this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; | 64 | this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; |
64 | 65 | ||
66 | this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon; | ||
67 | |||
65 | this.recipe = recipe; | 68 | this.recipe = recipe; |
66 | 69 | ||
67 | autorun(() => { | 70 | autorun(() => { |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 4fb5c9767..f2a8683ba 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -177,13 +177,21 @@ export default class ServicesStore extends Store { | |||
177 | await request._promise; | 177 | await request._promise; |
178 | 178 | ||
179 | newData.iconUrl = request.result.data.iconUrl; | 179 | newData.iconUrl = request.result.data.iconUrl; |
180 | newData.hasCustomUploadedIcon = true; | ||
180 | } | 181 | } |
181 | 182 | ||
182 | this.allServicesRequest.patch((result) => { | 183 | this.allServicesRequest.patch((result) => { |
183 | if (!result) return; | 184 | if (!result) return; |
184 | 185 | ||
186 | // patch custom icon deletion | ||
185 | if (data.customIcon === 'delete') { | 187 | if (data.customIcon === 'delete') { |
186 | data.iconUrl = ''; | 188 | data.iconUrl = ''; |
189 | data.hasCustomUploadedIcon = false; | ||
190 | } | ||
191 | |||
192 | // patch custom icon url | ||
193 | if (data.customIconUrl) { | ||
194 | data.iconUrl = data.customIconUrl; | ||
187 | } | 195 | } |
188 | 196 | ||
189 | Object.assign(result.find(c => c.id === serviceId), newData); | 197 | Object.assign(result.find(c => c.id === serviceId), newData); |
@@ -328,7 +336,7 @@ export default class ServicesStore extends Store { | |||
328 | } | 336 | } |
329 | } else if (channel === 'avatar') { | 337 | } else if (channel === 'avatar') { |
330 | const url = args[0]; | 338 | const url = args[0]; |
331 | if (service.customIconUrl !== url) { | 339 | if (service.iconUrl !== url && !service.hasCustomUploadedIcon) { |
332 | service.customIconUrl = url; | 340 | service.customIconUrl = url; |
333 | 341 | ||
334 | this.actions.service.updateService({ | 342 | this.actions.service.updateService({ |
diff --git a/src/styles/image-upload.scss b/src/styles/image-upload.scss index 764bb3158..06176a7af 100644 --- a/src/styles/image-upload.scss +++ b/src/styles/image-upload.scss | |||
@@ -1,9 +1,12 @@ | |||
1 | .image-upload { | 1 | .image-upload { |
2 | position: absolute; | 2 | position: absolute; |
3 | width: 100px; | 3 | width: 140px; |
4 | height: 100px; | 4 | height: 140px; |
5 | border-radius: $theme-border-radius; | 5 | border: 1px solid $theme-gray-lighter; |
6 | border-radius: $theme-border-radius-small; | ||
7 | background: $theme-gray-lightest; | ||
6 | overflow: hidden; | 8 | overflow: hidden; |
9 | margin-top: 5px; | ||
7 | 10 | ||
8 | &__preview, | 11 | &__preview, |
9 | &__action { | 12 | &__action { |
@@ -19,7 +22,7 @@ | |||
19 | background-size: 100%; | 22 | background-size: 100%; |
20 | background-repeat: no-repeat; | 23 | background-repeat: no-repeat; |
21 | background-position: center center; | 24 | background-position: center center; |
22 | margin: 5px; | 25 | border-radius: 3px; |
23 | } | 26 | } |
24 | 27 | ||
25 | &__action { | 28 | &__action { |
@@ -27,6 +30,8 @@ | |||
27 | z-index: 10; | 30 | z-index: 10; |
28 | opacity: 0; | 31 | opacity: 0; |
29 | transition: opacity 0.5s; | 32 | transition: opacity 0.5s; |
33 | display: flex; | ||
34 | justify-content: center; | ||
30 | 35 | ||
31 | &-background { | 36 | &-background { |
32 | position: absolute; | 37 | position: absolute; |
@@ -41,6 +46,33 @@ | |||
41 | button { | 46 | button { |
42 | position: relative; | 47 | position: relative; |
43 | z-index: 100; | 48 | z-index: 100; |
49 | color: #FFF; | ||
50 | |||
51 | .mdi { | ||
52 | color: #FFF; | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
57 | &__dropzone { | ||
58 | text-align: center; | ||
59 | border-radius: 5px; | ||
60 | padding: 10px; | ||
61 | display: flex; | ||
62 | align-items: center; | ||
63 | justify-content: center; | ||
64 | flex-direction: column; | ||
65 | } | ||
66 | |||
67 | &__dropzone, | ||
68 | button { | ||
69 | .mdi { | ||
70 | margin-bottom: 5px; | ||
71 | } | ||
72 | |||
73 | p { | ||
74 | font-size: 10px; | ||
75 | line-height: 10px; | ||
44 | } | 76 | } |
45 | } | 77 | } |
46 | 78 | ||
@@ -49,4 +81,11 @@ | |||
49 | opacity: 1; | 81 | opacity: 1; |
50 | } | 82 | } |
51 | } | 83 | } |
84 | } | ||
85 | |||
86 | .image-upload-wrapper { | ||
87 | .mdi { | ||
88 | font-size: 40px; | ||
89 | color: $theme-gray-light; | ||
90 | } | ||
52 | } \ No newline at end of file | 91 | } \ No newline at end of file |
diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 283913ab7..cb39717ea 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss | |||
@@ -121,9 +121,15 @@ | |||
121 | } | 121 | } |
122 | 122 | ||
123 | .service-icon { | 123 | .service-icon { |
124 | width: 30%; | 124 | width: 140px; |
125 | min-width: 100px; | 125 | float: right; |
126 | margin-top: 30px; | ||
126 | margin-left: 40px; | 127 | margin-left: 40px; |
128 | |||
129 | label { | ||
130 | font-weight: bold; | ||
131 | letter-spacing: -0.1px; | ||
132 | } | ||
127 | } | 133 | } |
128 | } | 134 | } |
129 | 135 | ||
@@ -167,6 +173,7 @@ | |||
167 | 173 | ||
168 | &__options { | 174 | &__options { |
169 | margin-top: 20px; | 175 | margin-top: 20px; |
176 | flex: 1; | ||
170 | } | 177 | } |
171 | 178 | ||
172 | &__settings-group { | 179 | &__settings-group { |