aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Stefan Malzner <stefan@adlk.io>2018-01-06 23:51:04 +0100
committerLibravatar GitHub <noreply@github.com>2018-01-06 23:51:04 +0100
commitfb16f121e8ff6678c74148cd456b964a85742fbe (patch)
tree5534162572aca6bda4d22984ab6103c76510d193
parentMerge pull request #541 from meetfranz/feature/external-links (diff)
parentfix displaying old icon while uploading new icon (diff)
downloadferdium-app-fb16f121e8ff6678c74148cd456b964a85742fbe.tar.gz
ferdium-app-fb16f121e8ff6678c74148cd456b964a85742fbe.tar.zst
ferdium-app-fb16f121e8ff6678c74148cd456b964a85742fbe.zip
Merge pull request #525 from meetfranz/feature/icon-upload
[PR] Feature/icon upload
-rw-r--r--package.json3
-rw-r--r--src/api/server/ServerApi.js42
-rw-r--r--src/components/settings/services/EditServiceForm.js75
-rw-r--r--src/components/ui/ImageUpload.js108
-rw-r--r--src/containers/settings/EditServiceScreen.js16
-rw-r--r--src/i18n/locales/en-US.json3
-rw-r--r--src/models/Service.js14
-rw-r--r--src/stores/ServicesStore.js24
-rw-r--r--src/styles/image-upload.scss91
-rw-r--r--src/styles/main.scss1
-rw-r--r--src/styles/settings.scss27
-rw-r--r--yarn.lock17
12 files changed, 377 insertions, 44 deletions
diff --git a/package.json b/package.json
index 0a2bd8991..5f5dd4182 100644
--- a/package.json
+++ b/package.json
@@ -51,7 +51,7 @@
51 "mkdirp": "^0.5.1", 51 "mkdirp": "^0.5.1",
52 "mobx": "^3.1.0", 52 "mobx": "^3.1.0",
53 "mobx-react": "^4.1.0", 53 "mobx-react": "^4.1.0",
54 "mobx-react-form": "1.24.0", 54 "mobx-react-form": "^1.32.2",
55 "mobx-react-router": "^3.1.2", 55 "mobx-react-router": "^3.1.2",
56 "moment": "^2.17.1", 56 "moment": "^2.17.1",
57 "normalize-url": "^1.9.1", 57 "normalize-url": "^1.9.1",
@@ -61,6 +61,7 @@
61 "react": "^15.4.1", 61 "react": "^15.4.1",
62 "react-addons-css-transition-group": "^15.4.2", 62 "react-addons-css-transition-group": "^15.4.2",
63 "react-dom": "^15.4.1", 63 "react-dom": "^15.4.1",
64 "react-dropzone": "^4.2.1",
64 "react-electron-web-view": "^2.0.1", 65 "react-electron-web-view": "^2.0.1",
65 "react-intl": "^2.3.0", 66 "react-intl": "^2.3.0",
66 "react-loader": "^2.4.0", 67 "react-loader": "^2.4.0",
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';
13import Input from '../../ui/Input'; 13import Input from '../../ui/Input';
14import Toggle from '../../ui/Toggle'; 14import Toggle from '../../ui/Toggle';
15import Button from '../../ui/Button'; 15import Button from '../../ui/Button';
16import ImageUpload from '../../ui/ImageUpload';
16 17
17const messages = defineMessages({ 18const 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 @@
1import React, { Component } from 'react';
2import PropTypes from 'prop-types';
3import { observer } from 'mobx-react';
4import { Field } from 'mobx-react-form';
5// import Loader from 'react-loader';
6import classnames from 'classnames';
7import Dropzone from 'react-dropzone';
8
9@observer
10export 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;
diff --git a/yarn.lock b/yarn.lock
index dbcb33647..58befebf1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -341,6 +341,10 @@ asynckit@^0.4.0:
341 version "0.4.0" 341 version "0.4.0"
342 resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 342 resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
343 343
344attr-accept@^1.0.3:
345 version "1.1.0"
346 resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-1.1.0.tgz#b5cd35227f163935a8f1de10ed3eba16941f6be6"
347
344"auto-launch@https://github.com/meetfranz/node-auto-launch.git": 348"auto-launch@https://github.com/meetfranz/node-auto-launch.git":
345 version "5.0.1" 349 version "5.0.1"
346 resolved "https://github.com/meetfranz/node-auto-launch.git#b90a0470467eb84435e6554ae9db1e2c6db79e61" 350 resolved "https://github.com/meetfranz/node-auto-launch.git#b90a0470467eb84435e6554ae9db1e2c6db79e61"
@@ -4277,9 +4281,9 @@ mksnapshot@^0.3.0:
4277 fs-extra "0.26.7" 4281 fs-extra "0.26.7"
4278 request "^2.79.0" 4282 request "^2.79.0"
4279 4283
4280mobx-react-form@1.24.0: 4284mobx-react-form@^1.32.2:
4281 version "1.24.0" 4285 version "1.32.2"
4282 resolved "https://registry.yarnpkg.com/mobx-react-form/-/mobx-react-form-1.24.0.tgz#bc9fbd652e65fb1f2b51917865d465fcaab7f0d9" 4286 resolved "https://registry.yarnpkg.com/mobx-react-form/-/mobx-react-form-1.32.2.tgz#5610dd0e4fab006acf2daf1becbedecad182a5a0"
4283 dependencies: 4287 dependencies:
4284 lodash "^4.16.2" 4288 lodash "^4.16.2"
4285 4289
@@ -5045,6 +5049,13 @@ react-dom@^15.4.1:
5045 object-assign "^4.1.0" 5049 object-assign "^4.1.0"
5046 prop-types "^15.5.10" 5050 prop-types "^15.5.10"
5047 5051
5052react-dropzone@^4.2.1:
5053 version "4.2.1"
5054 resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-4.2.1.tgz#695e80bd0b065f1181e69f2d0f6d1d5cc72664c9"
5055 dependencies:
5056 attr-accept "^1.0.3"
5057 prop-types "^15.5.7"
5058
5048react-electron-web-view@^2.0.1: 5059react-electron-web-view@^2.0.1:
5049 version "2.0.1" 5060 version "2.0.1"
5050 resolved "https://registry.yarnpkg.com/react-electron-web-view/-/react-electron-web-view-2.0.1.tgz#984b7bbbeb77e35bcca921dc50120fc8f2b0f27d" 5061 resolved "https://registry.yarnpkg.com/react-electron-web-view/-/react-electron-web-view-2.0.1.tgz#984b7bbbeb77e35bcca921dc50120fc8f2b0f27d"