mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-07 23:00:54 +00:00
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.
This commit is contained in:
parent
a6ee3e5013
commit
742da82515
8 changed files with 396 additions and 247 deletions
|
@ -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": {
|
||||
|
|
|
@ -5,13 +5,12 @@ query FFZ_GetEmoteInfo($id: ID!) {
|
|||
text
|
||||
subscriptionProduct {
|
||||
id
|
||||
state
|
||||
owner {
|
||||
id
|
||||
login
|
||||
displayName
|
||||
}
|
||||
tier
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
15
src/modules/chat/emote_set_info.gql
Normal file
15
src/modules/chat/emote_set_info.gql
Normal file
|
@ -0,0 +1,15 @@
|
|||
query FFZ_GetEmoteSetInfo($id: ID!) {
|
||||
emoteSet(id: $id) {
|
||||
id
|
||||
owner {
|
||||
id
|
||||
login
|
||||
displayName
|
||||
subscriptionProducts {
|
||||
id
|
||||
emoteSetID
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
const set_id = parseInt(emote.setID, 10),
|
||||
channel = emote?.subscriptionProduct?.owner;
|
||||
if ( set_id && ! tsc.has(set_id) ) {
|
||||
const type = determineEmoteType(emote);
|
||||
|
||||
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);
|
||||
|
||||
}).catch(() => tes.delete(emote_id));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
async awaitTwitchSetChannel(set_id, perform_lookup = true) {
|
||||
const tes = this.__twitch_set_to_channel,
|
||||
inv = this.twitch_inventory_sets;
|
||||
|
||||
if ( isNaN(set_id) || ! isFinite(set_id) )
|
||||
return null;
|
||||
|
||||
if ( tes.has(set_id) )
|
||||
return tes.get(set_id);
|
||||
|
||||
if ( inv.has(set_id) )
|
||||
return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'}
|
||||
|
||||
if ( ! perform_lookup )
|
||||
return null;
|
||||
|
||||
tes.set(set_id, null);
|
||||
|
||||
try {
|
||||
const data = await timeout(this.socket.call('get_emote_set', set_id), 1000);
|
||||
tes.set(set_id, data);
|
||||
return data;
|
||||
|
||||
} catch(err) {
|
||||
if ( err === 'No known Twitch emote set with that ID.' )
|
||||
return null;
|
||||
|
||||
tes.delete(set_id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
getTwitchSetChannel(set_id, callback, perform_lookup = true) {
|
||||
const tes = this.__twitch_set_to_channel,
|
||||
inv = this.twitch_inventory_sets;
|
||||
|
||||
if ( isNaN(set_id) || ! isFinite(set_id) )
|
||||
return null;
|
||||
|
||||
if ( tes.has(set_id) )
|
||||
return tes.get(set_id);
|
||||
|
||||
if ( inv.has(set_id) )
|
||||
return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'}
|
||||
|
||||
if ( ! perform_lookup )
|
||||
return null;
|
||||
|
||||
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);
|
||||
|
||||
}).catch(err => {
|
||||
if ( err === 'No known Twitch emote set with that ID.' ) {
|
||||
if ( callback )
|
||||
callback(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
tes.delete(set_id)
|
||||
tsc.set(set_id, {
|
||||
id: set_id,
|
||||
type,
|
||||
owner: emote?.subscriptionProduct?.owner
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tes.set(emote_id, set_id);
|
||||
for(const fn of promises)
|
||||
fn(set_id);
|
||||
|
||||
}).catch(() => {
|
||||
tes.set(emote_id, null);
|
||||
for(const fn of promises)
|
||||
fn(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getTwitchEmoteSet(emote_id, callback) {
|
||||
const promise = this._getTwitchEmoteSet(emote_id);
|
||||
if ( callback )
|
||||
promise.then(callback);
|
||||
else
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
_getTwitchSetChannel(set_id) {
|
||||
const tsc = this.__twitch_set_to_channel;
|
||||
|
||||
if ( isNaN(set_id) || ! isFinite(set_id) )
|
||||
return Promise.resolve(null);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const apollo = this.resolve('site.apollo');
|
||||
if ( ! apollo?.client )
|
||||
return Promise.resolve(null);
|
||||
|
||||
return new Promise(s => {
|
||||
const promises = [s];
|
||||
tsc.set(set_id, promises);
|
||||
|
||||
timeout(apollo.client.query({
|
||||
query: GET_EMOTE_SET,
|
||||
variables: {
|
||||
id: `${set_id}`
|
||||
}
|
||||
}), 2000).then(data => {
|
||||
const set = data?.data?.emoteSet;
|
||||
let result = null;
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
tsc.set(set_id, result);
|
||||
for(const fn of promises)
|
||||
fn(result);
|
||||
|
||||
}).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;
|
||||
|
||||
return EmoteTypes.Global;
|
||||
}
|
||||
|
||||
|
||||
function determineSetType(set) {
|
||||
const id = parseInt(set.id, 10);
|
||||
|
||||
if ( TWITCH_GLOBAL_SETS.includes(id) )
|
||||
return EmoteTypes.Global;
|
||||
|
||||
if ( TWITCH_POINTS_SETS.includes(id) )
|
||||
return EmoteTypes.ChannelPoints;
|
||||
|
||||
if ( TWITCH_PRIME_SETS.includes(id) )
|
||||
return EmoteTypes.Prime;
|
||||
|
||||
const owner = set.owner;
|
||||
if ( owner ) {
|
||||
if ( owner.id == 139075904 )
|
||||
return EmoteTypes.LimitedTime;
|
||||
|
||||
let product;
|
||||
if ( Array.isArray(owner.subscriptionProducts) )
|
||||
for(const prod of owner.subscriptionProducts)
|
||||
if ( set.id == prod.emoteSetID ) {
|
||||
product = prod;
|
||||
break;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
|
@ -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 = {
|
|||
</span>);
|
||||
},
|
||||
|
||||
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' ) {
|
||||
|
|
|
@ -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 (<section ref={this.saveRef} data-key={data.key} class={filtered ? 'filtered' : ''} onMouseEnter={this.mouseEnter}>
|
||||
{show_heading ? (<heading class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onClick={this.clickHeading}>
|
||||
{image}
|
||||
|
@ -604,7 +602,7 @@ export default class EmoteMenu extends Module {
|
|||
/>)}
|
||||
</div>
|
||||
<div class="tw-flex-grow-1" />
|
||||
{(data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source) || 'FrankerFaceZ'}
|
||||
{source}
|
||||
{filtered ? '' : <figure class={`tw-pd-l-05 ffz-i-${collapsed ? 'left' : 'down'}-dir`} />}
|
||||
</heading>) : 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,12 +1181,7 @@ 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,
|
||||
|
||||
em = {
|
||||
const em = {
|
||||
provider: 'emoji',
|
||||
emoji: true,
|
||||
code: emoji.code,
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
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,13 +1733,25 @@ 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;
|
||||
let tab, sets, is_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();
|
||||
|
||||
} 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';
|
||||
|
||||
const is_emoji = tab === 'emoji';
|
||||
is_emoji = tab === 'emoji';
|
||||
|
||||
switch(tab) {
|
||||
case 'fav':
|
||||
|
@ -1670,6 +1768,7 @@ export default class EmoteMenu extends Module {
|
|||
sets = this.state.filtered_all_sets;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (<div
|
||||
class={`tw-balloon tw-balloon--md tw-balloon--up tw-balloon--right tw-block tw-absolute ffz--emote-picker${padding ? ' reduced-padding' : ''}`}
|
||||
|
@ -1684,7 +1783,7 @@ 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 => 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 && <t.EmojiTonePicker
|
||||
{(no_tabs || is_emoji) && <t.EmojiTonePicker
|
||||
tone={this.state.tone}
|
||||
choices={this.state.tone_emoji}
|
||||
pickTone={this.pickTone}
|
||||
|
@ -1781,19 +1880,19 @@ export default class EmoteMenu extends Module {
|
|||
</div>
|
||||
</button>
|
||||
</div>}
|
||||
{/*<div class="tw-flex-grow-1" />
|
||||
{!loading && (<div class="emote-picker-tab-item tw-relative">
|
||||
<div class="tw-flex-grow-1" />
|
||||
<div class="emote-picker-tab-item tw-relative">
|
||||
<button
|
||||
class="ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive"
|
||||
data-tooltip-type="html"
|
||||
data-title={t.i18n.t('emote-menu.refresh', 'Refresh Data')}
|
||||
onClick={this.clickRefresh}
|
||||
data-title={t.i18n.t('emote-menu.settings', 'Open Settings')}
|
||||
onClick={this.clickSettings}
|
||||
>
|
||||
<div class="tw-inline-flex tw-pd-x-1 tw-pd-y-05 tw-font-size-4">
|
||||
<figure class="ffz-i-arrows-cw" />
|
||||
<figure class="ffz-i-cog" />
|
||||
</div>
|
||||
</button>
|
||||
</div>)*/}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
@ -88,3 +91,19 @@ export const IS_WEBKIT = navigator.userAgent.indexOf('AppleWebKit/') !== -1 && n
|
|||
export const IS_FIREFOX = navigator.userAgent.indexOf('Firefox/') !== -1;
|
||||
|
||||
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'
|
||||
);
|
Loading…
Add table
Add a link
Reference in a new issue