From 742da82515a54e968291c40f81d0670272c175d7 Mon Sep 17 00:00:00 2001 From: SirStendec Date: Mon, 28 Oct 2019 14:56:55 -0400 Subject: [PATCH] 4.15.2 * Added: Option to display all emotes in the same tab of the emote menu. (Closes #684) * Added: Button in the emote menu that opens the FFZ Control Center to `Chat > Emote Menu`. * Changed: Start using Twitch for all emote information rather than the FFZ socket cluster. * Fixed: When an emoji skin tone is set, use that skin tone for category icons if applicable. * Fixed: When reduced padding for the emote menu is enabled, reduce the padding around the navigation buttons. --- package.json | 2 +- src/modules/chat/emote_info.gql | 3 +- src/modules/chat/emote_set_info.gql | 15 + src/modules/chat/emotes.js | 282 +++++++++-------- src/modules/chat/tokenizers.jsx | 29 +- .../modules/chat/emote_menu.jsx | 286 +++++++++++------- src/sites/twitch-twilight/styles/chat.scss | 5 + src/utilities/constants.js | 21 +- 8 files changed, 396 insertions(+), 247 deletions(-) create mode 100644 src/modules/chat/emote_set_info.gql diff --git a/package.json b/package.json index 6d693068..4d4d1e1f 100755 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "frankerfacez", "author": "Dan Salvato LLC", - "version": "4.15.1", + "version": "4.15.2", "description": "FrankerFaceZ is a Twitch enhancement suite.", "license": "Apache-2.0", "scripts": { diff --git a/src/modules/chat/emote_info.gql b/src/modules/chat/emote_info.gql index d7877d1e..33b09a26 100644 --- a/src/modules/chat/emote_info.gql +++ b/src/modules/chat/emote_info.gql @@ -5,13 +5,12 @@ query FFZ_GetEmoteInfo($id: ID!) { text subscriptionProduct { id + state owner { id login displayName } - tier - url } } } \ No newline at end of file diff --git a/src/modules/chat/emote_set_info.gql b/src/modules/chat/emote_set_info.gql new file mode 100644 index 00000000..983abc08 --- /dev/null +++ b/src/modules/chat/emote_set_info.gql @@ -0,0 +1,15 @@ +query FFZ_GetEmoteSetInfo($id: ID!) { + emoteSet(id: $id) { + id + owner { + id + login + displayName + subscriptionProducts { + id + emoteSetID + state + } + } + } +} \ No newline at end of file diff --git a/src/modules/chat/emotes.js b/src/modules/chat/emotes.js index 6f906a02..f21de7c8 100644 --- a/src/modules/chat/emotes.js +++ b/src/modules/chat/emotes.js @@ -7,14 +7,13 @@ import Module from 'utilities/module'; import {ManagedStyle} from 'utilities/dom'; import {get, has, timeout, SourcedSet} from 'utilities/object'; -import {CLIENT_ID, NEW_API, API_SERVER, IS_OSX} from 'utilities/constants'; +import {NEW_API, API_SERVER, IS_OSX, EmoteTypes, TWITCH_GLOBAL_SETS, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS} from 'utilities/constants'; import GET_EMOTE from './emote_info.gql'; +import GET_EMOTE_SET from './emote_set_info.gql'; const MOD_KEY = IS_OSX ? 'metaKey' : 'ctrlKey'; -//const EXTRA_INVENTORY = [33563]; - const MODIFIERS = { 59847: { modifier_offset: '0 15px 15px 0', @@ -60,6 +59,8 @@ export default class Emotes extends Module { constructor(...args) { super(...args); + this.EmoteTypes = EmoteTypes; + this.inject('socket'); this.inject('settings'); this.inject('experiments'); @@ -126,9 +127,6 @@ export default class Emotes extends Module { } onEnable() { - // Just in case there's a weird load order going on. - // this.on('site:enabled', this.loadTwitchInventory); - this.style = new ManagedStyle('emotes'); if ( Object.keys(this.emote_sets).length ) { @@ -146,7 +144,6 @@ export default class Emotes extends Module { this.socket.on(':command:follow_sets', this.updateFollowSets, this); this.loadGlobalSets(); - //this.loadTwitchInventory(); } @@ -755,39 +752,6 @@ export default class Emotes extends Module { // Twitch Data Lookup // ======================================================================== - async loadTwitchInventory() { - const user = this.resolve('site').getUser(); - if ( ! user ) - return; - - let data; - try { - data = await fetch('https://api.twitch.tv/v5/inventory/emoticons', { - headers: { - 'Client-ID': CLIENT_ID, - 'Authorization': `OAuth ${user.authToken}` - } - }).then(r => { - if ( r.ok ) - return r.json(); - - throw r.status; - }); - - } catch(err) { - this.log.error('Error loading Twitch inventory.', err); - return; - } - - const sets = this.twitch_inventory_sets = new Set(EXTRA_INVENTORY); - for(const set in data.emoticon_sets) - if ( has(data.emoticon_sets, set) ) - sets.add(parseInt(set, 10)); - - this.log.info('Twitch Inventory Sets:', this.twitch_inventory_sets); - } - - setTwitchEmoteSet(emote_id, set_id) { if ( isNaN(emote_id) || ! isFinite(emote_id) ) return; @@ -802,127 +766,201 @@ export default class Emotes extends Module { this.__twitch_set_to_channel.set(set_id, channel); } - - getTwitchEmoteSet(emote_id, callback) { - const tes = this.__twitch_emote_to_set; + _getTwitchEmoteSet(emote_id) { + const tes = this.__twitch_emote_to_set, + tsc = this.__twitch_set_to_channel; if ( isNaN(emote_id) || ! isFinite(emote_id) ) - return null; + return Promise.resolve(null); - if ( tes.has(emote_id) ) - return tes.get(emote_id); + if ( tes.has(emote_id) ) { + const val = tes.get(emote_id); + if ( Array.isArray(val) ) + return new Promise(s => val.push(s)); + else + return Promise.resolve(val); + } - tes.set(emote_id, null); + const apollo = this.resolve('site.apollo'); + if ( ! apollo?.client ) + return Promise.resolve(null); + + return new Promise(s => { + const promises = [s]; + tes.set(emote_id, promises); - /*const apollo = this.resolve('site.apollo'); - if ( apollo?.client ) { timeout(apollo.client.query({ query: GET_EMOTE, variables: { id: `${emote_id}` } - }), 1000).then(result => { - const emote = result?.data?.emote; + }), 2000).then(data => { + const emote = data?.data?.emote; + let set_id = null; - if ( ! emote ) { - tes.delete(emote_id); - return; + if ( emote ) { + set_id = parseInt(emote.setID, 10); + + if ( set_id && ! tsc.has(set_id) ) { + const type = determineEmoteType(emote); + + tsc.set(set_id, { + id: set_id, + type, + owner: emote?.subscriptionProduct?.owner + }); + } } - const set_id = parseInt(emote.setID, 10), - channel = emote?.subscriptionProduct?.owner; - - this.__twitch_set_to_channel.set(set_id, { - s_id: set_id, - c_id: channel ? channel.id : null, - c_name: channel ? channel.login : null, - c_title: channel ? channel.displayName : null - }); - tes.set(emote_id, set_id); - if ( callback ) - callback(set_id); + for(const fn of promises) + fn(set_id); - }).catch(() => tes.delete(emote_id)); + }).catch(() => { + tes.set(emote_id, null); + for(const fn of promises) + fn(null); + }); + }); + } - return; - }*/ - - timeout(this.socket.call('get_emote', emote_id), 1000).then(data => { - const set_id = data['s_id']; - tes.set(emote_id, set_id); - this.__twitch_set_to_channel.set(set_id, data); - - if ( callback ) - callback(data['s_id']); - - }).catch(() => tes.delete(emote_id)); + getTwitchEmoteSet(emote_id, callback) { + const promise = this._getTwitchEmoteSet(emote_id); + if ( callback ) + promise.then(callback); + else + return promise; } - async awaitTwitchSetChannel(set_id, perform_lookup = true) { - const tes = this.__twitch_set_to_channel, - inv = this.twitch_inventory_sets; + _getTwitchSetChannel(set_id) { + const tsc = this.__twitch_set_to_channel; if ( isNaN(set_id) || ! isFinite(set_id) ) - return null; + return Promise.resolve(null); - if ( tes.has(set_id) ) - return tes.get(set_id); + if ( tsc.has(set_id) ) { + const val = tsc.get(set_id); + if ( Array.isArray(val) ) + return new Promise(s => val.push(s)); + else + return Promise.resolve(val); + } - if ( inv.has(set_id) ) - return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'} + const apollo = this.resolve('site.apollo'); + if ( ! apollo?.client ) + return Promise.resolve(null); - if ( ! perform_lookup ) - return null; + return new Promise(s => { + const promises = [s]; + tsc.set(set_id, promises); - tes.set(set_id, null); + timeout(apollo.client.query({ + query: GET_EMOTE_SET, + variables: { + id: `${set_id}` + } + }), 2000).then(data => { + const set = data?.data?.emoteSet; + let result = null; - try { - const data = await timeout(this.socket.call('get_emote_set', set_id), 1000); - tes.set(set_id, data); - return data; + if ( set ) { + result = { + id: set_id, + type: determineSetType(set), + owner: set.owner ? { + id: set.owner.id, + login: set.owner.login, + displayName: set.owner.displayName + } : null + }; + } - } catch(err) { - if ( err === 'No known Twitch emote set with that ID.' ) - return null; + tsc.set(set_id, result); + for(const fn of promises) + fn(result); - tes.delete(set_id); + }).catch(() => { + tsc.set(set_id, null); + for(const fn of promises) + fn(null); + }); + }); + } + + + getTwitchSetChannel(set_id, callback) { + const promise = this._getTwitchSetChannel(set_id); + if ( callback ) + promise.then(callback); + else + return promise; + } +} + + +function determineEmoteType(emote) { + const product = emote.subscriptionProduct; + if ( product ) { + if ( product.id == 12658 ) + return EmoteTypes.Prime; + else if ( product.id == 324 ) + return EmoteTypes.Turbo; + + // TODO: Care about Overwatch League + + const owner = product.owner; + if ( owner ) { + if ( owner.id == 139075904 || product.state === 'INACTIVE' ) + return EmoteTypes.LimitedTime; + + return EmoteTypes.Subscription; } } + if ( emote.setID == 300238151 ) + return EmoteTypes.ChannelPoints; - getTwitchSetChannel(set_id, callback, perform_lookup = true) { - const tes = this.__twitch_set_to_channel, - inv = this.twitch_inventory_sets; + return EmoteTypes.Global; +} - if ( isNaN(set_id) || ! isFinite(set_id) ) - return null; - if ( tes.has(set_id) ) - return tes.get(set_id); +function determineSetType(set) { + const id = parseInt(set.id, 10); - if ( inv.has(set_id) ) - return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'} + if ( TWITCH_GLOBAL_SETS.includes(id) ) + return EmoteTypes.Global; - if ( ! perform_lookup ) - return null; + if ( TWITCH_POINTS_SETS.includes(id) ) + return EmoteTypes.ChannelPoints; - tes.set(set_id, null); - timeout(this.socket.call('get_emote_set', set_id), 1000).then(data => { - tes.set(set_id, data); - if ( callback ) - callback(data); + if ( TWITCH_PRIME_SETS.includes(id) ) + return EmoteTypes.Prime; - }).catch(err => { - if ( err === 'No known Twitch emote set with that ID.' ) { - if ( callback ) - callback(null); + const owner = set.owner; + if ( owner ) { + if ( owner.id == 139075904 ) + return EmoteTypes.LimitedTime; - return; - } + let product; + if ( Array.isArray(owner.subscriptionProducts) ) + for(const prod of owner.subscriptionProducts) + if ( set.id == prod.emoteSetID ) { + product = prod; + break; + } - tes.delete(set_id) - }); + if ( product ) { + if ( product.id == 12658 ) + return EmoteTypes.Prime; + else if ( product.id == 324 ) + return EmoteTypes.Turbo; + else if ( product.state === 'INACTIVE' ) + return EmoteTypes.LimitedTime; + } + + return EmoteTypes.Subscription; } + + return EmoteTypes.Global; } \ No newline at end of file diff --git a/src/modules/chat/tokenizers.jsx b/src/modules/chat/tokenizers.jsx index 9f2f3302..5f4b13d8 100644 --- a/src/modules/chat/tokenizers.jsx +++ b/src/modules/chat/tokenizers.jsx @@ -7,7 +7,7 @@ import {sanitize, createElement} from 'utilities/dom'; import {has, split_chars} from 'utilities/object'; -import {TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; +import {TWITCH_EMOTE_BASE, EmoteTypes, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; const EMOTE_CLASS = 'chat-image chat-line__message--emote', @@ -1011,7 +1011,7 @@ export const AddonEmotes = { ); }, - tooltip(target, tip) { + async tooltip(target, tip) { const ds = target.dataset, provider = ds.provider, modifiers = ds.modifierInfo; @@ -1036,23 +1036,30 @@ export const AddonEmotes = { if ( provider === 'twitch' ) { emote_id = parseInt(ds.id, 10); - const set_id = this.emotes.getTwitchEmoteSet(emote_id, tip.rerender), - emote_set = set_id != null && this.emotes.getTwitchSetChannel(set_id, tip.rerender); + const set_id = hide_source ? null : await this.emotes.getTwitchEmoteSet(emote_id), + emote_set = set_id != null && await this.emotes.getTwitchSetChannel(set_id); - preview = `//static-cdn.jtvnw.net/emoticons/v1/${ds.id}/3.0?_=preview`; + preview = `${TWITCH_EMOTE_BASE}${ds.id}/3.0?_=preview`; fav_source = 'twitch'; if ( emote_set ) { - source = emote_set.c_name; - - if ( source === '--global--' || emote_id === 80393 ) + const type = emote_set.type; + if ( type === EmoteTypes.Global ) source = this.i18n.t('emote.global', 'Twitch Global'); - else if ( source === '--twitch-turbo--' || source === 'turbo' || source === '--turbo-faces--' || source === '--prime--' || source === '--prime-faces--' ) + else if ( type === EmoteTypes.Prime || type === EmoteTypes.Turbo ) source = this.i18n.t('emote.prime', 'Twitch Prime'); - else - source = this.i18n.t('tooltip.channel', 'Channel: {source}', {source}); + else if ( type === EmoteTypes.LimitedTime ) + source = this.i18n.t('emote.limited', 'Limited-Time Only Emote'); + + else if ( type === EmoteTypes.ChannelPoints ) + source = this.i18n.t('emote.points', 'Channel Points Emote'); + + else if ( type === EmoteTypes.Subscription && emote_set.owner?.login ) + source = this.i18n.t('tooltip.channel', 'Channel: {source}', { + source: emote_set.owner.displayName || emote_set.owner.login + }); } } else if ( provider === 'ffz' ) { diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index 9ae560ba..fe47c344 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -5,7 +5,7 @@ // ============================================================================ import {has, get, once, maybe_call, set_equals} from 'utilities/object'; -import {WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; +import {TWITCH_GLOBAL_SETS, EmoteTypes, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; import {ClickOutside} from 'utilities/dom'; import Twilight from 'site'; @@ -13,23 +13,6 @@ import Module from 'utilities/module'; import SUB_STATUS from './sub_status.gql'; -const GLOBAL_SETS = [ - 0, - 33, - 42 -]; - -const POINTS_SETS = [ - 300238151 -]; - -const PRIME_SETS = [ - 457, - 793, - 19151, - 19194 -]; - const TIERS = { 1000: 'Tier 1', 2000: 'Tier 2', @@ -202,6 +185,16 @@ export default class EmoteMenu extends Module { }); + this.settings.add('chat.emote-menu.combine-tabs', { + default: false, + ui: { + path: 'Chat > Emote Menu >> General', + title: 'Display all emotes on one tab.', + component: 'setting-check-box' + } + }); + + this.settings.add('chat.emote-menu.sort-emotes', { default: 0, ui: { @@ -263,6 +256,7 @@ export default class EmoteMenu extends Module { this.chat.context.on('changed:chat.emote-menu.show-heading', fup); this.chat.context.on('changed:chat.emote-menu.show-search', fup); this.chat.context.on('changed:chat.emote-menu.reduced-padding', fup); + this.chat.context.on('changed:chat.emote-menu.combine-tabs', fup); this.chat.context.on('changed:chat.emoji.style', this.updateEmojiVariables, this); @@ -545,7 +539,7 @@ export default class EmoteMenu extends Module { const data = this.props.data, filtered = this.props.filtered; - let show_heading = ! data.is_favorites && t.chat.context.get('chat.emote-menu.show-heading'); + 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 ) show_heading = ! filtered; else @@ -592,6 +586,10 @@ export default class EmoteMenu extends Module { } } + let source = data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source; + if ( source == null ) + source = 'FrankerFaceZ'; + return (
{show_heading ? ( {image} @@ -604,7 +602,7 @@ export default class EmoteMenu extends Module { />)}
- {(data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source) || 'FrankerFaceZ'} + {source} {filtered ? '' :
} ) : null} {collapsed || this.renderBody(show_heading)} @@ -958,11 +956,64 @@ export default class EmoteMenu extends Module { } clickTab(event) { + const tab = event.currentTarget.dataset.tab; + if ( t.chat.context.get('chat.emote-menu.combine-tabs') ) { + let sets; + switch(tab) { + case 'fav': + sets = this.state.filtered_fav_sets; + break; + case 'channel': + sets = this.state.filtered_channel_sets; + break; + case 'emoji': + sets = this.state.filtered_emoji_sets; + break; + case 'all': + default: + sets = this.state.filtered_all_sets; + break; + } + + const set = sets && sets[0], + el = set && this.ref?.querySelector?.(`section[data-key="${set.key}"]`); + + if ( el ) + el.scrollIntoView(); + + return; + } + this.setState({ - tab: event.currentTarget.dataset.tab + tab }); } + clickSettings(event) { // eslint-disable-line class-methods-use-this + const layout = t.resolve('site.layout'); + if ( (layout && layout.is_minimal) || (event && (event.ctrlKey || event.shiftKey)) ) { + const win = window.open( + 'https://twitch.tv/popout/frankerfacez/chat?ffz-settings', + '_blank', + 'resizable=yes,scrollbars=yes,width=850,height=600' + ); + + if ( win ) + win.focus(); + + } else { + const menu = t.resolve('main_menu'); + + if ( menu ) { + menu.requestPage('chat.emote_menu'); + if ( menu.showing ) + return; + } + + t.emit('site.menu_button:clicked'); + } + } + /*clickRefresh(event) { const target = event.currentTarget, tt = target && target._ffz_tooltip$0; @@ -1109,6 +1160,11 @@ export default class EmoteMenu extends Module { } } + const is_fav = emoji_favorites.includes(emoji.code), + toned = emoji.variants && emoji.variants[tone], + has_tone = toned && toned.has[style], + source = has_tone ? toned : emoji; + let cat = categories[emoji.category]; if ( ! cat ) { cat = categories[emoji.category] = []; @@ -1116,7 +1172,7 @@ export default class EmoteMenu extends Module { sets.push({ key: `emoji-${emoji.category}`, emoji: true, - image: t.emoji.getFullImage(emoji.image), + image: t.emoji.getFullImage(source.image), i18n: `emoji.category.${emoji.category.toSnakeCase()}`, title: emoji.category, source: 'Emoji', @@ -1125,31 +1181,26 @@ export default class EmoteMenu extends Module { }); } - const is_fav = emoji_favorites.includes(emoji.code), - toned = emoji.variants && emoji.variants[tone], - has_tone = toned && toned.has[style], - source = has_tone ? toned : emoji, + const em = { + provider: 'emoji', + emoji: true, + code: emoji.code, + name: source.raw, + variant: has_tone && tone, - em = { - provider: 'emoji', - emoji: true, - code: emoji.code, - name: source.raw, - variant: has_tone && tone, + search: emoji.names[0], - search: emoji.names[0], + height: 18, + width: 18, - height: 18, - width: 18, + x: source.sheet_x, + y: source.sheet_y, - x: source.sheet_x, - y: source.sheet_y, + favorite: is_fav, - favorite: is_fav, - - src: t.emoji.getFullImage(source.image), - srcSet: t.emoji.getFullImageSet(source.image) - }; + src: t.emoji.getFullImage(source.image), + srcSet: t.emoji.getFullImageSet(source.image) + }; cat.push(em); @@ -1161,6 +1212,12 @@ export default class EmoteMenu extends Module { state.fav_sets = [{ key: 'favorites', + + title: 'Favorites', + i18n: 'emote-menu.favorites', + icon: 'star', + source: '', + is_favorites: true, emotes: favorites }]; @@ -1232,7 +1289,7 @@ export default class EmoteMenu extends Module { const set_id = parseInt(emote_set.id, 10), owner = emote_set.owner, - is_points = owner?.login === 'channel_points', + is_points = TWITCH_POINTS_SETS.includes(set_id) || owner?.login === 'channel_points', chan = is_points ? null : owner, set_data = data[set_id]; @@ -1251,27 +1308,54 @@ export default class EmoteMenu extends Module { icon = 'twitch', title = chan && (chan.displayName || chan.login); - if ( title ) + if ( title ) { key = `twitch-${chan?.id}`; + t.emotes.setTwitchSetChannel(set_id, { + id: set_id, + type: EmoteTypes.Subscription, + owner: { + id: chan.id, + login: chan.login, + displayName: chan.displayName + } + }); - else if ( ! chan ) { - if ( is_points || POINTS_SETS.includes(set_id) ) { + } else if ( ! chan ) { + if ( is_points ) { title = t.i18n.t('emote-menu.points', 'Unlocked with Points'); key = 'twitch-points'; icon = 'channel-points'; sort_key = 45; - } else if ( GLOBAL_SETS.includes(set_id) ) { + /*t.emotes.setTwitchSetChannel(set_id, { + id: set_id, + type: EmoteTypes.ChannelPoints, + owner: null + });*/ + + } else if ( TWITCH_GLOBAL_SETS.includes(set_id) ) { title = t.i18n.t('emote-menu.global', 'Global Emotes'); key = 'twitch-global'; sort_key = 100; - } else if ( PRIME_SETS.includes(set_id) ) { + t.emotes.setTwitchSetChannel(set_id, { + id: set_id, + type: EmoteTypes.Global, + owner: null + }); + + } else if ( TWITCH_PRIME_SETS.includes(set_id) ) { title = t.i18n.t('emote_menu.prime', 'Prime'); key = 'twitch-prime'; icon = 'crown'; sort_key = 75; + t.emotes.setTwitchSetChannel(set_id, { + id: set_id, + type: EmoteTypes.Prime, + owner: null + }); + } else { title = t.i18n.t('emote-menu.misc', 'Miscellaneous'); key = 'twitch-misc'; @@ -1352,7 +1436,9 @@ export default class EmoteMenu extends Module { favorite: is_fav }; - t.emotes.setTwitchEmoteSet(id, set_id); + if ( ! is_points ) + t.emotes.setTwitchEmoteSet(id, set_id); + emotes.push(em); if ( is_fav && ! twitch_seen.has(id) ) @@ -1381,7 +1467,7 @@ export default class EmoteMenu extends Module { locks = {}, section = { sort_key: -10, - key: `twitch-${user.id}`, + key: `twitch-current-channel`, image: badge && badge.image1x, image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`, icon: 'twitch', @@ -1647,28 +1733,41 @@ export default class EmoteMenu extends Module { return null; const loading = this.state.loading || this.props.loading, - padding = t.chat.context.get('chat.emote-menu.reduced-padding'); + padding = t.chat.context.get('chat.emote-menu.reduced-padding'), + no_tabs = t.chat.context.get('chat.emote-menu.combine-tabs'); - let tab = this.state.tab || t.chat.context.get('chat.emote-menu.default-tab'), sets; - if ( (tab === 'channel' && ! this.state.has_channel_tab) || (tab === 'emoji' && ! this.state.has_emoji_tab) ) - tab = 'all'; + let tab, sets, is_emoji; - const is_emoji = tab === 'emoji'; + if ( no_tabs ) { + sets = [ + this.state.filtered_fav_sets, + this.state.filtered_channel_sets, + this.state.filtered_all_sets, + this.state.filtered_emoji_sets + ].flat(); - switch(tab) { - case 'fav': - sets = this.state.filtered_fav_sets; - break; - case 'channel': - sets = this.state.filtered_channel_sets; - break; - case 'emoji': - sets = this.state.filtered_emoji_sets; - break; - case 'all': - default: - sets = this.state.filtered_all_sets; - break; + } else { + tab = this.state.tab || t.chat.context.get('chat.emote-menu.default-tab'); + if ( (tab === 'channel' && ! this.state.has_channel_tab) || (tab === 'emoji' && ! this.state.has_emoji_tab) ) + tab = 'all'; + + is_emoji = tab === 'emoji'; + + switch(tab) { + case 'fav': + sets = this.state.filtered_fav_sets; + break; + case 'channel': + sets = this.state.filtered_channel_sets; + break; + case 'emoji': + sets = this.state.filtered_emoji_sets; + break; + case 'all': + default: + sets = this.state.filtered_all_sets; + break; + } } return (
{loading && this.renderLoading()} - {!loading && sets && sets.map(data => createElement( + {!loading && sets && sets.map(data => data && createElement( data.emoji ? t.EmojiSection : t.MenuSection, { key: data.key, @@ -1717,7 +1816,7 @@ export default class EmoteMenu extends Module { onChange={this.handleFilterChange} onKeyDown={this.handleKeyDown} /> - {is_emoji &&
} - {/*
- {!loading && (
+
+
-
)*/} +
@@ -1861,39 +1960,6 @@ export default class EmoteMenu extends Module { type: product.type, gift: node.gift?.isGift }; - - /*const owner = product.owner || {}, - badges = owner.broadcastBadges; - - let image, image_set; - if ( badges ) - for(const badge of badges) - if ( badge.setID === 'subscriber' && badge.version === '0' ) { - image = badge.imageURL; - - if ( image.endsWith('/1') ) { - const base = image.slice(0, -2); - image_set = `${base}/1 1x, ${base}/2 2x, ${base}/3 4x`; - } - - break; - } - - out[set_id] = { - ends: maybe_date(node.endsAt), - renews: maybe_date(node.renewsAt), - prime: node.purchasedWithPrime, - - set_id: parseInt(set_id, 10), - type: product.type, - image, - image_set, - user: { - id: owner.id, - login: owner.login, - display_name: owner.displayName - } - }*/ } this._data_sets = sets; diff --git a/src/sites/twitch-twilight/styles/chat.scss b/src/sites/twitch-twilight/styles/chat.scss index c2bdc701..66307b6e 100644 --- a/src/sites/twitch-twilight/styles/chat.scss +++ b/src/sites/twitch-twilight/styles/chat.scss @@ -288,6 +288,11 @@ } &.reduced-padding { + .emote-picker-tab-item button > div { + padding: 0.25rem 0.5rem !important; + font-size: 1.6rem !important; + } + .tw-pd-1 { padding: 0.5rem !important; } diff --git a/src/utilities/constants.js b/src/utilities/constants.js index f3a1a0b0..40f4784e 100644 --- a/src/utilities/constants.js +++ b/src/utilities/constants.js @@ -1,5 +1,8 @@ 'use strict'; +import {make_enum} from 'utilities/object'; + + export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev'); export const SERVER = DEBUG ? '//localhost:8000' : 'https://cdn.frankerfacez.com'; @@ -87,4 +90,20 @@ export const IS_WIN = navigator.platform ? navigator.platform.indexOf('Win') !== export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && navigator.userAgent.indexOf('Edge/') === -1; export const IS_FIREFOX = navigator.userAgent.indexOf('Firefox/') !== -1; -export const WEBKIT_CSS = IS_WEBKIT ? '-webkit-' : ''; \ No newline at end of file +export const WEBKIT_CSS = IS_WEBKIT ? '-webkit-' : ''; + + +export const TWITCH_GLOBAL_SETS = [0, 33, 42]; +export const TWITCH_POINTS_SETS = [300238151]; +export const TWITCH_PRIME_SETS = [457, 793, 19151, 19194]; + +export const EmoteTypes = make_enum( + 'Unknown', + 'Prime', + 'Turbo', + 'LimitedTime', + 'ChannelPoints', + 'Unavailable', + 'Subscription', + 'Global' +); \ No newline at end of file