diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/api/server/ServerApi.js | 42 | ||||
-rw-r--r-- | src/components/settings/services/EditServiceForm.js | 75 | ||||
-rw-r--r-- | src/components/ui/ImageUpload.js | 108 | ||||
-rw-r--r-- | src/containers/settings/EditServiceScreen.js | 16 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 3 | ||||
-rw-r--r-- | src/models/Service.js | 14 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 24 | ||||
-rw-r--r-- | src/styles/image-upload.scss | 91 | ||||
-rw-r--r-- | src/styles/main.scss | 1 | ||||
-rw-r--r-- | src/styles/settings.scss | 27 |
10 files changed, 361 insertions, 40 deletions
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index d75d2e559..a684ff98b 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -171,27 +171,65 @@ export default class ServerApi { | |||
171 | throw request; | 171 | throw request; |
172 | } | 172 | } |
173 | const serviceData = await request.json(); | 173 | const serviceData = await request.json(); |
174 | |||
175 | if (data.iconFile) { | ||
176 | const iconUrl = await this.uploadServiceIcon(serviceData.data.id, data.iconFile); | ||
177 | |||
178 | serviceData.data.iconUrl = iconUrl; | ||
179 | } | ||
180 | |||
174 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); | 181 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); |
175 | 182 | ||
176 | console.debug('ServerApi::createService resolves', service); | 183 | console.debug('ServerApi::createService resolves', service); |
177 | return service; | 184 | return service; |
178 | } | 185 | } |
179 | 186 | ||
180 | async updateService(recipeId, data) { | 187 | async updateService(serviceId, rawData) { |
181 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${recipeId}`, this._prepareAuthRequest({ | 188 | const data = rawData; |
189 | |||
190 | if (data.iconFile) { | ||
191 | await this.uploadServiceIcon(serviceId, data.iconFile); | ||
192 | } | ||
193 | |||
194 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${serviceId}`, this._prepareAuthRequest({ | ||
182 | method: 'PUT', | 195 | method: 'PUT', |
183 | body: JSON.stringify(data), | 196 | body: JSON.stringify(data), |
184 | })); | 197 | })); |
198 | |||
185 | if (!request.ok) { | 199 | if (!request.ok) { |
186 | throw request; | 200 | throw request; |
187 | } | 201 | } |
202 | |||
188 | const serviceData = await request.json(); | 203 | const serviceData = await request.json(); |
204 | |||
189 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); | 205 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); |
190 | 206 | ||
191 | console.debug('ServerApi::updateService resolves', service); | 207 | console.debug('ServerApi::updateService resolves', service); |
192 | return service; | 208 | return service; |
193 | } | 209 | } |
194 | 210 | ||
211 | async uploadServiceIcon(serviceId, icon) { | ||
212 | const formData = new FormData(); | ||
213 | formData.append('icon', icon); | ||
214 | |||
215 | const requestData = this._prepareAuthRequest({ | ||
216 | method: 'PUT', | ||
217 | body: formData, | ||
218 | }); | ||
219 | |||
220 | delete requestData.headers['Content-Type']; | ||
221 | |||
222 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${serviceId}`, requestData); | ||
223 | |||
224 | if (!request.ok) { | ||
225 | throw request; | ||
226 | } | ||
227 | |||
228 | const serviceData = await request.json(); | ||
229 | |||
230 | return serviceData.data.iconUrl; | ||
231 | } | ||
232 | |||
195 | async reorderService(data) { | 233 | async reorderService(data) { |
196 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/reorder`, this._prepareAuthRequest({ | 234 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/reorder`, this._prepareAuthRequest({ |
197 | method: 'PUT', | 235 | method: 'PUT', |
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index b1a1c2e68..f6f2df2f3 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -13,6 +13,7 @@ import Tabs, { TabItem } from '../../ui/Tabs'; | |||
13 | import Input from '../../ui/Input'; | 13 | import Input from '../../ui/Input'; |
14 | import Toggle from '../../ui/Toggle'; | 14 | import Toggle from '../../ui/Toggle'; |
15 | import Button from '../../ui/Button'; | 15 | import Button from '../../ui/Button'; |
16 | import ImageUpload from '../../ui/ImageUpload'; | ||
16 | 17 | ||
17 | const messages = defineMessages({ | 18 | const messages = defineMessages({ |
18 | saveService: { | 19 | saveService: { |
@@ -83,6 +84,14 @@ const messages = defineMessages({ | |||
83 | id: 'settings.service.form.headlineGeneral', | 84 | id: 'settings.service.form.headlineGeneral', |
84 | defaultMessage: '!!!General', | 85 | defaultMessage: '!!!General', |
85 | }, | 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 | }, | ||
86 | }); | 95 | }); |
87 | 96 | ||
88 | @observer | 97 | @observer |
@@ -126,6 +135,11 @@ export default class EditServiceForm extends Component { | |||
126 | const values = form.values(); | 135 | const values = form.values(); |
127 | let isValid = true; | 136 | let isValid = true; |
128 | 137 | ||
138 | const files = form.$('customIcon').files; | ||
139 | if (files) { | ||
140 | values.iconFile = files[0]; | ||
141 | } | ||
142 | |||
129 | if (recipe.validateUrl && values.customUrl) { | 143 | if (recipe.validateUrl && values.customUrl) { |
130 | this.setState({ isValidatingCustomUrl: true }); | 144 | this.setState({ isValidatingCustomUrl: true }); |
131 | try { | 145 | try { |
@@ -217,7 +231,9 @@ export default class EditServiceForm extends Component { | |||
217 | </div> | 231 | </div> |
218 | <div className="settings__body"> | 232 | <div className="settings__body"> |
219 | <form onSubmit={e => this.submit(e)} id="form"> | 233 | <form onSubmit={e => this.submit(e)} id="form"> |
220 | <Input field={form.$('name')} focus /> | 234 | <div className="service-name"> |
235 | <Input field={form.$('name')} focus /> | ||
236 | </div> | ||
221 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( | 237 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( |
222 | <Tabs | 238 | <Tabs |
223 | active={activeTabIndex} | 239 | active={activeTabIndex} |
@@ -261,32 +277,41 @@ export default class EditServiceForm extends Component { | |||
261 | )} | 277 | )} |
262 | </Tabs> | 278 | </Tabs> |
263 | )} | 279 | )} |
264 | <div className="settings__options"> | 280 | <div className="service-flex-grid"> |
265 | <div className="settings__settings-group"> | 281 | <div className="settings__options"> |
266 | <h3>{intl.formatMessage(messages.headlineNotifications)}</h3> | 282 | <div className="settings__settings-group"> |
267 | <Toggle field={form.$('isNotificationEnabled')} /> | 283 | <h3>{intl.formatMessage(messages.headlineNotifications)}</h3> |
268 | <Toggle field={form.$('isMuted')} /> | 284 | <Toggle field={form.$('isNotificationEnabled')} /> |
269 | <p className="settings__help"> | 285 | <Toggle field={form.$('isMuted')} /> |
270 | {intl.formatMessage(messages.isMutedInfo)} | 286 | <p className="settings__help"> |
271 | </p> | 287 | {intl.formatMessage(messages.isMutedInfo)} |
272 | </div> | 288 | </p> |
289 | </div> | ||
273 | 290 | ||
274 | <div className="settings__settings-group"> | 291 | <div className="settings__settings-group"> |
275 | <h3>{intl.formatMessage(messages.headlineBadges)}</h3> | 292 | <h3>{intl.formatMessage(messages.headlineBadges)}</h3> |
276 | <Toggle field={form.$('isBadgeEnabled')} /> | 293 | <Toggle field={form.$('isBadgeEnabled')} /> |
277 | {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( | 294 | {recipe.hasIndirectMessages && form.$('isBadgeEnabled').value && ( |
278 | <div> | 295 | <div> |
279 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> | 296 | <Toggle field={form.$('isIndirectMessageBadgeEnabled')} /> |
280 | <p className="settings__help"> | 297 | <p className="settings__help"> |
281 | {intl.formatMessage(messages.indirectMessageInfo)} | 298 | {intl.formatMessage(messages.indirectMessageInfo)} |
282 | </p> | 299 | </p> |
283 | </div> | 300 | </div> |
284 | )} | 301 | )} |
285 | </div> | 302 | </div> |
286 | 303 | ||
287 | <div className="settings__settings-group"> | 304 | <div className="settings__settings-group"> |
288 | <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> | 305 | <h3>{intl.formatMessage(messages.headlineGeneral)}</h3> |
289 | <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 | /> | ||
290 | </div> | 315 | </div> |
291 | </div> | 316 | </div> |
292 | {recipe.message && ( | 317 | {recipe.message && ( |
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js new file mode 100644 index 000000000..81c3b8da6 --- /dev/null +++ b/src/components/ui/ImageUpload.js | |||
@@ -0,0 +1,108 @@ | |||
1 | import React, { Component } from 'react'; | ||
2 | import PropTypes from 'prop-types'; | ||
3 | import { observer } from 'mobx-react'; | ||
4 | import { Field } from 'mobx-react-form'; | ||
5 | // import Loader from 'react-loader'; | ||
6 | import classnames from 'classnames'; | ||
7 | import Dropzone from 'react-dropzone'; | ||
8 | |||
9 | @observer | ||
10 | export default class ImageUpload extends Component { | ||
11 | static propTypes = { | ||
12 | field: PropTypes.instanceOf(Field).isRequired, | ||
13 | className: PropTypes.string, | ||
14 | multiple: PropTypes.bool, | ||
15 | textDelete: PropTypes.string.isRequired, | ||
16 | textUpload: PropTypes.string.isRequired, | ||
17 | }; | ||
18 | |||
19 | static defaultProps = { | ||
20 | className: null, | ||
21 | multiple: false, | ||
22 | }; | ||
23 | |||
24 | state = { | ||
25 | path: null, | ||
26 | } | ||
27 | |||
28 | onDrop(acceptedFiles) { | ||
29 | const { field } = this.props; | ||
30 | |||
31 | acceptedFiles.forEach((file) => { | ||
32 | this.setState({ | ||
33 | path: file.path, | ||
34 | }); | ||
35 | this.props.field.onDrop(file); | ||
36 | }); | ||
37 | |||
38 | field.set(''); | ||
39 | } | ||
40 | |||
41 | dropzoneRef = null; | ||
42 | |||
43 | render() { | ||
44 | const { | ||
45 | field, | ||
46 | className, | ||
47 | multiple, | ||
48 | textDelete, | ||
49 | textUpload, | ||
50 | } = this.props; | ||
51 | |||
52 | const cssClasses = classnames({ | ||
53 | 'image-upload__dropzone': true, | ||
54 | [`${className}`]: className, | ||
55 | }); | ||
56 | |||
57 | return ( | ||
58 | <div className="image-upload-wrapper"> | ||
59 | <label className="franz-form__label" htmlFor="iconUpload">{field.label}</label> | ||
60 | <div className="image-upload"> | ||
61 | {(field.value && field.value !== 'delete') || this.state.path ? ( | ||
62 | <div> | ||
63 | <div | ||
64 | className="image-upload__preview" | ||
65 | style={({ | ||
66 | backgroundImage: `url("${this.state.path || field.value}")`, | ||
67 | })} | ||
68 | /> | ||
69 | <div className="image-upload__action"> | ||
70 | <button | ||
71 | type="button" | ||
72 | onClick={() => { | ||
73 | if (field.value) { | ||
74 | field.set('delete'); | ||
75 | } else { | ||
76 | this.setState({ | ||
77 | path: null, | ||
78 | }); | ||
79 | } | ||
80 | }} | ||
81 | > | ||
82 | <i className="mdi mdi-delete" /> | ||
83 | <p> | ||
84 | {textDelete} | ||
85 | </p> | ||
86 | </button> | ||
87 | <div className="image-upload__action-background" /> | ||
88 | </div> | ||
89 | </div> | ||
90 | ) : ( | ||
91 | <Dropzone | ||
92 | ref={(node) => { this.dropzoneRef = node; }} | ||
93 | onDrop={this.onDrop.bind(this)} | ||
94 | className={cssClasses} | ||
95 | multiple={multiple} | ||
96 | accept="image/jpeg, image/png" | ||
97 | > | ||
98 | <i className="mdi mdi-file-image" /> | ||
99 | <p> | ||
100 | {textUpload} | ||
101 | </p> | ||
102 | </Dropzone> | ||
103 | )} | ||
104 | </div> | ||
105 | </div> | ||
106 | ); | ||
107 | } | ||
108 | } | ||
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index 3c52152b1..c26195a1e 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js | |||
@@ -46,6 +46,10 @@ const messages = defineMessages({ | |||
46 | id: 'settings.service.form.indirectMessages', | 46 | id: 'settings.service.form.indirectMessages', |
47 | defaultMessage: '!!!Show message badge for all new messages', | 47 | defaultMessage: '!!!Show message badge for all new messages', |
48 | }, | 48 | }, |
49 | icon: { | ||
50 | id: 'settings.service.form.icon', | ||
51 | defaultMessage: '!!!Custom icon', | ||
52 | }, | ||
49 | }); | 53 | }); |
50 | 54 | ||
51 | @inject('stores', 'actions') @observer | 55 | @inject('stores', 'actions') @observer |
@@ -102,6 +106,12 @@ export default class EditServiceScreen extends Component { | |||
102 | value: !service.isMuted, | 106 | value: !service.isMuted, |
103 | default: true, | 107 | default: true, |
104 | }, | 108 | }, |
109 | customIcon: { | ||
110 | label: intl.formatMessage(messages.icon), | ||
111 | value: service.hasCustomUploadedIcon ? service.icon : false, | ||
112 | default: null, | ||
113 | type: 'file', | ||
114 | }, | ||
105 | }, | 115 | }, |
106 | }; | 116 | }; |
107 | 117 | ||
@@ -199,6 +209,12 @@ export default class EditServiceScreen extends Component { | |||
199 | return (<div>Loading...</div>); | 209 | return (<div>Loading...</div>); |
200 | } | 210 | } |
201 | 211 | ||
212 | if (!recipe) { | ||
213 | return ( | ||
214 | <div>something went wrong</div> | ||
215 | ); | ||
216 | } | ||
217 | |||
202 | const form = this.prepareForm(recipe, service); | 218 | const form = this.prepareForm(recipe, service); |
203 | 219 | ||
204 | return ( | 220 | return ( |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 2bfbf2455..7fc9eac1c 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -128,6 +128,9 @@ | |||
128 | "settings.service.form.headlineNotifications": "Notifications", | 128 | "settings.service.form.headlineNotifications": "Notifications", |
129 | "settings.service.form.headlineBadges": "Unread message badges", | 129 | "settings.service.form.headlineBadges": "Unread message badges", |
130 | "settings.service.form.headlineGeneral": "General", | 130 | "settings.service.form.headlineGeneral": "General", |
131 | "settings.service.form.icon": "Custom icon", | ||
132 | "settings.service.form.iconDelete": "Delete", | ||
133 | "settings.service.form.iconUpload": "Drop your image, or click here", | ||
131 | "settings.service.error.headline": "Error", | 134 | "settings.service.error.headline": "Error", |
132 | "settings.service.error.goBack": "Back to services", | 135 | "settings.service.error.goBack": "Back to services", |
133 | "settings.service.error.message": "Could not load service recipe.", | 136 | "settings.service.error.message": "Could not load service recipe.", |
diff --git a/src/models/Service.js b/src/models/Service.js index 0b19440e7..423510c7d 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -24,7 +24,8 @@ export default class Service { | |||
24 | @observable isNotificationEnabled = true; | 24 | @observable isNotificationEnabled = true; |
25 | @observable isBadgeEnabled = true; | 25 | @observable isBadgeEnabled = true; |
26 | @observable isIndirectMessageBadgeEnabled = true; | 26 | @observable isIndirectMessageBadgeEnabled = true; |
27 | @observable customIconUrl = ''; | 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) { |
@@ -42,7 +43,8 @@ export default class Service { | |||
42 | this.name = data.name || this.name; | 43 | this.name = data.name || this.name; |
43 | this.team = data.team || this.team; | 44 | this.team = data.team || this.team; |
44 | this.customUrl = data.customUrl || this.customUrl; | 45 | this.customUrl = data.customUrl || this.customUrl; |
45 | this.customIconUrl = data.customIconUrl || this.customIconUrl; | 46 | // this.customIconUrl = data.customIconUrl || this.customIconUrl; |
47 | this.iconUrl = data.iconUrl || this.iconUrl; | ||
46 | 48 | ||
47 | this.order = data.order !== undefined | 49 | this.order = data.order !== undefined |
48 | ? data.order : this.order; | 50 | ? data.order : this.order; |
@@ -61,6 +63,8 @@ export default class Service { | |||
61 | 63 | ||
62 | this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; | 64 | this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; |
63 | 65 | ||
66 | this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon; | ||
67 | |||
64 | this.recipe = recipe; | 68 | this.recipe = recipe; |
65 | 69 | ||
66 | autorun(() => { | 70 | autorun(() => { |
@@ -97,15 +101,15 @@ export default class Service { | |||
97 | } | 101 | } |
98 | 102 | ||
99 | @computed get icon() { | 103 | @computed get icon() { |
100 | if (this.hasCustomIcon) { | 104 | if (this.iconUrl) { |
101 | return this.customIconUrl; | 105 | return this.iconUrl; |
102 | } | 106 | } |
103 | 107 | ||
104 | return path.join(this.recipe.path, 'icon.svg'); | 108 | return path.join(this.recipe.path, 'icon.svg'); |
105 | } | 109 | } |
106 | 110 | ||
107 | @computed get hasCustomIcon() { | 111 | @computed get hasCustomIcon() { |
108 | return (this.customIconUrl !== ''); | 112 | return Boolean(this.iconUrl); |
109 | } | 113 | } |
110 | 114 | ||
111 | @computed get iconPNG() { | 115 | @computed get iconPNG() { |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index bd5014aaa..7300a76c8 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -174,9 +174,29 @@ export default class ServicesStore extends Store { | |||
174 | const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); | 174 | const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); |
175 | const request = this.updateServiceRequest.execute(serviceId, data); | 175 | const request = this.updateServiceRequest.execute(serviceId, data); |
176 | 176 | ||
177 | const newData = serviceData; | ||
178 | if (serviceData.iconFile) { | ||
179 | await request._promise; | ||
180 | |||
181 | newData.iconUrl = request.result.data.iconUrl; | ||
182 | newData.hasCustomUploadedIcon = true; | ||
183 | } | ||
184 | |||
177 | this.allServicesRequest.patch((result) => { | 185 | this.allServicesRequest.patch((result) => { |
178 | if (!result) return; | 186 | if (!result) return; |
179 | Object.assign(result.find(c => c.id === serviceId), serviceData); | 187 | |
188 | // patch custom icon deletion | ||
189 | if (data.customIcon === 'delete') { | ||
190 | data.iconUrl = ''; | ||
191 | data.hasCustomUploadedIcon = false; | ||
192 | } | ||
193 | |||
194 | // patch custom icon url | ||
195 | if (data.customIconUrl) { | ||
196 | data.iconUrl = data.customIconUrl; | ||
197 | } | ||
198 | |||
199 | Object.assign(result.find(c => c.id === serviceId), newData); | ||
180 | }); | 200 | }); |
181 | 201 | ||
182 | await request._promise; | 202 | await request._promise; |
@@ -325,7 +345,7 @@ export default class ServicesStore extends Store { | |||
325 | } | 345 | } |
326 | } else if (channel === 'avatar') { | 346 | } else if (channel === 'avatar') { |
327 | const url = args[0]; | 347 | const url = args[0]; |
328 | if (service.customIconUrl !== url) { | 348 | if (service.iconUrl !== url && !service.hasCustomUploadedIcon) { |
329 | service.customIconUrl = url; | 349 | service.customIconUrl = url; |
330 | 350 | ||
331 | this.actions.service.updateService({ | 351 | this.actions.service.updateService({ |
diff --git a/src/styles/image-upload.scss b/src/styles/image-upload.scss new file mode 100644 index 000000000..06176a7af --- /dev/null +++ b/src/styles/image-upload.scss | |||
@@ -0,0 +1,91 @@ | |||
1 | .image-upload { | ||
2 | position: absolute; | ||
3 | width: 140px; | ||
4 | height: 140px; | ||
5 | border: 1px solid $theme-gray-lighter; | ||
6 | border-radius: $theme-border-radius-small; | ||
7 | background: $theme-gray-lightest; | ||
8 | overflow: hidden; | ||
9 | margin-top: 5px; | ||
10 | |||
11 | &__preview, | ||
12 | &__action { | ||
13 | position: absolute; | ||
14 | top: 0; | ||
15 | left: 0; | ||
16 | right: 0; | ||
17 | } | ||
18 | |||
19 | &__preview { | ||
20 | z-index: 1; | ||
21 | background-size: cover; | ||
22 | background-size: 100%; | ||
23 | background-repeat: no-repeat; | ||
24 | background-position: center center; | ||
25 | border-radius: 3px; | ||
26 | } | ||
27 | |||
28 | &__action { | ||
29 | position: relative; | ||
30 | z-index: 10; | ||
31 | opacity: 0; | ||
32 | transition: opacity 0.5s; | ||
33 | display: flex; | ||
34 | justify-content: center; | ||
35 | |||
36 | &-background { | ||
37 | position: absolute; | ||
38 | top: 0; | ||
39 | left: 0; | ||
40 | right: 0; | ||
41 | bottom: 0; | ||
42 | background: rgba($theme-gray, 0.7); | ||
43 | z-index: 10; | ||
44 | } | ||
45 | |||
46 | button { | ||
47 | position: relative; | ||
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; | ||
76 | } | ||
77 | } | ||
78 | |||
79 | &:hover { | ||
80 | .image-upload__action { | ||
81 | opacity: 1; | ||
82 | } | ||
83 | } | ||
84 | } | ||
85 | |||
86 | .image-upload-wrapper { | ||
87 | .mdi { | ||
88 | font-size: 40px; | ||
89 | color: $theme-gray-light; | ||
90 | } | ||
91 | } \ No newline at end of file | ||
diff --git a/src/styles/main.scss b/src/styles/main.scss index 0a082729c..261396f6f 100644 --- a/src/styles/main.scss +++ b/src/styles/main.scss | |||
@@ -35,3 +35,4 @@ $mdi-font-path: '../node_modules/mdi/fonts'; | |||
35 | @import './button.scss'; | 35 | @import './button.scss'; |
36 | @import './searchInput.scss'; | 36 | @import './searchInput.scss'; |
37 | @import './select.scss'; | 37 | @import './select.scss'; |
38 | @import './image-upload.scss'; | ||
diff --git a/src/styles/settings.scss b/src/styles/settings.scss index 2da56c930..2182c9b5f 100644 --- a/src/styles/settings.scss +++ b/src/styles/settings.scss | |||
@@ -111,6 +111,26 @@ | |||
111 | &::-webkit-scrollbar-thumb:window-inactive { | 111 | &::-webkit-scrollbar-thumb:window-inactive { |
112 | background: none; | 112 | background: none; |
113 | } | 113 | } |
114 | |||
115 | .service-flex-grid { | ||
116 | display: flex; | ||
117 | } | ||
118 | |||
119 | .service-name { | ||
120 | flex: 1px; | ||
121 | } | ||
122 | |||
123 | .service-icon { | ||
124 | width: 140px; | ||
125 | float: right; | ||
126 | margin-top: 30px; | ||
127 | margin-left: 40px; | ||
128 | |||
129 | label { | ||
130 | font-weight: bold; | ||
131 | letter-spacing: -0.1px; | ||
132 | } | ||
133 | } | ||
114 | } | 134 | } |
115 | 135 | ||
116 | .settings__close { | 136 | .settings__close { |
@@ -135,6 +155,7 @@ | |||
135 | 155 | ||
136 | &__options { | 156 | &__options { |
137 | margin-top: 20px; | 157 | margin-top: 20px; |
158 | flex: 1; | ||
138 | } | 159 | } |
139 | 160 | ||
140 | &__settings-group { | 161 | &__settings-group { |
@@ -305,12 +326,6 @@ | |||
305 | } | 326 | } |
306 | } | 327 | } |
307 | 328 | ||
308 | // @include element(add-service-teaser) { | ||
309 | // height: auto; | ||
310 | // margin-top: 20px; | ||
311 | // display: block; | ||
312 | // text-align: center; | ||
313 | // } | ||
314 | .emoji { | 329 | .emoji { |
315 | display: block; | 330 | display: block; |
316 | font-size: 40px; | 331 | font-size: 40px; |