1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-09-15 17:46:55 +00:00

Add emote menu options to control heading visibility, control search visibility, reduce padding, and change the default tab. Also set add a way to get FineWrappers for classes we define without having to search the tree.

This commit is contained in:
SirStendec 2018-04-07 18:01:28 -04:00
parent 2eb92a7075
commit 411b2d6390
3 changed files with 172 additions and 31 deletions

View file

@ -77,17 +77,69 @@ export default class EmoteMenu extends Module {
}, },
ui: { 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.', title: 'Replace the emote menu icon with the FFZ icon for that classic feel.',
component: 'setting-check-box' 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( this.EmoteMenu = this.fine.define(
'chat-emote-menu', 'chat-emote-menu',
n => n.subscriptionProductHasEmotes, n => n.subscriptionProductHasEmotes,
Twilight.CHAT_ROUTES 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() { 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-user-sets', this.maybeUpdate, this);
this.on('chat.emotes:update-room-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.chat.context.on('changed:chat.emote-menu.icon', val =>
this.css_tweaks.toggle('emote-menu', val)); this.css_tweaks.toggle('emote-menu', val));
@ -174,7 +238,7 @@ export default class EmoteMenu extends Module {
data-provider={data.provider} data-provider={data.provider}
data-id={data.id} data-id={data.id}
data-set={data.set_id} data-set={data.set_id}
data-no-source="true" data-no-source={this.props.source}
data-name={data.name} data-name={data.name}
aria-label={data.name} aria-label={data.name}
data-locked={data.locked} 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 { this.MenuSection = class FFZMenuSection extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
@ -238,16 +305,24 @@ export default class EmoteMenu extends Module {
render() { render() {
const data = this.props.data, 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 ) if ( ! data )
return null; return null;
let image; let image;
if ( data.image ) if ( data.image )
image = (<img src={data.image} />); image = (<img class="ffz--menu-badge" src={data.image} srcSet={data.image_set} />);
else else
image = (<figure class={`ffz-i-${data.icon || 'zreknarf'}`} />); image = (<figure class={`ffz--menu-badge ffz-i-${data.icon || 'zreknarf'}`} />);
let calendar; let calendar;
@ -275,8 +350,8 @@ export default class EmoteMenu extends Module {
} }
} }
return (<section data-key={data.key} class={this.props.filtered ? 'filtered' : ''}> return (<section data-key={data.key} class={filtered ? 'filtered' : ''}>
<heading class="tw-pd-1 tw-border-b tw-flex" onClick={this.clickHeading}> {show_heading ? (<heading class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onClick={this.clickHeading}>
{image} {image}
<div class="tw-pd-l-05"> <div class="tw-pd-l-05">
{data.title || t.i18n.t('emote-menu.unknown', 'Unknown Source')} {data.title || t.i18n.t('emote-menu.unknown', 'Unknown Source')}
@ -289,12 +364,12 @@ export default class EmoteMenu extends Module {
<div class="tw-flex-grow-1" /> <div class="tw-flex-grow-1" />
{data.source || 'FrankerFaceZ'} {data.source || 'FrankerFaceZ'}
<figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} /> <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />
</heading> </heading>) : null}
{collapsed || this.renderBody()} {collapsed || this.renderBody(show_heading)}
</section>) </section>)
} }
renderBody() { renderBody(show_sources) {
const data = this.props.data, const data = this.props.data,
filtered = this.props.filtered, filtered = this.props.filtered,
lock = data.locks && data.locks[this.state.unlocked], lock = data.locks && data.locks[this.state.unlocked],
@ -302,6 +377,7 @@ export default class EmoteMenu extends Module {
key={emote.id} key={emote.id}
onClickEmote={this.props.onClickEmote} onClickEmote={this.props.onClickEmote}
data={emote} data={emote}
source={show_sources}
locked={emote.locked && (! lock || ! lock.emotes.has(emote.id))} locked={emote.locked && (! lock || ! lock.emotes.has(emote.id))}
all_locked={data.all_locked} all_locked={data.all_locked}
lock={data.locks && data.locks[emote.set_id]} 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 { this.MenuComponent = class FFZEmoteMenuComponent extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {page: null} this.state = {tab: null}
this.componentWillReceiveProps(props); this.componentWillReceiveProps(props);
this.clickTab = this.clickTab.bind(this); this.clickTab = this.clickTab.bind(this);
this.clickRefresh = this.clickRefresh.bind(this); this.clickRefresh = this.clickRefresh.bind(this);
this.handleFilterChange = this.handleFilterChange.bind(this); this.handleFilterChange = this.handleFilterChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this); this.handleKeyDown = this.handleKeyDown.bind(this);
}
componentDidMount() {
window.ffz_menu = this; window.ffz_menu = this;
} }
componentWillUnmount() {
if ( window.ffz_menu === this )
window.ffz_menu = null;
}
clickTab(event) { clickTab(event) {
this.setState({ 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), const set_id = parseInt(emote_set.id, 10),
set_data = data[set_id] || {}, set_data = data[set_id] || {},
more_data = t.emotes.getTwitchSetChannel(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); set_ids.add(set_id);
@ -585,6 +672,7 @@ export default class EmoteMenu extends Module {
if ( chan && ! chan.bad && section.bad ) { if ( chan && ! chan.bad && section.bad ) {
section.title = title; section.title = title;
section.image = image; section.image = image;
section.image_set = image_set;
section.icon = icon; section.icon = icon;
section.sort_key = sort_key; section.sort_key = sort_key;
section.bad = false; section.bad = false;
@ -597,6 +685,7 @@ export default class EmoteMenu extends Module {
bad: chan ? chan.bad : true, bad: chan ? chan.bad : true,
key, key,
image, image,
image_set,
icon, icon,
title, title,
source: t.i18n.t('emote-menu.twitch', 'Twitch'), source: t.i18n.t('emote-menu.twitch', 'Twitch'),
@ -640,6 +729,7 @@ export default class EmoteMenu extends Module {
sort_key: -10, sort_key: -10,
key: `twitch-${user.id}`, key: `twitch-${user.id}`,
image: badge && badge.image1x, image: badge && badge.image1x,
image_set: badge && `${badge.image1x} 1x, ${badge.image2x} 2x, ${badge.image4x} 4x`,
icon: 'twitch', icon: 'twitch',
title: t.i18n.t('emote-menu.sub-set', 'Subscriber Emotes'), title: t.i18n.t('emote-menu.sub-set', 'Subscriber Emotes'),
source: t.i18n.t('emote-menu.twitch', 'Twitch'), source: t.i18n.t('emote-menu.twitch', 'Twitch'),
@ -726,7 +816,7 @@ export default class EmoteMenu extends Module {
channel.sort(sort_sets); channel.sort(sort_sets);
all.sort(sort_sets); all.sort(sort_sets);
state.has_channel_page = channel.length > 0; state.has_channel_tab = channel.length > 0;
return state; return state;
} }
@ -795,7 +885,10 @@ export default class EmoteMenu extends Module {
return (<div class="tw-align-center tw-pd-1"> return (<div class="tw-align-center tw-pd-1">
<div class="tw-mg-b-1"> <div class="tw-mg-b-1">
<div class="tw-mg-5"> <div class="tw-mg-5">
<img src="//cdn.frankerfacez.com/emoticon/26608/2" /> <img
src="//cdn.frankerfacez.com/emoticon/26608/2"
srcSet="//cdn.frankerfacez.com/emoticon/26608/2 1x, //cdn.frankerfacez.com/emoticon/26608/4 2x"
/>
</div> </div>
{t.i18n.t('emote-menu.error', 'There was an error rendering this menu.')} {t.i18n.t('emote-menu.error', 'There was an error rendering this menu.')}
</div> </div>
@ -810,7 +903,10 @@ export default class EmoteMenu extends Module {
renderEmpty() { // eslint-disable-line class-methods-use-this renderEmpty() { // eslint-disable-line class-methods-use-this
return (<div class="tw-align-center tw-pd-1"> return (<div class="tw-align-center tw-pd-1">
<div class="tw-mg-5"> <div class="tw-mg-5">
<img src="//cdn.frankerfacez.com/emoticon/26608/2" /> <img
src="//cdn.frankerfacez.com/emoticon/26608/2"
srcSet="//cdn.frankerfacez.com/emoticon/26608/2 1x, //cdn.frankerfacez.com/emoticon/26608/4 2x"
/>
</div> </div>
{this.state.filtered ? {this.state.filtered ?
t.i18n.t('emote-menu.empty-search', 'There are no matching emotes.') : 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 ) if ( ! this.props.visible )
return null; 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; let tab = this.state.tab || t.chat.context.get('chat.emote-menu.default-tab'), sets;
if ( ! page ) if ( tab === 'channel' && ! this.state.has_channel_tab )
page = this.state.has_channel_page ? 'channel' : 'all'; tab = 'all';
switch(page) { switch(tab) {
case 'channel': case 'channel':
sets = this.state.filtered_channel_sets; sets = this.state.filtered_channel_sets;
break; break;
@ -846,7 +943,7 @@ export default class EmoteMenu extends Module {
} }
return (<div return (<div
class="tw-balloon tw-balloon--md tw-balloon--up tw-balloon--right tw-block tw-absolute ffz--emote-picker" class={`tw-balloon tw-balloon--md tw-balloon--up tw-balloon--right tw-block tw-absolute ffz--emote-picker${padding ? ' reduced-padding' : ''}`}
data-a-target="emote-picker" data-a-target="emote-picker"
> >
<div class="tw-border tw-elevation-1 tw-border-radius-small tw-c-background"> <div class="tw-border tw-elevation-1 tw-border-radius-small tw-c-background">
@ -869,7 +966,7 @@ export default class EmoteMenu extends Module {
</div> </div>
</div> </div>
<div class="emote-picker__controls-container tw-relative"> <div class="emote-picker__controls-container tw-relative">
<div class="tw-border-t tw-pd-1"> {t.chat.context.get('chat.emote-menu.show-search') && (<div class="tw-border-t tw-pd-1">
<div class="tw-relative"> <div class="tw-relative">
<input <input
type="text" type="text"
@ -883,23 +980,23 @@ export default class EmoteMenu extends Module {
autoCorrect="off" autoCorrect="off"
/> />
</div> </div>
</div> </div>)}
<div class="emote-picker__tabs-container tw-flex tw-border-t tw-c-background"> <div class="emote-picker__tabs-container tw-flex tw-border-t tw-c-background">
{null && (<div class="ffz-tooltip emote-picker__tab tw-pd-x-1" data-tooltip-type="html" data-title="Favorites"> {null && (<div class="ffz-tooltip emote-picker__tab tw-pd-x-1" data-tooltip-type="html" data-title="Favorites">
<figure class="ffz-i-star" /> <figure class="ffz-i-star" />
</div>)} </div>)}
{this.state.has_channel_page && <div {this.state.has_channel_tab && <div
class={`emote-picker__tab tw-pd-x-1${page === 'channel' ? ' emote-picker__tab--active' : ''}`} class={`emote-picker__tab tw-pd-x-1${tab === 'channel' ? ' emote-picker__tab--active' : ''}`}
id="emote-picker__channel" id="emote-picker__channel"
data-page="channel" data-tab="channel"
onClick={this.clickTab} onClick={this.clickTab}
> >
{t.i18n.t('emote-menu.channel', 'Channel')} {t.i18n.t('emote-menu.channel', 'Channel')}
</div>} </div>}
<div <div
class={`emote-picker__tab tw-pd-x-1${page === 'all' ? ' emote-picker__tab--active' : ''}`} class={`emote-picker__tab tw-pd-x-1${tab === 'all' ? ' emote-picker__tab--active' : ''}`}
id="emote-picker__all" id="emote-picker__all"
data-page="all" data-tab="all"
onClick={this.clickTab} onClick={this.clickTab}
> >
{t.i18n.t('emote-menu.all', 'All')} {t.i18n.t('emote-menu.all', 'All')}
@ -919,6 +1016,8 @@ export default class EmoteMenu extends Module {
</div>); </div>);
} }
} }
this.fine.wrap('ffz-emote-menu', this.MenuComponent);
} }
@ -964,11 +1063,17 @@ export default class EmoteMenu extends Module {
const owner = product.owner || {}, const owner = product.owner || {},
badges = owner.broadcastBadges; badges = owner.broadcastBadges;
let image; let image, image_set;
if ( badges ) if ( badges )
for(const badge of badges) for(const badge of badges)
if ( badge.setID === 'subscriber' && badge.version === '0' ) { if ( badge.setID === 'subscriber' && badge.version === '0' ) {
image = badge.imageURL; 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; break;
} }
@ -980,6 +1085,7 @@ export default class EmoteMenu extends Module {
set_id: parseInt(set_id, 10), set_id: parseInt(set_id, 10),
type: product.type, type: product.type,
image, image,
image_set,
user: { user: {
id: owner.id, id: owner.id,
login: owner.login, login: owner.login,

View file

@ -45,6 +45,10 @@
animation: ffz-rotateplane 1.2s infinite linear; animation: ffz-rotateplane 1.2s infinite linear;
} }
.ffz--menu-badge {
width: 1.8rem; height: 1.8rem;
}
.ffz--expiry-info { .ffz--expiry-info {
opacity: 0.5; opacity: 0.5;
} }
@ -63,6 +67,10 @@
@media only screen and (max-height: 750px) { @media only screen and (max-height: 750px) {
.emote-picker__tab-content { .emote-picker__tab-content {
.twilight-root & {
max-height: calc(100vh - 31rem);
}
max-height: calc(100vh - 26rem); max-height: calc(100vh - 26rem);
} }
} }
@ -108,4 +116,10 @@
} }
} }
&.reduced-padding {
.tw-pd-1 {
padding: 0.5rem !important;
}
}
} }

View file

@ -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) { _checkWaiters(nodes) {
if ( ! this._live_waiting ) if ( ! this._live_waiting )
return; return;