From 8c9a3aa8a40854060165207caaadeabfba0a3019 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Tue, 23 Jun 2020 17:17:00 -0400 Subject: [PATCH] 4.20.0 * Added: Emote Visibility Control for Emote Menu. You can now hide emotes you don't want from your emote menu. You still have them, and they'll still appear in chat if someone else uses them, but it helps keep the clutter down in your menu. (Closes #811) * Added: Setting to toggle mute for the player when middle-clicking it. (Closes #812) * Added: Setting to toggle the bold style applied to chat mentions. (Closes #816) * Fixed: No background color being applied to Highlight My Message chat messages when using the new Twitch layout. Now, the default Twitch purple will be used when FFZ hasn't yet extracted the color for the current channel. Extracting the channel color is still broken at this time. (Closes #821) * Fixed: The player volume resetting to 100% when changing channels. (Closes #820) * Fixed: Chat appearing incorrectly when a custom width smaller than 340 pixels is set. (Closes #819) --- package.json | 2 +- src/modules/chat/emotes.js | 46 ++ src/modules/chat/index.js | 9 + src/sites/twitch-twilight/modules/channel.js | 2 + .../modules/chat/emote_menu.jsx | 165 +++++- .../twitch-twilight/modules/chat/index.js | 2 + .../twitch-twilight/modules/chat/input.jsx | 5 +- .../styles/chat-mention-no-bold.scss | 3 + .../modules/css_tweaks/styles/chat-width.scss | 4 + src/sites/twitch-twilight/modules/player.jsx | 61 ++- src/sites/twitch-twilight/styles/chat.scss | 11 +- src/utilities/data/user-color.gql | 6 + src/utilities/data/user-fetch.gql | 1 + src/utilities/twitch-data.js | 491 +++++++++--------- styles/tooltips.scss | 7 + 15 files changed, 525 insertions(+), 290 deletions(-) create mode 100644 src/sites/twitch-twilight/modules/css_tweaks/styles/chat-mention-no-bold.scss create mode 100644 src/utilities/data/user-color.gql diff --git a/package.json b/package.json index 3a4d52d0..bf9810c4 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.19.13", + "version": "4.20.0", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { diff --git a/src/modules/chat/emotes.js b/src/modules/chat/emotes.js index 50ad2068..7e18cbaa 100644 --- a/src/modules/chat/emotes.js +++ b/src/modules/chat/emotes.js @@ -194,6 +194,52 @@ export default class Emotes extends Module { } + // ======================================================================== + // Hidden Checking + // ======================================================================== + + toggleHidden(source, id, value = null) { + const key = `hidden-emotes.${source}`, + p = this.settings.provider, + hidden = p.get(key, []), + + idx = hidden.indexOf(id); + + if ( value === null ) + value = idx === -1; + + if ( value && idx === -1 ) + hidden.push(id); + else if ( ! value && idx !== -1 ) + hidden.splice(idx, 1); + else + return; + + if ( hidden.length ) + p.set(key, hidden); + else + p.delete(key); + + this.emit(':change-hidden', source, id, value); + } + + isHidden(source, id) { + return this.getHidden(source).includes(id); + } + + getHidden(source) { + return this.settings.provider.get(`hidden-emotes.${source}`, []); + } + + setHidden(source, list) { + const key = `hidden-emotes.${source}`; + if ( ! Array.isArray(list) || ! list.length ) + this.settings.provider.delete(key); + else + this.settings.provider.set(key, list); + } + + // ======================================================================== // Favorite Checking // ======================================================================== diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index b6893ade..33a50730 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -628,6 +628,15 @@ export default class Chat extends Module { } }); + this.settings.add('chat.filtering.bold-mentions', { + default: true, + ui: { + component: 'setting-check-box', + path: 'Chat > Filtering >> Appearance', + title: 'Display mentions in chat with a bold font.' + } + }); + this.settings.add('chat.filtering.mention-color', { default: '', ui: { diff --git a/src/sites/twitch-twilight/modules/channel.js b/src/sites/twitch-twilight/modules/channel.js index 6f5e97ef..b7e9d192 100644 --- a/src/sites/twitch-twilight/modules/channel.js +++ b/src/sites/twitch-twilight/modules/channel.js @@ -97,6 +97,8 @@ export default class Channel extends Module { onEnable() { + this.updateChannelColor(); + this.ChannelPage.on('mount', this.wrapChannelPage, this); this.RaidController.on('mount', this.wrapRaidController, this); this.RaidController.on('update', this.noAutoRaids, this); diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index 3c8c5e63..6f7fe564 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -253,6 +253,7 @@ export default class EmoteMenu extends Module { this.on('chat.emotes:update-user-sets', this.maybeUpdate, this); this.on('chat.emotes:update-room-sets', this.maybeUpdate, this); this.on('chat.emotes:change-favorite', this.maybeUpdate, this); + this.on('chat.emotes:change-hidden', this.maybeUpdate, this); this.on('chat.emoji:populated', this.maybeUpdate, this); this.chat.context.on('changed:chat.emote-menu.enabled', () => @@ -491,9 +492,12 @@ export default class EmoteMenu extends Module { this.props.startObserving(this.ref, this); } - const collapsed = storage.get('emote-menu.collapsed') || []; + const collapsed = storage.get('emote-menu.collapsed'), + hidden = storage.get('emote-menu.hidden-sets'); + this.state = { - collapsed: props.data && collapsed.includes(props.data.key), + hidden: hidden && props.data && hidden.includes(props.data.hide_key || props.data.key), + collapsed: collapsed && props.data && collapsed.includes(props.data.key), intersecting: window.IntersectionObserver ? false : true } @@ -517,6 +521,29 @@ export default class EmoteMenu extends Module { } clickEmote(event) { + if ( this.props.visibility_control ) { + const ds = event.currentTarget.dataset; + let source, id = ds.id; + + if ( ds.provider === 'twitch' ) + source = 'twitch'; + else if ( ds.provider === 'ffz' ) { + const emote_set = t.emotes.emote_sets[ds.set], + emote = emote_set && emote_set.emotes[id]; + + if ( ! emote ) + return; + + source = emote_set.source || 'ffz'; + id = emote.id; + + } else + return; + + t.emotes.toggleHidden(source, id); + return; + } + if ( t.emotes.handleClick(event) ) return; @@ -524,6 +551,26 @@ export default class EmoteMenu extends Module { } clickHeading() { + if ( this.props.visibility_control ) { + const hidden = storage.get('emote-menu.hidden-sets') || [], + key = this.props.data.hide_key || this.props.data.key, + idx = hidden.indexOf(key); + + if ( key === 'twitch-current-channel' ) + return; + + if ( idx === -1 ) { + hidden.push(key); + this.setState({hidden: true}); + } else { + hidden.splice(idx, 1); + this.setState({hidden: false}); + } + + storage.set('emote-menu.hidden-sets', hidden); + return; + } + if ( this.props.filtered ) return; @@ -555,7 +602,8 @@ export default class EmoteMenu extends Module { render() { const data = this.props.data, - filtered = this.props.filtered; + filtered = this.props.filtered, + visibility = this.props.visibility_control; let show_heading = ! (data.is_favorites && ! t.chat.context.get('chat.emote-menu.combine-tabs')) && t.chat.context.get('chat.emote-menu.show-heading'); if ( show_heading === 2 ) @@ -563,7 +611,11 @@ export default class EmoteMenu extends Module { else show_heading = !! show_heading; - const collapsed = show_heading && ! filtered && this.state.collapsed; + if ( visibility ) + show_heading = true; + + const hidden = visibility ? this.state.hidden : false, + collapsed = visibility ? hidden : (show_heading && ! filtered && this.state.collapsed); if ( ! data ) return null; @@ -613,15 +665,20 @@ export default class EmoteMenu extends Module { {image}
{(data.i18n ? t.i18n.t(data.i18n, data.title) : data.title) || t.i18n.t('emote-menu.unknown', 'Unknown Source')} - {calendar && ()}
- {source} - {filtered ? '' :
} + {visibility ? + (hidden ? + t.i18n.t('emote-menu.visibility.hidden', 'Hidden') : + t.i18n.t('emote-menu.visibility.visible', 'Visible') ) + : source + } + {(visibility ? false : filtered) ? '' :
} ) : null} {collapsed || this.renderBody(show_heading)} ) @@ -659,7 +716,7 @@ export default class EmoteMenu extends Module { return (
{emotes} - {!filtered && this.renderSellout()} + {! this.props.visibility_control && !filtered && this.renderSellout()}
) } @@ -667,9 +724,12 @@ export default class EmoteMenu extends Module { if ( ! this.state.intersecting ) return ; + const visibility = this.props.visibility_control, + hidden = visibility && emote.hidden; + return () } @@ -876,6 +937,7 @@ export default class EmoteMenu extends Module { //this.clickRefresh = this.clickRefresh.bind(this); this.handleFilterChange = this.handleFilterChange.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); + this.toggleVisibilityControl = this.toggleVisibilityControl.bind(this); } createObserver() { @@ -1079,6 +1141,10 @@ export default class EmoteMenu extends Module { }); }*/ + toggleVisibilityControl() { + this.setState(this.filterState(this.state.filter, this.state, ! this.state.visibility_control)); + } + handleFilterChange(event) { this.setState(this.filterState(event.target.value, this.state)); } @@ -1115,30 +1181,43 @@ export default class EmoteMenu extends Module { return true; } - filterState(input, old_state) { + filterState(input, old_state, visibility_control) { const state = Object.assign({}, old_state); + if ( visibility_control != null ) + state.visibility_control = visibility_control; + else + visibility_control = state.visibility_control; + state.filter = input; state.filtered = input && input.length > 0 && input !== ':' || false; - state.filtered_channel_sets = this.filterSets(input, state.channel_sets); - state.filtered_all_sets = this.filterSets(input, state.all_sets); - state.filtered_fav_sets = this.filterSets(input, state.fav_sets); - state.filtered_emoji_sets = this.filterSets(input, state.emoji_sets); + state.filtered_channel_sets = this.filterSets(input, state.channel_sets, visibility_control); + state.filtered_all_sets = this.filterSets(input, state.all_sets, visibility_control); + state.filtered_fav_sets = this.filterSets(input, state.fav_sets, visibility_control); + state.filtered_emoji_sets = this.filterSets(input, state.emoji_sets, visibility_control); return state; } - filterSets(input, sets) { + filterSets(input, sets, visibility_control) { const out = []; if ( ! sets || ! sets.length ) return out; - const filtering = input && input.length > 0 && input !== ':'; + const filtering = input && input.length > 0 && input !== ':', + hidden_sets = storage.get('emote-menu.hidden-sets', []); for(const emote_set of sets) { - const filtered = emote_set.filtered_emotes = emote_set.emotes.filter(emote => - ! filtering || (! emote.locked && this.doesEmoteMatch(input, emote))); + if ( ! visibility_control && hidden_sets.includes(emote_set.key) ) + continue; + + const filtered = emote_set.filtered_emotes = emote_set.emotes.filter(emote => { + if ( ! visibility_control && emote.hidden ) + return false; + + return ! filtering || (! emote.locked && this.doesEmoteMatch(input, emote)) + }); if ( filtered.length ) out.push(emote_set); @@ -1206,6 +1285,7 @@ export default class EmoteMenu extends Module { image: t.emoji.getFullImage(source.image), i18n: `emoji.category.${emoji.category.toSnakeCase()}`, title: emoji.category, + src: 'emoji', source: 'Emoji', source_i18n: 'emote-menu.emoji', emotes: cat @@ -1331,6 +1411,7 @@ export default class EmoteMenu extends Module { const emote_sets = props.emote_data && props.emote_data.emoteSets, emote_map = props.emote_data && props.emote_data.emoteMap, twitch_favorites = t.emotes.getFavorites('twitch'), + twitch_hidden = t.emotes.getHidden('twitch'), twitch_seen = new Set, bits_unlocked = [], @@ -1507,6 +1588,7 @@ export default class EmoteMenu extends Module { overridden: overridden ? mapped.id : null, misc: ! chan, bits: is_bits, + hidden: twitch_hidden.includes(id), favorite: is_fav }; @@ -1546,6 +1628,7 @@ export default class EmoteMenu extends Module { section = { sort_key: -10, key: `twitch-current-channel`, + hide_key: `twitch-${user.id}`, image: badge && badge.image1x, image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`, icon: 'twitch', @@ -1609,7 +1692,8 @@ export default class EmoteMenu extends Module { locked: locked && ! seen, src: `${base}/1.0`, srcSet: `${base}/1.0 1x, ${base}/2.0 2x`, - favorite: is_fav + favorite: is_fav, + hidden: twitch_hidden.includes(id) }; emotes.push(em); @@ -1663,7 +1747,8 @@ export default class EmoteMenu extends Module { srcSet: `${base}/1.0 1x, ${base}/2.0 2x`, bits: true, bit_value: summary.threshold, - favorite: is_fav + favorite: is_fav, + hidden: twitch_hidden.includes(id) }; emotes.push(em); @@ -1744,6 +1829,7 @@ export default class EmoteMenu extends Module { const fav_key = emote_set.source || 'ffz', known_favs = t.emotes.getFavorites(fav_key), + known_hidden = t.emotes.getHidden(fav_key), seen_favs = seen_favorites[fav_key] = seen_favorites[fav_key] || new Set; const key = `${emote_set.merge_source || fav_key}-${emote_set.merge_id || emote_set.id}`, @@ -1802,6 +1888,7 @@ export default class EmoteMenu extends Module { srcSet: emote.srcSet, name: emote.name, favorite: is_fav, + hidden: known_hidden.includes(emote.id), height: emote.height, width: emote.width }; @@ -1921,6 +2008,8 @@ export default class EmoteMenu extends Module { } } + const visibility = this.state.visibility_control; + return (
{loading && this.renderLoading()} - {!loading && sets && sets.map(data => data && createElement( + {!loading && sets && sets.map(data => data && (! visibility || (! data.emoji && ! data.is_favorites)) && createElement( data.emoji ? t.EmojiSection : t.MenuSection, { key: data.key, data, filtered: this.state.filtered, + visibility_control: visibility, onClickToken: this.props.onClickToken, startObserving: this.startObserving, stopObserving: this.stopObserving @@ -1967,7 +2057,26 @@ export default class EmoteMenu extends Module { onChange={this.handleFilterChange} onKeyDown={this.handleKeyDown} /> - {(no_tabs || is_emoji) && this.state.has_emoji_tab && + +
+ {this.state.visibility_control ? + t.i18n.t('emote-menu.toggle-hide.on', 'Exit Emote Visibility Control') : + t.i18n.t('emote-menu.toggle-hide.off', 'Emote Visibility Control') + } +
+ {t.i18n.t('emote-menu.toggle-hide.info', 'Emote Visibility Control allows you to hide emotes from your emote menu, either individually or by set. With Emote Visibility Control enabled, just click an emote to hide or unhide it. Please note that you will still see the emotes in chat if someone uses them, but they won\'t appear in your emote menu.')} +
+
+
} + {(no_tabs || is_emoji) && ! visibility && this.state.has_emoji_tab &&
)}
-
+ {! visibility &&
-
+
} {this.state.has_channel_tab &&
- {this.state.has_emoji_tab &&
+ {! visibility && this.state.has_emoji_tab &&