mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-08 23:30:53 +00:00
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)
This commit is contained in:
parent
1c311c89bf
commit
8c9a3aa8a4
15 changed files with 525 additions and 290 deletions
|
@ -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": {
|
||||
|
|
|
@ -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
|
||||
// ========================================================================
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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}
|
||||
<div class="tw-pd-l-05">
|
||||
{(data.i18n ? t.i18n.t(data.i18n, data.title) : data.title) || t.i18n.t('emote-menu.unknown', 'Unknown Source')}
|
||||
{calendar && (<span
|
||||
{! visibility && calendar && (<span
|
||||
class={`tw-mg-x-05 ffz--expiry-info ffz-tooltip ffz-i-${calendar.icon}`}
|
||||
data-tooltip-type="html"
|
||||
data-title={calendar.message}
|
||||
/>)}
|
||||
</div>
|
||||
<div class="tw-flex-grow-1" />
|
||||
{source}
|
||||
{filtered ? '' : <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />}
|
||||
{visibility ?
|
||||
(hidden ?
|
||||
t.i18n.t('emote-menu.visibility.hidden', 'Hidden') :
|
||||
t.i18n.t('emote-menu.visibility.visible', 'Visible') )
|
||||
: source
|
||||
}
|
||||
{(visibility ? false : filtered) ? '' : <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />}
|
||||
</heading>) : null}
|
||||
{collapsed || this.renderBody(show_heading)}
|
||||
</section>)
|
||||
|
@ -659,7 +716,7 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
return (<div class="tw-pd-1 tw-border-b tw-c-background-alt tw-align-center">
|
||||
{emotes}
|
||||
{!filtered && this.renderSellout()}
|
||||
{! this.props.visibility_control && !filtered && this.renderSellout()}
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
@ -667,9 +724,12 @@ export default class EmoteMenu extends Module {
|
|||
if ( ! this.state.intersecting )
|
||||
return <span key={emote.id} class="emote-picker__placeholder" style={{width: `${emote.width||28}px`, height: `${emote.height||28}px`}} />;
|
||||
|
||||
const visibility = this.props.visibility_control,
|
||||
hidden = visibility && emote.hidden;
|
||||
|
||||
return (<button
|
||||
key={emote.id}
|
||||
class={`ffz-tooltip emote-picker__emote-link${locked ? ' locked' : ''}`}
|
||||
class={`ffz-tooltip emote-picker__emote-link${!visibility && locked ? ' locked' : ''}${hidden ? ' hidden' : ''}`}
|
||||
data-tooltip-type="emote"
|
||||
data-provider={emote.provider}
|
||||
data-id={emote.id}
|
||||
|
@ -681,7 +741,7 @@ export default class EmoteMenu extends Module {
|
|||
aria-label={emote.name}
|
||||
data-locked={emote.locked}
|
||||
data-sellout={sellout}
|
||||
onClick={!emote.locked && this.clickEmote}
|
||||
onClick={(this.props.visibility_control || !emote.locked) && this.clickEmote}
|
||||
>
|
||||
<figure class="emote-picker__emote-figure">
|
||||
<img
|
||||
|
@ -693,8 +753,9 @@ export default class EmoteMenu extends Module {
|
|||
width={emote.width ? `${emote.width}px` : null}
|
||||
/>
|
||||
</figure>
|
||||
{emote.favorite && <figure class="ffz--favorite ffz-i-star" />}
|
||||
{locked && <figure class="ffz-i-lock" />}
|
||||
{! visibility && emote.favorite && <figure class="ffz--favorite ffz-i-star" />}
|
||||
{! visibility && locked && <figure class="ffz-i-lock" />}
|
||||
{hidden && <figure class="ffz-i-eye-off" />}
|
||||
</button>)
|
||||
}
|
||||
|
||||
|
@ -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 (<div
|
||||
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"
|
||||
|
@ -1934,12 +2023,13 @@ export default class EmoteMenu extends Module {
|
|||
<div ref={this.saveScrollRef} class="simplebar-scroll-content">
|
||||
<div class="simplebar-content">
|
||||
{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 && <t.EmojiTonePicker
|
||||
{(no_tabs || ! is_emoji) && <div class="tw-relative tw-tooltip-wrapper tw-mg-l-1">
|
||||
<button
|
||||
class={`tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon--primary tw-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative${this.state.visibility_control ? ' tw-core-button--primary' : ' tw-button-icon'}`}
|
||||
onClick={this.toggleVisibilityControl}
|
||||
>
|
||||
<span class="tw-button-icon__icon tw-mg-x-05">
|
||||
<figure class={this.state.visibility_control ? 'ffz-i-eye-off' : 'ffz-i-eye'} />
|
||||
</span>
|
||||
</button>
|
||||
<div class="tw-tooltip tw-tooltip--up tw-tooltip--align-right">
|
||||
{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')
|
||||
}
|
||||
<div class="tw-mg-t-1 ffz--tooltip-explain">
|
||||
{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.')}
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
{(no_tabs || is_emoji) && ! visibility && this.state.has_emoji_tab && <t.EmojiTonePicker
|
||||
tone={this.state.tone}
|
||||
choices={this.state.tone_emoji}
|
||||
pickTone={this.pickTone}
|
||||
|
@ -1975,7 +2084,7 @@ export default class EmoteMenu extends Module {
|
|||
</div>
|
||||
</div>)}
|
||||
<div class="emote-picker__tab-nav-container tw-flex tw-border-t tw-c-background-alt">
|
||||
<div class={`emote-picker-tab-item${tab === 'fav' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
{! visibility && <div class={`emote-picker-tab-item${tab === 'fav' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive${tab === 'fav' ? ' tw-interactable--selected' : ''}`}
|
||||
id="emote-picker__fav"
|
||||
|
@ -1988,7 +2097,7 @@ export default class EmoteMenu extends Module {
|
|||
<figure class="ffz-i-star" />
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>}
|
||||
{this.state.has_channel_tab && <div class={`emote-picker-tab-item${tab === 'channel' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive${tab === 'channel' ? ' tw-interactable--selected' : ''}`}
|
||||
|
@ -2017,7 +2126,7 @@ export default class EmoteMenu extends Module {
|
|||
</div>
|
||||
</button>
|
||||
</div>
|
||||
{this.state.has_emoji_tab && <div class={`emote-picker-tab-item${tab === 'emoji' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
{! visibility && this.state.has_emoji_tab && <div class={`emote-picker-tab-item${tab === 'emoji' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive${tab === 'emoji' ? ' tw-interactable--selected' : ''}`}
|
||||
id="emote-picker__emoji"
|
||||
|
|
|
@ -677,6 +677,7 @@ export default class ChatHook extends Module {
|
|||
this.chat.context.on('changed:chat.filtering.display-deleted', this.updateChatLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.display-mod-action', this.updateChatLines, this);
|
||||
this.chat.context.on('changed:chat.filtering.clickable-mentions', val => this.css_tweaks.toggle('clickable-mentions', val));
|
||||
this.chat.context.on('changed:chat.filtering.bold-mentions', val => this.css_tweaks.toggle('chat-mention-no-bold', ! val));
|
||||
this.chat.context.on('changed:chat.pin-resubs', val => {
|
||||
if ( val ) {
|
||||
this.updateInlineCallouts();
|
||||
|
@ -713,6 +714,7 @@ export default class ChatHook extends Module {
|
|||
this.css_tweaks.toggle('chat-deleted-fade', val < 2);
|
||||
|
||||
this.css_tweaks.toggle('clickable-mentions', this.chat.context.get('chat.filtering.clickable-mentions'));
|
||||
this.css_tweaks.toggle('chat-mention-no-bold', ! this.chat.context.get('chat.filtering.bold-mentions'));
|
||||
|
||||
this.chat.context.on('changed:chat.hide-community-highlights', val => this.css_tweaks.toggleHide('community-highlights', val));
|
||||
|
||||
|
|
|
@ -482,12 +482,13 @@ export default class Input extends Module {
|
|||
startingResults = [],
|
||||
otherResults = [],
|
||||
favorites = this.emotes.getFavorites('twitch'),
|
||||
hidden = this.emotes.getHidden('twitch'),
|
||||
search = input.startsWith(':') ? input.slice(1) : input;
|
||||
|
||||
for (const set of hydratedEmotes) {
|
||||
if (set && Array.isArray(set.emotes)) {
|
||||
for (const emote of set.emotes) {
|
||||
if (inst.doesEmoteMatchTerm(emote, search)) {
|
||||
if (inst.doesEmoteMatchTerm(emote, search) && ! hidden.includes(emote.id)) {
|
||||
const favorite = favorites.includes(emote.id);
|
||||
const element = {
|
||||
current: input,
|
||||
|
@ -590,7 +591,7 @@ export default class Input extends Module {
|
|||
for(const set of sets) {
|
||||
if ( set && set.emotes )
|
||||
for(const emote of Object.values(set.emotes))
|
||||
if ( inst.doesEmoteMatchTerm(emote, search) && !added_emotes.has(emote.name) ) {
|
||||
if ( inst.doesEmoteMatchTerm(emote, search) && !added_emotes.has(emote.name) && ! this.emotes.isHidden(set.source || 'ffz', emote.id) ) {
|
||||
const favorite = this.emotes.isFavorite(set.source || 'ffz', emote.id);
|
||||
results.push({
|
||||
current: input,
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
strong.chat-line__message-mention {
|
||||
font-weight: 100 !important;
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
.chat-shell__expanded {
|
||||
min-width: unset !important;
|
||||
}
|
||||
|
||||
body .whispers--theatre-mode.whispers--right-column-expanded-beside {
|
||||
right: var(--ffz-chat-width);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import Module from 'utilities/module';
|
||||
import {createElement, on, off} from 'utilities/dom';
|
||||
import {debounce} from 'utilities/object';
|
||||
|
||||
export const PLAYER_ROUTES = [
|
||||
'front-page', 'user', 'video', 'user-video', 'user-clip', 'user-videos',
|
||||
|
@ -231,6 +232,15 @@ export default class Player extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('player.mute-click', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Player > General >> Volume',
|
||||
title: 'Mute or unmute the player by middle-clicking.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('player.volume-scroll', {
|
||||
default: false,
|
||||
ui: {
|
||||
|
@ -559,6 +569,9 @@ export default class Player extends Module {
|
|||
|
||||
this._ffz_installed = true;
|
||||
|
||||
if ( ! this._ffzUpdateVolume )
|
||||
this._ffzUpdateVolume = debounce(this.ffzUpdateVolume.bind(this));
|
||||
|
||||
if ( ! this._ffzUpdateState )
|
||||
this._ffzUpdateState = this.ffzUpdateState.bind(this);
|
||||
|
||||
|
@ -607,6 +620,16 @@ export default class Player extends Module {
|
|||
this.ffzStopAutoplay();
|
||||
}
|
||||
|
||||
cls.prototype.ffzUpdateVolume = function() {
|
||||
const player = this.props.mediaPlayerInstance,
|
||||
video = player?.mediaSinkManager?.video || player?.core?.mediaSinkManager?.video;
|
||||
if ( video ) {
|
||||
const volume = video.volume;
|
||||
if ( ! player.isMuted() && ! video.muted && player.getVolume() !== volume )
|
||||
player.setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
cls.prototype.ffzUninstall = function() {
|
||||
if ( this._ffz_state_raf )
|
||||
cancelAnimationFrame(this._ffz_state_raf);
|
||||
|
@ -698,7 +721,12 @@ export default class Player extends Module {
|
|||
this._ffz_listeners = true;
|
||||
if ( ! this._ffz_scroll_handler )
|
||||
this._ffz_scroll_handler = this.ffzScrollHandler.bind(this);
|
||||
|
||||
if ( ! this._ffz_click_handler )
|
||||
this._ffz_click_handler = this.ffzClickHandler.bind(this);
|
||||
|
||||
on(cont, 'wheel', this._ffz_scroll_handler);
|
||||
on(cont, 'mousedown', this._ffz_click_handler);
|
||||
}
|
||||
|
||||
cls.prototype.ffzRemoveListeners = function() {
|
||||
|
@ -711,9 +739,27 @@ export default class Player extends Module {
|
|||
this._ffz_scroll_handler = null;
|
||||
}
|
||||
|
||||
if ( this._ffz_click_handler ) {
|
||||
off(cont, 'mousedown', this._ffz_click_handler);
|
||||
this._ffz_click_handler = null;
|
||||
}
|
||||
|
||||
this._ffz_listeners = false;
|
||||
}
|
||||
|
||||
cls.prototype.ffzClickHandler = function(event) {
|
||||
if ( ! t.settings.get('player.mute-click') || ! event || event.button !== 1 )
|
||||
return;
|
||||
|
||||
const player = this.props?.mediaPlayerInstance;
|
||||
if ( ! player?.isMuted )
|
||||
return;
|
||||
|
||||
player.setMuted(! player.isMuted());
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
cls.prototype.ffzScrollHandler = function(event) {
|
||||
if ( ! t.settings.get('player.volume-scroll') )
|
||||
return;
|
||||
|
@ -956,10 +1002,11 @@ export default class Player extends Module {
|
|||
this.addCompressorButton(inst, false);
|
||||
|
||||
const player = inst?.props?.mediaPlayerInstance;
|
||||
if ( player && ! this.settings.get('player.allow-catchup') ) {
|
||||
if ( player.setLiveSpeedUpRate )
|
||||
if ( player && ! this.settings.get('player.allow-catchup') && player.setLiveSpeedUpRate )
|
||||
player.setLiveSpeedUpRate(1);
|
||||
}
|
||||
|
||||
if ( inst._ffzUpdateVolume )
|
||||
inst._ffzUpdateVolume();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1003,7 +1050,7 @@ export default class Player extends Module {
|
|||
<div class="tw-tooltip tw-tooltip--align-left tw-tooltip--up" role="tooltip">
|
||||
<div>
|
||||
{tip = (<div class="ffz--p-tip" />)}
|
||||
{extra = (<div class="ffz--p-extra tw-pd-t-05" />)}
|
||||
{extra = (<div class="ffz--p-extra tw-pd-t-05 ffz--tooltip-explain" />)}
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
|
@ -1416,7 +1463,7 @@ export default class Player extends Module {
|
|||
const video = player.mediaSinkManager?.video || player.core?.mediaSinkManager?.video;
|
||||
if ( video?._ffz_compressor && player.attachHTMLVideoElement ) {
|
||||
const new_vid = createElement('video'),
|
||||
vol = player.getVolume(),
|
||||
vol = video?.volume ?? player.getVolume(),
|
||||
muted = player.isMuted();
|
||||
new_vid.volume = muted ? 0 : vol;
|
||||
new_vid.playsInline = true;
|
||||
|
|
|
@ -305,14 +305,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.locked {
|
||||
cursor: not-allowed;
|
||||
|
||||
&.locked,
|
||||
&.hidden {
|
||||
img {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.ffz-i-lock {
|
||||
.ffz-i-lock,
|
||||
.ffz-i-eye-off {
|
||||
position: absolute;
|
||||
bottom: 0; right: 0;
|
||||
border-radius: .2rem;
|
||||
|
@ -320,7 +320,10 @@
|
|||
background-color: rgba(0,0,0,0.5);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&.locked {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
src/utilities/data/user-color.gql
Normal file
6
src/utilities/data/user-color.gql
Normal file
|
@ -0,0 +1,6 @@
|
|||
query FFZ_UserColor($id: ID, $login: String) {
|
||||
user(id: $id, login: $login) {
|
||||
id
|
||||
primaryColorHex
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ query FFZ_FetchUser($id: ID, $login: String) {
|
|||
login
|
||||
displayName
|
||||
profileImageURL(width: 50)
|
||||
primaryColorHex
|
||||
roles {
|
||||
isAffiliate
|
||||
isPartner
|
||||
|
|
|
@ -52,7 +52,6 @@ const ALGOLIA_LANGUAGES = {
|
|||
*
|
||||
* console.log(getAlgoliaLanguage('en'));
|
||||
*/
|
||||
|
||||
function getAlgoliaLanguage(locale) {
|
||||
if ( ! locale )
|
||||
return ALGOLIA_LANGUAGES.en;
|
||||
|
@ -65,12 +64,12 @@ function getAlgoliaLanguage(locale) {
|
|||
return ALGOLIA_LANGUAGES[locale] || ALGOLIA_LANGUAGES.en;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TwitchData is a container for getting different types of Twitch data
|
||||
* @class TwitchData
|
||||
* @extends Module
|
||||
*/
|
||||
|
||||
export default class TwitchData extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
@ -176,7 +175,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getMatchingCategories("siege"));
|
||||
*/
|
||||
|
||||
async getMatchingCategories(query) {
|
||||
const data = await this.queryApollo(
|
||||
await import(/* webpackChunkName: 'queries' */ './data/search-category.gql'),
|
||||
|
@ -204,7 +202,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getCategory(null, 'Just Chatting'));
|
||||
*/
|
||||
|
||||
async getCategory(id, name) {
|
||||
const data = await this.queryApollo(
|
||||
await import(/* webpackChunkName: 'queries' */ './data/category-fetch.gql'),
|
||||
|
@ -232,7 +229,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getMatchingUsers("ninja"));
|
||||
*/
|
||||
|
||||
async getMatchingUsers(query) {
|
||||
const data = await this.queryApollo(
|
||||
await import(/* webpackChunkName: 'queries' */ './data/search-user.gql'),
|
||||
|
@ -260,7 +256,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getUser(19571641, null));
|
||||
*/
|
||||
|
||||
async getUser(id, login) {
|
||||
const data = await this.queryApollo(
|
||||
await import(/* webpackChunkName: 'queries' */ './data/user-fetch.gql'),
|
||||
|
@ -284,7 +279,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getUserSelf(null, "ninja"));
|
||||
*/
|
||||
|
||||
async getUserSelf(id, login) {
|
||||
const data = await this.queryApollo(
|
||||
await import(/* webpackChunkName: 'queries' */ './data/user-self.gql'),
|
||||
|
@ -308,7 +302,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getLastBroadcast(19571641, null));
|
||||
*/
|
||||
|
||||
async getLastBroadcast(id, login) {
|
||||
const data = await this.queryApollo(
|
||||
await import(/* webpackChunkName: 'queries' */ './data/last-broadcast.gql'),
|
||||
|
@ -336,7 +329,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getBroadcastID(null, "ninja"));
|
||||
*/
|
||||
|
||||
async getBroadcastID(id, login) {
|
||||
const data = await this.queryApollo({
|
||||
query: await import(/* webpackChunkName: 'queries' */ './data/broadcast-id.gql'),
|
||||
|
@ -350,6 +342,19 @@ export default class TwitchData extends Module {
|
|||
}
|
||||
|
||||
|
||||
async getChannelColor(id, login) {
|
||||
const data = await this.queryApollo({
|
||||
query: await import(/* webpackChunkName: 'queries' */ './data/user-color.gql'),
|
||||
variables: {
|
||||
id,
|
||||
login
|
||||
}
|
||||
});
|
||||
|
||||
return get('data.user.primaryColorHex', data);
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Polls
|
||||
// ========================================================================
|
||||
|
@ -367,7 +372,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getPoll(1337));
|
||||
*/
|
||||
|
||||
async getPoll(poll_id) {
|
||||
const data = await this.queryApollo({
|
||||
query: await import(/* webpackChunkName: 'queries' */ './data/poll-get.gql'),
|
||||
|
@ -399,7 +403,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.createPoll(19571641, "Pick an option:", ["One", "Two", "Three"], {bits: 10, duration: 120, subscriberMultiplier: false, subscriberOnly: true}));
|
||||
*/
|
||||
|
||||
async createPoll(channel_id, title, choices, options = {}) {
|
||||
if ( typeof title !== 'string' )
|
||||
throw new TypeError('title must be string');
|
||||
|
@ -446,7 +449,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.archivePoll(1337));
|
||||
*/
|
||||
|
||||
async archivePoll(poll_id) {
|
||||
const data = await this.mutate({
|
||||
mutation: await import(/* webpackChunkName: 'queries' */ './data/poll-archive.gql'),
|
||||
|
@ -471,7 +473,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.archivePoll(1337));
|
||||
*/
|
||||
|
||||
async terminatePoll(poll_id) {
|
||||
const data = await this.mutate({
|
||||
mutation: await import(/* webpackChunkName: 'queries' */ './data/poll-terminate.gql'),
|
||||
|
@ -501,7 +502,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* this.twitch_data.getStreamMeta(19571641, null).then(function(returnObj){console.log(returnObj);});
|
||||
*/
|
||||
|
||||
getStreamMeta(id, login) {
|
||||
return new Promise((s, f) => {
|
||||
if ( id ) {
|
||||
|
@ -733,7 +733,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* this.twitch_data.getTag(50).then(function(returnObj){console.log(returnObj);});
|
||||
*/
|
||||
|
||||
getTag(id, want_description = false) {
|
||||
// Make sure we weren't accidentally handed a tag object.
|
||||
if ( id && id.id )
|
||||
|
@ -770,7 +769,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getTagImmediate(50));
|
||||
*/
|
||||
|
||||
getTagImmediate(id, callback, want_description = false) {
|
||||
// Make sure we weren't accidentally handed a tag object.
|
||||
if ( id && id.id )
|
||||
|
@ -810,7 +808,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getTopTags(20));
|
||||
*/
|
||||
|
||||
async getTopTags(limit = 50) {
|
||||
const data = await this.queryApollo(
|
||||
await import(/* webpackChunkName: 'queries' */ './data/tags-top.gql'),
|
||||
|
@ -845,7 +842,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getLanguagesFromTags([50, 53, 58, 84]));
|
||||
*/
|
||||
|
||||
getLanguagesFromTags(tags, callback) { // TODO: actually use the callback
|
||||
const out = [],
|
||||
fn = callback ? debounce(() => {
|
||||
|
@ -880,7 +876,6 @@ export default class TwitchData extends Module {
|
|||
*
|
||||
* console.log(this.twitch_data.getMatchingTags("Rainbo"));
|
||||
*/
|
||||
|
||||
async getMatchingTags(query, locale, category = null) {
|
||||
if ( ! locale )
|
||||
locale = this.locale;
|
||||
|
|
|
@ -74,6 +74,13 @@ body {
|
|||
}
|
||||
|
||||
|
||||
.ffz--tooltip-explain {
|
||||
width: 30rem;
|
||||
font-weight: 100;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
|
||||
.ffz__tooltip {
|
||||
z-index: 999999999;
|
||||
margin: 6px;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue