diff options
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/api/server/ServerApi.js | 42 | ||||
-rw-r--r-- | src/components/settings/services/EditServiceForm.js | 7 | ||||
-rw-r--r-- | src/components/ui/ImageUpload.js | 42 | ||||
-rw-r--r-- | src/containers/settings/EditServiceScreen.js | 7 | ||||
-rw-r--r-- | src/i18n/locales/en-US.json | 1 | ||||
-rw-r--r-- | src/models/Service.js | 11 | ||||
-rw-r--r-- | src/stores/ServicesStore.js | 14 | ||||
-rw-r--r-- | yarn.lock | 6 |
9 files changed, 86 insertions, 46 deletions
diff --git a/package.json b/package.json index 6c4c65cb8..5dae5bc7c 100644 --- a/package.json +++ b/package.json | |||
@@ -50,7 +50,7 @@ | |||
50 | "mkdirp": "^0.5.1", | 50 | "mkdirp": "^0.5.1", |
51 | "mobx": "^3.1.0", | 51 | "mobx": "^3.1.0", |
52 | "mobx-react": "^4.1.0", | 52 | "mobx-react": "^4.1.0", |
53 | "mobx-react-form": "1.24.0", | 53 | "mobx-react-form": "^1.32.2", |
54 | "mobx-react-router": "^3.1.2", | 54 | "mobx-react-router": "^3.1.2", |
55 | "moment": "^2.17.1", | 55 | "moment": "^2.17.1", |
56 | "normalize-url": "^1.9.1", | 56 | "normalize-url": "^1.9.1", |
diff --git a/src/api/server/ServerApi.js b/src/api/server/ServerApi.js index 8b3136d27..6b96f709e 100644 --- a/src/api/server/ServerApi.js +++ b/src/api/server/ServerApi.js | |||
@@ -167,27 +167,65 @@ export default class ServerApi { | |||
167 | throw request; | 167 | throw request; |
168 | } | 168 | } |
169 | const serviceData = await request.json(); | 169 | const serviceData = await request.json(); |
170 | |||
171 | if (data.iconFile) { | ||
172 | const iconUrl = await this.uploadServiceIcon(serviceData.data.id, data.iconFile); | ||
173 | |||
174 | serviceData.data.iconUrl = iconUrl; | ||
175 | } | ||
176 | |||
170 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); | 177 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); |
171 | 178 | ||
172 | console.debug('ServerApi::createService resolves', service); | 179 | console.debug('ServerApi::createService resolves', service); |
173 | return service; | 180 | return service; |
174 | } | 181 | } |
175 | 182 | ||
176 | async updateService(recipeId, data) { | 183 | async updateService(serviceId, rawData) { |
177 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${recipeId}`, this._prepareAuthRequest({ | 184 | const data = rawData; |
185 | |||
186 | if (data.iconFile) { | ||
187 | await this.uploadServiceIcon(serviceId, data.iconFile); | ||
188 | } | ||
189 | |||
190 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${serviceId}`, this._prepareAuthRequest({ | ||
178 | method: 'PUT', | 191 | method: 'PUT', |
179 | body: JSON.stringify(data), | 192 | body: JSON.stringify(data), |
180 | })); | 193 | })); |
194 | |||
181 | if (!request.ok) { | 195 | if (!request.ok) { |
182 | throw request; | 196 | throw request; |
183 | } | 197 | } |
198 | |||
184 | const serviceData = await request.json(); | 199 | const serviceData = await request.json(); |
200 | |||
185 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); | 201 | const service = Object.assign(serviceData, { data: await this._prepareServiceModel(serviceData.data) }); |
186 | 202 | ||
187 | console.debug('ServerApi::updateService resolves', service); | 203 | console.debug('ServerApi::updateService resolves', service); |
188 | return service; | 204 | return service; |
189 | } | 205 | } |
190 | 206 | ||
207 | async uploadServiceIcon(serviceId, icon) { | ||
208 | const formData = new FormData(); | ||
209 | formData.append('icon', icon); | ||
210 | |||
211 | const requestData = this._prepareAuthRequest({ | ||
212 | method: 'PUT', | ||
213 | body: formData, | ||
214 | }); | ||
215 | |||
216 | delete requestData.headers['Content-Type']; | ||
217 | |||
218 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/${serviceId}`, requestData); | ||
219 | |||
220 | if (!request.ok) { | ||
221 | throw request; | ||
222 | } | ||
223 | |||
224 | const serviceData = await request.json(); | ||
225 | |||
226 | return serviceData.data.iconUrl; | ||
227 | } | ||
228 | |||
191 | async reorderService(data) { | 229 | async reorderService(data) { |
192 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/reorder`, this._prepareAuthRequest({ | 230 | const request = await window.fetch(`${SERVER_URL}/${API_VERSION}/service/reorder`, this._prepareAuthRequest({ |
193 | method: 'PUT', | 231 | method: 'PUT', |
diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index ee69d53aa..4f2f98a01 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js | |||
@@ -127,6 +127,11 @@ export default class EditServiceForm extends Component { | |||
127 | const values = form.values(); | 127 | const values = form.values(); |
128 | let isValid = true; | 128 | let isValid = true; |
129 | 129 | ||
130 | const files = form.$('customIcon').files; | ||
131 | if (files) { | ||
132 | values.iconFile = files[0]; | ||
133 | } | ||
134 | |||
130 | if (recipe.validateUrl && values.customUrl) { | 135 | if (recipe.validateUrl && values.customUrl) { |
131 | this.setState({ isValidatingCustomUrl: true }); | 136 | this.setState({ isValidatingCustomUrl: true }); |
132 | try { | 137 | try { |
@@ -224,7 +229,7 @@ export default class EditServiceForm extends Component { | |||
224 | </div> | 229 | </div> |
225 | <div className="service-icon"> | 230 | <div className="service-icon"> |
226 | {/* <Input field={form.$('name')} focus /> */} | 231 | {/* <Input field={form.$('name')} focus /> */} |
227 | <ImageUpload field={form.$('icon')} /> | 232 | <ImageUpload field={form.$('customIcon')} /> |
228 | </div> | 233 | </div> |
229 | </div> | 234 | </div> |
230 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( | 235 | {(recipe.hasTeamId || recipe.hasCustomUrl) && ( |
diff --git a/src/components/ui/ImageUpload.js b/src/components/ui/ImageUpload.js index d07c3649c..f25d966f4 100644 --- a/src/components/ui/ImageUpload.js +++ b/src/components/ui/ImageUpload.js | |||
@@ -12,82 +12,64 @@ 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 | // disabled: PropTypes.bool, | ||
16 | // onClick: PropTypes.func, | ||
17 | // type: PropTypes.string, | ||
18 | // buttonType: PropTypes.string, | ||
19 | // loaded: PropTypes.bool, | ||
20 | // htmlForm: PropTypes.string, | ||
21 | }; | 15 | }; |
22 | 16 | ||
23 | static defaultProps = { | 17 | static defaultProps = { |
24 | className: null, | 18 | className: null, |
25 | multiple: false, | 19 | multiple: false, |
26 | // disabled: false, | ||
27 | // onClick: () => {}, | ||
28 | // type: 'button', | ||
29 | // buttonType: '', | ||
30 | // loaded: true, | ||
31 | // htmlForm: '', | ||
32 | }; | 20 | }; |
33 | 21 | ||
34 | dropzoneRef = null; | ||
35 | |||
36 | state = { | 22 | state = { |
37 | path: null, | 23 | path: null, |
38 | } | 24 | } |
39 | 25 | ||
40 | onDrop(acceptedFiles) { | 26 | onDrop(acceptedFiles) { |
41 | // const req = request.post('/upload'); | ||
42 | acceptedFiles.forEach((file) => { | 27 | acceptedFiles.forEach((file) => { |
43 | console.log(file); | ||
44 | this.setState({ | 28 | this.setState({ |
45 | path: file.path, | 29 | path: file.path, |
46 | }); | 30 | }); |
47 | // req.attach(file.name, file); | 31 | this.props.field.onDrop(file); |
48 | }); | 32 | }); |
49 | // req.end(callback); | ||
50 | } | 33 | } |
51 | 34 | ||
35 | dropzoneRef = null; | ||
36 | |||
52 | render() { | 37 | render() { |
53 | const { | 38 | const { |
54 | field, | 39 | field, |
55 | className, | 40 | className, |
56 | multiple, | 41 | multiple, |
57 | // disabled, | ||
58 | // onClick, | ||
59 | // type, | ||
60 | // buttonType, | ||
61 | // loaded, | ||
62 | // htmlForm, | ||
63 | } = this.props; | 42 | } = this.props; |
64 | 43 | ||
65 | const cssClasses = classnames({ | 44 | const cssClasses = classnames({ |
66 | 'franz-form__button': true, | 45 | 'franz-form__button': true, |
67 | // [`franz-form__button--${buttonType}`]: buttonType, | ||
68 | [`${className}`]: className, | 46 | [`${className}`]: className, |
69 | }); | 47 | }); |
70 | 48 | ||
71 | return ( | 49 | return ( |
72 | <div> | 50 | <div> |
73 | {field.label} | 51 | {field.label} |
74 | {this.state.path ? ( | 52 | {(field.value && field.value !== 'delete') || this.state.path ? ( |
75 | <div | 53 | <div |
76 | className="image-upload" | 54 | className="image-upload" |
77 | > | 55 | > |
78 | <div | 56 | <div |
79 | className="image-upload__preview" | 57 | className="image-upload__preview" |
80 | style={({ | 58 | style={({ |
81 | backgroundImage: `url(${this.state.path})`, | 59 | backgroundImage: `url(${field.value || this.state.path})`, |
82 | })} | 60 | })} |
83 | /> | 61 | /> |
84 | <div className="image-upload__action"> | 62 | <div className="image-upload__action"> |
85 | <button | 63 | <button |
86 | type="button" | 64 | type="button" |
87 | onClick={() => { | 65 | onClick={() => { |
88 | this.setState({ | 66 | if (field.value) { |
89 | path: null, | 67 | field.value = 'delete'; |
90 | }); | 68 | } else { |
69 | this.setState({ | ||
70 | path: null, | ||
71 | }); | ||
72 | } | ||
91 | }} | 73 | }} |
92 | > | 74 | > |
93 | remove icon | 75 | remove icon |
diff --git a/src/containers/settings/EditServiceScreen.js b/src/containers/settings/EditServiceScreen.js index 8827896ef..8d3a268ad 100644 --- a/src/containers/settings/EditServiceScreen.js +++ b/src/containers/settings/EditServiceScreen.js | |||
@@ -106,10 +106,11 @@ export default class EditServiceScreen extends Component { | |||
106 | value: !service.isMuted, | 106 | value: !service.isMuted, |
107 | default: true, | 107 | default: true, |
108 | }, | 108 | }, |
109 | icon: { | 109 | customIcon: { |
110 | label: intl.formatMessage(messages.icon), | 110 | label: intl.formatMessage(messages.icon), |
111 | value: service.icon, | 111 | value: service.hasCustomIcon ? service.icon : false, |
112 | default: true, | 112 | default: null, |
113 | type: 'file', | ||
113 | }, | 114 | }, |
114 | }, | 115 | }, |
115 | }; | 116 | }; |
diff --git a/src/i18n/locales/en-US.json b/src/i18n/locales/en-US.json index 567537d75..bfb95da04 100644 --- a/src/i18n/locales/en-US.json +++ b/src/i18n/locales/en-US.json | |||
@@ -127,6 +127,7 @@ | |||
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.error.headline": "Error", | 131 | "settings.service.error.headline": "Error", |
131 | "settings.service.error.goBack": "Back to services", | 132 | "settings.service.error.goBack": "Back to services", |
132 | "settings.service.error.message": "Could not load service recipe.", | 133 | "settings.service.error.message": "Could not load service recipe.", |
diff --git a/src/models/Service.js b/src/models/Service.js index 0b19440e7..652d2594c 100644 --- a/src/models/Service.js +++ b/src/models/Service.js | |||
@@ -24,7 +24,7 @@ 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 hasCrashed = false; | 28 | @observable hasCrashed = false; |
29 | 29 | ||
30 | constructor(data, recipe) { | 30 | constructor(data, recipe) { |
@@ -42,7 +42,8 @@ export default class Service { | |||
42 | this.name = data.name || this.name; | 42 | this.name = data.name || this.name; |
43 | this.team = data.team || this.team; | 43 | this.team = data.team || this.team; |
44 | this.customUrl = data.customUrl || this.customUrl; | 44 | this.customUrl = data.customUrl || this.customUrl; |
45 | this.customIconUrl = data.customIconUrl || this.customIconUrl; | 45 | // this.customIconUrl = data.customIconUrl || this.customIconUrl; |
46 | this.iconUrl = data.iconUrl || this.iconUrl; | ||
46 | 47 | ||
47 | this.order = data.order !== undefined | 48 | this.order = data.order !== undefined |
48 | ? data.order : this.order; | 49 | ? data.order : this.order; |
@@ -97,15 +98,15 @@ export default class Service { | |||
97 | } | 98 | } |
98 | 99 | ||
99 | @computed get icon() { | 100 | @computed get icon() { |
100 | if (this.hasCustomIcon) { | 101 | if (this.iconUrl) { |
101 | return this.customIconUrl; | 102 | return this.iconUrl; |
102 | } | 103 | } |
103 | 104 | ||
104 | return path.join(this.recipe.path, 'icon.svg'); | 105 | return path.join(this.recipe.path, 'icon.svg'); |
105 | } | 106 | } |
106 | 107 | ||
107 | @computed get hasCustomIcon() { | 108 | @computed get hasCustomIcon() { |
108 | return (this.customIconUrl !== ''); | 109 | return Boolean(this.iconUrl); |
109 | } | 110 | } |
110 | 111 | ||
111 | @computed get iconPNG() { | 112 | @computed get iconPNG() { |
diff --git a/src/stores/ServicesStore.js b/src/stores/ServicesStore.js index 66f37af26..4fb5c9767 100644 --- a/src/stores/ServicesStore.js +++ b/src/stores/ServicesStore.js | |||
@@ -172,9 +172,21 @@ export default class ServicesStore extends Store { | |||
172 | const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); | 172 | const data = this._cleanUpTeamIdAndCustomUrl(service.recipe.id, serviceData); |
173 | const request = this.updateServiceRequest.execute(serviceId, data); | 173 | const request = this.updateServiceRequest.execute(serviceId, data); |
174 | 174 | ||
175 | const newData = serviceData; | ||
176 | if (serviceData.iconFile) { | ||
177 | await request._promise; | ||
178 | |||
179 | newData.iconUrl = request.result.data.iconUrl; | ||
180 | } | ||
181 | |||
175 | this.allServicesRequest.patch((result) => { | 182 | this.allServicesRequest.patch((result) => { |
176 | if (!result) return; | 183 | if (!result) return; |
177 | Object.assign(result.find(c => c.id === serviceId), serviceData); | 184 | |
185 | if (data.customIcon === 'delete') { | ||
186 | data.iconUrl = ''; | ||
187 | } | ||
188 | |||
189 | Object.assign(result.find(c => c.id === serviceId), newData); | ||
178 | }); | 190 | }); |
179 | 191 | ||
180 | await request._promise; | 192 | await request._promise; |
@@ -4231,9 +4231,9 @@ mksnapshot@^0.3.0: | |||
4231 | fs-extra "0.26.7" | 4231 | fs-extra "0.26.7" |
4232 | request "^2.79.0" | 4232 | request "^2.79.0" |
4233 | 4233 | ||
4234 | mobx-react-form@1.24.0: | 4234 | mobx-react-form@^1.32.2: |
4235 | version "1.24.0" | 4235 | version "1.32.2" |
4236 | resolved "https://registry.yarnpkg.com/mobx-react-form/-/mobx-react-form-1.24.0.tgz#bc9fbd652e65fb1f2b51917865d465fcaab7f0d9" | 4236 | resolved "https://registry.yarnpkg.com/mobx-react-form/-/mobx-react-form-1.32.2.tgz#5610dd0e4fab006acf2daf1becbedecad182a5a0" |
4237 | dependencies: | 4237 | dependencies: |
4238 | lodash "^4.16.2" | 4238 | lodash "^4.16.2" |
4239 | 4239 | ||