logo

mastofe

My custom branche(s) on git.pleroma.social/pleroma/mastofe
commit: 488584bfc15ace3a097947f5190b73354aaa19e9
parent: 0717d9b3e6904a4dcd5d2dc9e680cc5b21c50e51
Author: Eugen Rochko <eugen@zeonfederated.com>
Date:   Sun,  8 Oct 2017 21:47:15 +0200

Track frequently used emojis in web UI (#5275)

* Track frequently used emojis in web UI

* Persist emoji usage, but debounce commits to the settings API

* Fix #5144 - Add tooltips to picker

* Display only 2 lines of frequently used emojis

Diffstat:

Mapp/javascript/mastodon/actions/compose.js3+++
Aapp/javascript/mastodon/actions/emojis.js14++++++++++++++
Mapp/javascript/mastodon/actions/settings.js18+++++++++++++-----
Mapp/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js10++++++++--
Mapp/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js27++++++++++++++++++++++++++-
Mapp/javascript/mastodon/reducers/settings.js27++++++++++++++++++++++-----
Mpackage.json2+-
Myarn.lock6+++---
8 files changed, 90 insertions(+), 17 deletions(-)

diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js @@ -1,6 +1,7 @@ import api from '../api'; import { throttle } from 'lodash'; import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; +import { useEmoji } from './emojis'; import { updateTimeline, @@ -305,6 +306,8 @@ export function selectComposeSuggestion(position, token, suggestion) { if (typeof suggestion === 'object' && suggestion.id) { completion = suggestion.native || suggestion.colons; startPosition = position - 1; + + dispatch(useEmoji(suggestion)); } else { completion = getState().getIn(['accounts', suggestion, 'acct']); startPosition = position; diff --git a/app/javascript/mastodon/actions/emojis.js b/app/javascript/mastodon/actions/emojis.js @@ -0,0 +1,14 @@ +import { saveSettings } from './settings'; + +export const EMOJI_USE = 'EMOJI_USE'; + +export function useEmoji(emoji) { + return dispatch => { + dispatch({ + type: EMOJI_USE, + emoji, + }); + + dispatch(saveSettings()); + }; +}; diff --git a/app/javascript/mastodon/actions/settings.js b/app/javascript/mastodon/actions/settings.js @@ -1,6 +1,8 @@ import axios from 'axios'; +import { debounce } from 'lodash'; export const SETTING_CHANGE = 'SETTING_CHANGE'; +export const SETTING_SAVE = 'SETTING_SAVE'; export function changeSetting(key, value) { return dispatch => { @@ -14,10 +16,16 @@ export function changeSetting(key, value) { }; }; +const debouncedSave = debounce((dispatch, getState) => { + if (getState().getIn(['settings', 'saved'])) { + return; + } + + const data = getState().get('settings').filter((_, key) => key !== 'saved').toJS(); + + axios.put('/api/web/settings', { data }).then(() => dispatch({ type: SETTING_SAVE })); +}, 5000, { trailing: true }); + export function saveSettings() { - return (_, getState) => { - axios.put('/api/web/settings', { - data: getState().get('settings').toJS(), - }); - }; + return (dispatch, getState) => debouncedSave(dispatch, getState); }; diff --git a/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js b/app/javascript/mastodon/features/compose/components/emoji_picker_dropdown.js @@ -146,6 +146,7 @@ class EmojiPickerMenu extends React.PureComponent { static propTypes = { custom_emojis: ImmutablePropTypes.list, + frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string), loading: PropTypes.bool, onClose: PropTypes.func.isRequired, onPick: PropTypes.func.isRequired, @@ -163,6 +164,7 @@ class EmojiPickerMenu extends React.PureComponent { style: {}, loading: true, placement: 'bottom', + frequentlyUsedEmojis: [], }; state = { @@ -233,7 +235,7 @@ class EmojiPickerMenu extends React.PureComponent { } render () { - const { loading, style, intl, custom_emojis, autoPlay, skinTone } = this.props; + const { loading, style, intl, custom_emojis, autoPlay, skinTone, frequentlyUsedEmojis } = this.props; if (loading) { return <div style={{ width: 299 }} />; @@ -256,9 +258,11 @@ class EmojiPickerMenu extends React.PureComponent { i18n={this.getI18n()} onClick={this.handleClick} include={categoriesSort} + recent={frequentlyUsedEmojis} skin={skinTone} showPreview={false} backgroundImageFn={backgroundImageFn} + emojiTooltip /> <ModifierPicker @@ -279,6 +283,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { static propTypes = { custom_emojis: ImmutablePropTypes.list, + frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string), autoPlay: PropTypes.bool, intl: PropTypes.object.isRequired, onPickEmoji: PropTypes.func.isRequired, @@ -341,7 +346,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { } render () { - const { intl, onPickEmoji, autoPlay, onSkinTone, skinTone } = this.props; + const { intl, onPickEmoji, autoPlay, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props; const title = intl.formatMessage(messages.emoji); const { active, loading } = this.state; @@ -364,6 +369,7 @@ export default class EmojiPickerDropdown extends React.PureComponent { autoPlay={autoPlay} onSkinTone={onSkinTone} skinTone={skinTone} + frequentlyUsedEmojis={frequentlyUsedEmojis} /> </Overlay> </div> diff --git a/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/mastodon/features/compose/containers/emoji_picker_dropdown_container.js @@ -1,17 +1,42 @@ import { connect } from 'react-redux'; import EmojiPickerDropdown from '../components/emoji_picker_dropdown'; import { changeSetting } from '../../../actions/settings'; +import { createSelector } from 'reselect'; +import { Map as ImmutableMap } from 'immutable'; +import { useEmoji } from '../../../actions/emojis'; + +const perLine = 8; +const lines = 2; + +const getFrequentlyUsedEmojis = createSelector([ + state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()), +], emojiCounters => emojiCounters + .keySeq() + .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b)) + .reverse() + .slice(0, perLine * lines) + .toArray() +); const mapStateToProps = state => ({ custom_emojis: state.get('custom_emojis'), autoPlay: state.getIn(['meta', 'auto_play_gif']), skinTone: state.getIn(['settings', 'skinTone']), + frequentlyUsedEmojis: getFrequentlyUsedEmojis(state), }); -const mapDispatchToProps = dispatch => ({ +const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({ onSkinTone: skinTone => { dispatch(changeSetting(['skinTone'], skinTone)); }, + + onPickEmoji: emoji => { + dispatch(useEmoji(emoji)); + + if (onPickEmoji) { + onPickEmoji(emoji); + } + }, }); export default connect(mapStateToProps, mapDispatchToProps)(EmojiPickerDropdown); diff --git a/app/javascript/mastodon/reducers/settings.js b/app/javascript/mastodon/reducers/settings.js @@ -1,10 +1,13 @@ -import { SETTING_CHANGE } from '../actions/settings'; +import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings'; import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from '../actions/columns'; import { STORE_HYDRATE } from '../actions/store'; +import { EMOJI_USE } from '../actions/emojis'; import { Map as ImmutableMap, fromJS } from 'immutable'; import uuid from '../uuid'; const initialState = ImmutableMap({ + saved: true, + onboarded: false, skinTone: 1, @@ -74,21 +77,35 @@ const moveColumn = (state, uuid, direction) => { newColumns = columns.splice(index, 1); newColumns = newColumns.splice(newIndex, 0, columns.get(index)); - return state.set('columns', newColumns); + return state + .set('columns', newColumns) + .set('saved', false); }; +const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false); + export default function settings(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: return hydrate(state, action.state.get('settings')); case SETTING_CHANGE: - return state.setIn(action.key, action.value); + return state + .setIn(action.key, action.value) + .set('saved', false); case COLUMN_ADD: - return state.update('columns', list => list.push(fromJS({ id: action.id, uuid: uuid(), params: action.params }))); + return state + .update('columns', list => list.push(fromJS({ id: action.id, uuid: uuid(), params: action.params }))) + .set('saved', false); case COLUMN_REMOVE: - return state.update('columns', list => list.filterNot(item => item.get('uuid') === action.uuid)); + return state + .update('columns', list => list.filterNot(item => item.get('uuid') === action.uuid)) + .set('saved', false); case COLUMN_MOVE: return moveColumn(state, action.uuid, action.direction); + case EMOJI_USE: + return updateFrequentEmojis(state, action.emoji); + case SETTING_SAVE: + return state.set('saved', true); default: return state; } diff --git a/package.json b/package.json @@ -45,7 +45,7 @@ "css-loader": "^0.28.4", "detect-passive-events": "^1.0.2", "dotenv": "^4.0.0", - "emoji-mart": "^2.1.1", + "emoji-mart": "Gargron/emoji-mart#build", "es6-symbol": "^3.1.1", "escape-html": "^1.0.3", "express": "^4.15.2", diff --git a/yarn.lock b/yarn.lock @@ -2191,9 +2191,9 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.0" -emoji-mart@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-2.1.1.tgz#4bce8ec9d9fd0d8adfd2517e7e296871c40762ac" +emoji-mart@Gargron/emoji-mart#build: + version "2.1.2" + resolved "https://codeload.github.com/Gargron/emoji-mart/tar.gz/c28a721169d95eb40031a4dae5a79fa8a12a66c7" emoji-regex@^6.1.0: version "6.4.3"