From 150cfe764aeb9e93341ba2f231fd121fe85472af Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Wed, 27 Dec 2017 13:09:17 +0100 Subject: First working draft of icon upload --- src/i18n/locales/en-US.json | 1 + 1 file changed, 1 insertion(+) (limited to 'src/i18n/locales/en-US.json') 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 @@ "settings.service.form.headlineNotifications": "Notifications", "settings.service.form.headlineBadges": "Unread message badges", "settings.service.form.headlineGeneral": "General", + "settings.service.form.icon": "Icon", "settings.service.error.headline": "Error", "settings.service.error.goBack": "Back to services", "settings.service.error.message": "Could not load service recipe.", -- cgit v1.2.3-70-g09d2 From 9e2a998cccee808d8f2f16d4dc6f45d3c069ec32 Mon Sep 17 00:00:00 2001 From: Danny Qiu Date: Fri, 29 Dec 2017 01:31:52 -0500 Subject: Add button to clear service cache --- src/actions/service.js | 3 +++ src/api/LocalApi.js | 4 +++ src/api/ServicesApi.js | 7 ++++- src/api/server/LocalApi.js | 11 ++++++++ .../settings/services/EditServiceForm.js | 30 ++++++++++++++++++++++ src/containers/settings/EditServiceScreen.js | 11 ++++++++ src/i18n/locales/en-US.json | 2 ++ src/stores/ServicesStore.js | 9 +++++++ src/styles/button.scss | 12 +++++++++ 9 files changed, 88 insertions(+), 1 deletion(-) (limited to 'src/i18n/locales/en-US.json') diff --git a/src/actions/service.js b/src/actions/service.js index e3100e986..5d483b12a 100644 --- a/src/actions/service.js +++ b/src/actions/service.js @@ -25,6 +25,9 @@ export default { serviceId: PropTypes.string.isRequired, redirect: PropTypes.string, }, + clearCache: { + serviceId: PropTypes.string.isRequired, + }, setUnreadMessageCount: { serviceId: PropTypes.string.isRequired, count: PropTypes.object.isRequired, diff --git a/src/api/LocalApi.js b/src/api/LocalApi.js index 6f2b049d6..d52e9cd10 100644 --- a/src/api/LocalApi.js +++ b/src/api/LocalApi.js @@ -15,4 +15,8 @@ export default class LocalApi { removeKey(key) { return this.local.removeKey(key); } + + clearAppCache() { + return this.local.clearAppCache(); + } } diff --git a/src/api/ServicesApi.js b/src/api/ServicesApi.js index 3cb40ba0d..36ed9482f 100644 --- a/src/api/ServicesApi.js +++ b/src/api/ServicesApi.js @@ -1,5 +1,6 @@ export default class ServicesApi { - constructor(server) { + constructor(server, local) { + this.local = local; this.server = server; } @@ -30,4 +31,8 @@ export default class ServicesApi { reorder(data) { return this.server.reorderService(data); } + + clearCache(serviceId) { + return this.local.clearCache(serviceId); + } } diff --git a/src/api/server/LocalApi.js b/src/api/server/LocalApi.js index 79ac6e12f..fec89f948 100644 --- a/src/api/server/LocalApi.js +++ b/src/api/server/LocalApi.js @@ -1,3 +1,7 @@ +import { remote } from 'electron'; + +const { session } = remote; + export default class LocalApi { // App async updateAppSettings(data) { @@ -30,4 +34,11 @@ export default class LocalApi { localStorage.setItem('app', JSON.stringify(settings)); } } + + // Services + async clearCache(serviceId) { + console.debug(`Clearing cache for persist:service-${serviceId}`); + const s = session.fromPartition(`persist:service-${serviceId}`); + await new Promise(resolve => s.clearCache(resolve)); + } } diff --git a/src/components/settings/services/EditServiceForm.js b/src/components/settings/services/EditServiceForm.js index 4458c4c5a..b5300f605 100644 --- a/src/components/settings/services/EditServiceForm.js +++ b/src/components/settings/services/EditServiceForm.js @@ -71,6 +71,14 @@ const messages = defineMessages({ id: 'settings.service.form.isMutedInfo', defaultMessage: '!!!When disabled, all notification sounds and audio playback are muted', }, + buttonClearCache: { + id: 'settings.service.form.buttonClearCache', + defaultMessage: '!!!Clear cache', + }, + buttonClearingCache: { + id: 'settings.service.form.buttonClearingCache', + defaultMessage: '!!!Clearing cache', + }, headlineNotifications: { id: 'settings.service.form.headlineNotifications', defaultMessage: '!!!Notifications', @@ -102,8 +110,10 @@ export default class EditServiceForm extends Component { form: PropTypes.instanceOf(Form).isRequired, onSubmit: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, + onClearCache: PropTypes.func.isRequired, isSaving: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired, + isClearingCache: PropTypes.bool.isRequired, }; static defaultProps = { @@ -158,7 +168,9 @@ export default class EditServiceForm extends Component { form, isSaving, isDeleting, + isClearingCache, onDelete, + onClearCache, } = this.props; const { intl } = this.context; @@ -181,6 +193,23 @@ export default class EditServiceForm extends Component { /> ); + const clearCacheButton = isClearingCache ? ( + -
+
+ +
+ {(field.value && field.value !== 'delete') || this.state.path ? ( +
+
+
+ +
+
-
- ) : ( - { this.dropzoneRef = node; }} - onDrop={this.onDrop.bind(this)} - className={cssClasses} - multiple={multiple} - accept="image/jpeg, image/png" - > -

Try dropping some files here, or click to select files to upload.

-
- )} + ) : ( + { this.dropzoneRef = node; }} + onDrop={this.onDrop.bind(this)} + className={cssClasses} + multiple={multiple} + accept="image/jpeg, image/png" + > + +

+ {textUpload} +

+
+ )} +
); } 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({ }, icon: { id: 'settings.service.form.icon', - defaultMessage: '!!!Icon', + defaultMessage: '!!!Custom icon', }, }); @@ -108,7 +108,7 @@ export default class EditServiceScreen extends Component { }, customIcon: { label: intl.formatMessage(messages.icon), - value: service.hasCustomIcon ? service.icon : false, + value: service.hasCustomUploadedIcon ? service.icon : false, default: null, type: 'file', }, 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 @@ "settings.service.form.headlineNotifications": "Notifications", "settings.service.form.headlineBadges": "Unread message badges", "settings.service.form.headlineGeneral": "General", - "settings.service.form.icon": "Icon", + "settings.service.form.icon": "Custom icon", + "settings.service.form.iconDelete": "Delete", + "settings.service.form.iconUpload": "Drop your image, or click here", "settings.service.error.headline": "Error", "settings.service.error.goBack": "Back to services", "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 { @observable isBadgeEnabled = true; @observable isIndirectMessageBadgeEnabled = true; @observable iconUrl = ''; + @observable hasCustomUploadedIcon = false; @observable hasCrashed = false; constructor(data, recipe) { @@ -62,6 +63,8 @@ export default class Service { this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted; + this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon; + this.recipe = recipe; 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 { await request._promise; newData.iconUrl = request.result.data.iconUrl; + newData.hasCustomUploadedIcon = true; } this.allServicesRequest.patch((result) => { if (!result) return; + // patch custom icon deletion if (data.customIcon === 'delete') { data.iconUrl = ''; + data.hasCustomUploadedIcon = false; + } + + // patch custom icon url + if (data.customIconUrl) { + data.iconUrl = data.customIconUrl; } Object.assign(result.find(c => c.id === serviceId), newData); @@ -328,7 +336,7 @@ export default class ServicesStore extends Store { } } else if (channel === 'avatar') { const url = args[0]; - if (service.customIconUrl !== url) { + if (service.iconUrl !== url && !service.hasCustomUploadedIcon) { service.customIconUrl = url; 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 @@ .image-upload { position: absolute; - width: 100px; - height: 100px; - border-radius: $theme-border-radius; + width: 140px; + height: 140px; + border: 1px solid $theme-gray-lighter; + border-radius: $theme-border-radius-small; + background: $theme-gray-lightest; overflow: hidden; + margin-top: 5px; &__preview, &__action { @@ -19,7 +22,7 @@ background-size: 100%; background-repeat: no-repeat; background-position: center center; - margin: 5px; + border-radius: 3px; } &__action { @@ -27,6 +30,8 @@ z-index: 10; opacity: 0; transition: opacity 0.5s; + display: flex; + justify-content: center; &-background { position: absolute; @@ -41,6 +46,33 @@ button { position: relative; z-index: 100; + color: #FFF; + + .mdi { + color: #FFF; + } + } + } + + &__dropzone { + text-align: center; + border-radius: 5px; + padding: 10px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + } + + &__dropzone, + button { + .mdi { + margin-bottom: 5px; + } + + p { + font-size: 10px; + line-height: 10px; } } @@ -49,4 +81,11 @@ opacity: 1; } } +} + +.image-upload-wrapper { + .mdi { + font-size: 40px; + color: $theme-gray-light; + } } \ 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 @@ } .service-icon { - width: 30%; - min-width: 100px; + width: 140px; + float: right; + margin-top: 30px; margin-left: 40px; + + label { + font-weight: bold; + letter-spacing: -0.1px; + } } } @@ -167,6 +173,7 @@ &__options { margin-top: 20px; + flex: 1; } &__settings-group { -- cgit v1.2.3-70-g09d2 From 7e784c699554dd85be3e9e219c59578995cadd38 Mon Sep 17 00:00:00 2001 From: Stefan Malzner Date: Wed, 3 Jan 2018 15:22:05 +0100 Subject: feat(Services): Improve user experience of service search --- .../settings/recipes/RecipesDashboard.js | 20 +++++---- .../settings/services/ServicesDashboard.js | 42 +++++++++++++++---- src/components/ui/SearchInput.js | 48 ++++++++-------------- src/containers/settings/ServicesScreen.js | 1 + src/i18n/locales/en-US.json | 1 + src/styles/searchInput.scss | 16 ++++++++ src/styles/settings.scss | 22 +--------- 7 files changed, 85 insertions(+), 65 deletions(-) (limited to 'src/i18n/locales/en-US.json') diff --git a/src/components/settings/recipes/RecipesDashboard.js b/src/components/settings/recipes/RecipesDashboard.js index b6ade5da4..4610c69a5 100644 --- a/src/components/settings/recipes/RecipesDashboard.js +++ b/src/components/settings/recipes/RecipesDashboard.js @@ -16,6 +16,10 @@ const messages = defineMessages({ id: 'settings.recipes.headline', defaultMessage: '!!!Available Services', }, + searchService: { + id: 'settings.searchService', + defaultMessage: '!!!Search service', + }, mostPopularRecipes: { id: 'settings.recipes.mostPopular', defaultMessage: '!!!Most popular', @@ -81,13 +85,7 @@ export default class RecipesDashboard extends Component { return (
- searchRecipes(e)} - onReset={() => resetSearch()} - throttle - /> +

