diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index d4ee703f..6252c97a 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -77,17 +77,69 @@ export default class EmoteMenu extends Module { }, ui: { - path: 'Chat > Emote Menu >> General', + path: 'Chat > Emote Menu >> Appearance', title: 'Replace the emote menu icon with the FFZ icon for that classic feel.', component: 'setting-check-box' } - }) + }); + + this.settings.add('chat.emote-menu.show-heading', { + default: 1, + ui: { + path: 'Chat > Emote Menu >> Appearance', + title: 'Show Headers', + component: 'setting-select-box', + data: [ + {value: 0, title: 'Never'}, + {value: 1, title: 'Always'}, + {value: 2, title: 'When Not Searching'} + ] + } + }); + + this.settings.add('chat.emote-menu.show-search', { + default: true, + ui: { + path: 'Chat > Emote Menu >> Appearance', + title: 'Show the search box.', + component: 'setting-check-box' + } + }); + + this.settings.add('chat.emote-menu.reduced-padding', { + default: false, + ui: { + path: 'Chat > Emote Menu >> Appearance', + title: 'Use reduced padding.', + component: 'setting-check-box' + } + }); + + + this.settings.add('chat.emote-menu.default-tab', { + default: 'channel', + ui: { + path: 'Chat > Emote Menu >> General', + title: 'Default Tab', + component: 'setting-select-box', + data: [ + {value: 'channel', title: 'Channel'}, + {value: 'all', title: 'All'} + ] + } + }); + this.EmoteMenu = this.fine.define( 'chat-emote-menu', n => n.subscriptionProductHasEmotes, Twilight.CHAT_ROUTES ) + + + this.MenuWrapper = this.fine.wrap('ffz-emote-menu'); + this.MenuSection = this.fine.wrap('ffz-menu-section'); + this.MenuEmote = this.fine.wrap('ffz-menu-emote'); } onEnable() { @@ -96,6 +148,18 @@ 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.chat.context.on('changed:chat.emote-menu.enabled', () => + this.EmoteMenu.forceUpdate()); + + this.chat.context.on('changed:chat.emote-menu.show-heading', () => + this.MenuWrapper.forceUpdate()); + + this.chat.context.on('changed:chat.emote-menu.show-search', () => + this.MenuWrapper.forceUpdate()); + + this.chat.context.on('changed:chat.emote-menu.reduced-padding', () => + this.MenuWrapper.forceUpdate()); + this.chat.context.on('changed:chat.emote-menu.icon', val => this.css_tweaks.toggle('emote-menu', val)); @@ -174,7 +238,7 @@ export default class EmoteMenu extends Module { data-provider={data.provider} data-id={data.id} data-set={data.set_id} - data-no-source="true" + data-no-source={this.props.source} data-name={data.name} aria-label={data.name} data-locked={data.locked} @@ -194,6 +258,9 @@ export default class EmoteMenu extends Module { } } + this.fine.wrap('ffz-menu-emote', this.MenuEmote); + + this.MenuSection = class FFZMenuSection extends React.Component { constructor(props) { super(props); @@ -238,16 +305,24 @@ export default class EmoteMenu extends Module { render() { const data = this.props.data, - collapsed = ! this.props.filtered && this.state.collapsed; + filtered = this.props.filtered; + + let show_heading = t.chat.context.get('chat.emote-menu.show-heading'); + if ( show_heading === 2 ) + show_heading = ! filtered; + else + show_heading = !! show_heading; + + const collapsed = show_heading && ! filtered && this.state.collapsed; if ( ! data ) return null; let image; if ( data.image ) - image = (); + image = (); else - image = (
); + image = (
); let calendar; @@ -275,8 +350,8 @@ export default class EmoteMenu extends Module { } } - return (
- + return (
+ {show_heading ? ( {image}
{data.title || t.i18n.t('emote-menu.unknown', 'Unknown Source')} @@ -289,12 +364,12 @@ export default class EmoteMenu extends Module {
{data.source || 'FrankerFaceZ'}
- - {collapsed || this.renderBody()} + ) : null} + {collapsed || this.renderBody(show_heading)}
) } - renderBody() { + renderBody(show_sources) { const data = this.props.data, filtered = this.props.filtered, lock = data.locks && data.locks[this.state.unlocked], @@ -302,6 +377,7 @@ export default class EmoteMenu extends Module { key={emote.id} onClickEmote={this.props.onClickEmote} data={emote} + source={show_sources} locked={emote.locked && (! lock || ! lock.emotes.has(emote.id))} all_locked={data.all_locked} lock={data.locks && data.locks[emote.set_id]} @@ -345,24 +421,34 @@ export default class EmoteMenu extends Module { } } + this.fine.wrap('ffz-menu-section', this.MenuSection); + + this.MenuComponent = class FFZEmoteMenuComponent extends React.Component { constructor(props) { super(props); - this.state = {page: null} + this.state = {tab: null} this.componentWillReceiveProps(props); this.clickTab = this.clickTab.bind(this); this.clickRefresh = this.clickRefresh.bind(this); this.handleFilterChange = this.handleFilterChange.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this); + } + componentDidMount() { window.ffz_menu = this; } + componentWillUnmount() { + if ( window.ffz_menu === this ) + window.ffz_menu = null; + } + clickTab(event) { this.setState({ - page: event.target.dataset.page + tab: event.target.dataset.tab }); } @@ -528,7 +614,8 @@ export default class EmoteMenu extends Module { const set_id = parseInt(emote_set.id, 10), set_data = data[set_id] || {}, more_data = t.emotes.getTwitchSetChannel(set_id), - image = set_data.image; + image = set_data.image, + image_set = set_data.image_set; set_ids.add(set_id); @@ -585,6 +672,7 @@ export default class EmoteMenu extends Module { if ( chan && ! chan.bad && section.bad ) { section.title = title; section.image = image; + section.image_set = image_set; section.icon = icon; section.sort_key = sort_key; section.bad = false; @@ -597,6 +685,7 @@ export default class EmoteMenu extends Module { bad: chan ? chan.bad : true, key, image, + image_set, icon, title, source: t.i18n.t('emote-menu.twitch', 'Twitch'), @@ -640,6 +729,7 @@ export default class EmoteMenu extends Module { sort_key: -10, key: `twitch-${user.id}`, image: badge && badge.image1x, + image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`, icon: 'twitch', title: t.i18n.t('emote-menu.sub-set', 'Subscriber Emotes'), source: t.i18n.t('emote-menu.twitch', 'Twitch'), @@ -726,7 +816,7 @@ export default class EmoteMenu extends Module { channel.sort(sort_sets); all.sort(sort_sets); - state.has_channel_page = channel.length > 0; + state.has_channel_tab = channel.length > 0; return state; } @@ -795,7 +885,10 @@ export default class EmoteMenu extends Module { return (
- +
{t.i18n.t('emote-menu.error', 'There was an error rendering this menu.')}
@@ -810,7 +903,10 @@ export default class EmoteMenu extends Module { renderEmpty() { // eslint-disable-line class-methods-use-this return (
- +
{this.state.filtered ? t.i18n.t('emote-menu.empty-search', 'There are no matching emotes.') : @@ -829,13 +925,14 @@ export default class EmoteMenu extends Module { if ( ! this.props.visible ) return null; - const loading = this.state.loading || this.props.loading; + const loading = this.state.loading || this.props.loading, + padding = t.chat.context.get('chat.emote-menu.reduced-padding'); - let page = this.state.page, sets; - if ( ! page ) - page = this.state.has_channel_page ? 'channel' : 'all'; + let tab = this.state.tab || t.chat.context.get('chat.emote-menu.default-tab'), sets; + if ( tab === 'channel' && ! this.state.has_channel_tab ) + tab = 'all'; - switch(page) { + switch(tab) { case 'channel': sets = this.state.filtered_channel_sets; break; @@ -846,7 +943,7 @@ export default class EmoteMenu extends Module { } return (
@@ -869,7 +966,7 @@ export default class EmoteMenu extends Module {
-
+ {t.chat.context.get('chat.emote-menu.show-search') && (
-
+
)}
{null && (
)} - {this.state.has_channel_page &&
{t.i18n.t('emote-menu.channel', 'Channel')}
}
{t.i18n.t('emote-menu.all', 'All')} @@ -919,6 +1016,8 @@ export default class EmoteMenu extends Module {
); } } + + this.fine.wrap('ffz-emote-menu', this.MenuComponent); } @@ -964,11 +1063,17 @@ export default class EmoteMenu extends Module { const owner = product.owner || {}, badges = owner.broadcastBadges; - let image; + 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}/4 4x`; + } + break; } @@ -980,6 +1085,7 @@ export default class EmoteMenu extends Module { set_id: parseInt(set_id, 10), type: product.type, image, + image_set, user: { id: owner.id, login: owner.login, diff --git a/src/sites/twitch-twilight/styles/chat.scss b/src/sites/twitch-twilight/styles/chat.scss index c4887177..729ab916 100644 --- a/src/sites/twitch-twilight/styles/chat.scss +++ b/src/sites/twitch-twilight/styles/chat.scss @@ -45,6 +45,10 @@ animation: ffz-rotateplane 1.2s infinite linear; } + .ffz--menu-badge { + width: 1.8rem; height: 1.8rem; + } + .ffz--expiry-info { opacity: 0.5; } @@ -63,6 +67,10 @@ @media only screen and (max-height: 750px) { .emote-picker__tab-content { + .twilight-root & { + max-height: calc(100vh - 31rem); + } + max-height: calc(100vh - 26rem); } } @@ -108,4 +116,10 @@ } } + + &.reduced-padding { + .tw-pd-1 { + padding: 0.5rem !important; + } + } } \ No newline at end of file diff --git a/src/utilities/compat/fine.js b/src/utilities/compat/fine.js index 0fa3f409..4726dbf5 100644 --- a/src/utilities/compat/fine.js +++ b/src/utilities/compat/fine.js @@ -299,6 +299,27 @@ export default class Fine extends Module { } + wrap(key, cls) { + let wrapper; + if ( this._wrappers.has(key) ) + wrapper = this._wrappers.get(key); + else { + wrapper = new FineWrapper(key, null, undefined, this); + this._wrappers.set(key, wrapper); + } + + if ( cls ) { + if ( wrapper._class || wrapper.criteria ) + throw new Error('tried setting a class on an already initialized FineWrapper'); + + wrapper._set(cls, new Set); + this._known_classes.set(cls, wrapper); + } + + return wrapper; + } + + _checkWaiters(nodes) { if ( ! this._live_waiting ) return;