{intl.formatMessage(messages.headline)}

{serviceStatus.length > 0 && serviceStatus.includes('created') && ( @@ -101,7 +99,13 @@ export default class RecipesDashboard extends Component { )} - {/* {!searchNeedle && ( */} + searchRecipes(e)} + onReset={() => resetSearch()} + autoFocus + throttle + />
- filterServices({ needle })} - onReset={() => resetFilter()} - /> +

{intl.formatMessage(messages.headline)}

+ {!isLoading && ( + filterServices({ needle })} + onReset={() => resetFilter()} + autoFocus + /> + )} {!isLoading && servicesRequestFailed && (
)} - {!isLoading && services.length === 0 && ( + {!isLoading && services.length === 0 && !searchNeedle && (

@@ -132,6 +150,16 @@ export default class ServicesDashboard extends Component { {intl.formatMessage(messages.discoverServices)}

)} + {!isLoading && services.length === 0 && searchNeedle && ( +
+

+ + + + {intl.formatMessage(messages.noServiceFound)} +

+
+ )} {isLoading ? ( ) : ( diff --git a/src/components/ui/SearchInput.js b/src/components/ui/SearchInput.js index bca412cef..a94cde201 100644 --- a/src/components/ui/SearchInput.js +++ b/src/components/ui/SearchInput.js @@ -9,36 +9,46 @@ import { debounce } from 'lodash'; export default class SearchInput extends Component { static propTypes = { value: PropTypes.string, - defaultValue: PropTypes.string, + placeholder: PropTypes.string, className: PropTypes.string, onChange: PropTypes.func, onReset: PropTypes.func, name: PropTypes.string, throttle: PropTypes.bool, throttleDelay: PropTypes.number, + autoFocus: PropTypes.bool, }; static defaultProps = { value: '', - defaultValue: '', + placeholder: '', className: '', name: uuidv1(), throttle: false, throttleDelay: 250, onChange: () => null, onReset: () => null, + autoFocus: false, } constructor(props) { super(props); this.state = { - value: props.value || props.defaultValue, + value: props.value, }; this.throttledOnChange = debounce(this.throttledOnChange, this.props.throttleDelay); } + componentDidMount() { + const { autoFocus } = this.props; + + if (autoFocus) { + this.input.focus(); + } + } + onChange(e) { const { throttle, onChange } = this.props; const { value } = e.target; @@ -52,26 +62,6 @@ export default class SearchInput extends Component { } } - onClick() { - const { defaultValue } = this.props; - const { value } = this.state; - - if (value === defaultValue) { - this.setState({ value: '' }); - } - - this.input.focus(); - } - - onBlur() { - const { defaultValue } = this.props; - const { value } = this.state; - - if (value === '') { - this.setState({ value: defaultValue }); - } - } - throttledOnChange(e) { const { onChange } = this.props; @@ -79,8 +69,8 @@ export default class SearchInput extends Component { } reset() { - const { defaultValue, onReset } = this.props; - this.setState({ value: defaultValue }); + const { onReset } = this.props; + this.setState({ value: '' }); onReset(); } @@ -88,7 +78,7 @@ export default class SearchInput extends Component { input = null; render() { - const { className, name, defaultValue } = this.props; + const { className, name, placeholder } = this.props; const { value } = this.state; return ( @@ -101,18 +91,16 @@ export default class SearchInput extends Component {
{recipe.message && ( diff --git a/src/components/settings/settings/EditSettingsForm.js b/src/components/settings/settings/EditSettingsForm.js index 074a4b731..4f027638c 100644 --- a/src/components/settings/settings/EditSettingsForm.js +++ b/src/components/settings/settings/EditSettingsForm.js @@ -40,13 +40,13 @@ const messages = defineMessages({ id: 'settings.app.translationHelp', defaultMessage: '!!!Help us to translate Franz into your language.', }, + cacheInfo: { + id: 'settings.app.cacheInfo', + defaultMessage: '!!!Franz cache is currently using {size} of disk space.', + }, buttonClearAllCache: { id: 'settings.app.buttonClearAllCache', - defaultMessage: '!!!Clear global cache for Franz and all services', - }, - buttonClearingAllCache: { - id: 'settings.app.buttonClearingAllCache', - defaultMessage: '!!!Clearing global cache...', + defaultMessage: '!!!Clear cache', }, buttonSearchForUpdate: { id: 'settings.app.buttonSearchForUpdate', @@ -87,6 +87,7 @@ export default class EditSettingsForm extends Component { updateIsReadyToInstall: PropTypes.bool.isRequired, isClearingAllCache: PropTypes.bool.isRequired, onClearAllCache: PropTypes.func.isRequired, + cacheSize: PropTypes.string.isRequired, }; static contextTypes = { @@ -115,6 +116,7 @@ export default class EditSettingsForm extends Component { updateIsReadyToInstall, isClearingAllCache, onClearAllCache, + cacheSize, } = this.props; const { intl } = this.context; @@ -127,23 +129,6 @@ export default class EditSettingsForm extends Component { updateButtonLabelMessage = messages.buttonSearchForUpdate; } - const clearAllCacheButton = isClearingAllCache ? ( -