mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-03 08:28:31 +00:00
Add favorite emotes. Add emote menu sorting. Use a CSS variable for border colors for chat lines. Use fixed emote images for the emote menu.
This commit is contained in:
parent
a01b21e9d9
commit
1841ab156c
13 changed files with 450 additions and 99 deletions
|
@ -1,3 +1,11 @@
|
|||
<div class="list-header">4.0.0-beta2.3<span>@a07fb33207e6659acc9f</span> <time datetime="2018-04-09">(2018-04-09)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Added: Favorite emotes by Ctrl-Clicking them! ⌘-Click for Mac users.</li>
|
||||
<li>Added: Open information pages for emotes by Shift-Clicking them.</li>
|
||||
<li>Added: Sorting options for the emote menu.</li>
|
||||
<li>Changed: Use cleaned up versions of certain low quality global Twitch emotes in the emote menu.</li>
|
||||
</ul>
|
||||
|
||||
<div class="list-header">4.0.0-beta2.2<span>@201497e9898b452ba698</span> <time datetime="2018-04-08">(2018-04-08)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Added: Support for the old Featured Channels feature.</li>
|
||||
|
|
|
@ -6,6 +6,7 @@ import Module from 'utilities/module';
|
|||
import {DEBUG} from 'utilities/constants';
|
||||
|
||||
import SettingsManager from './settings/index';
|
||||
//import ExperimentManager from './experiments';
|
||||
import {TranslationManager} from './i18n';
|
||||
import SocketClient from './socket';
|
||||
import Site from 'site';
|
||||
|
@ -34,6 +35,7 @@ class FrankerFaceZ extends Module {
|
|||
// ========================================================================
|
||||
|
||||
this.inject('settings', SettingsManager);
|
||||
//this.inject('experiments', ExperimentManager);
|
||||
this.inject('i18n', TranslationManager);
|
||||
this.inject('socket', SocketClient);
|
||||
this.inject('site', Site);
|
||||
|
@ -95,7 +97,7 @@ class FrankerFaceZ extends Module {
|
|||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-beta2.2',
|
||||
major: 4, minor: 0, revision: 0, extra: '-beta2.3',
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
import Module from 'utilities/module';
|
||||
import {ManagedStyle} from 'utilities/dom';
|
||||
import {has, timeout, SourcedSet} from 'utilities/object';
|
||||
import {CLIENT_ID, API_SERVER} from 'utilities/constants';
|
||||
import {CLIENT_ID, API_SERVER, IS_OSX} from 'utilities/constants';
|
||||
|
||||
const MOD_KEY = IS_OSX ? 'metaKey' : 'ctrlKey';
|
||||
|
||||
const EXTRA_INVENTORY = [33563];
|
||||
|
||||
|
@ -88,6 +89,10 @@ export default class Emotes extends Module {
|
|||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Because this may be used elsewhere.
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
|
@ -142,6 +147,116 @@ export default class Emotes extends Module {
|
|||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Favorite Checking
|
||||
// ========================================================================
|
||||
|
||||
toggleFavorite(source, id, value = null) {
|
||||
const key = `favorite-emotes.${source}`,
|
||||
p = this.settings.provider,
|
||||
favorites = p.get(key) || [],
|
||||
|
||||
idx = favorites.indexOf(id);
|
||||
|
||||
if ( value === null )
|
||||
value = idx === -1;
|
||||
|
||||
if ( value && idx === -1 )
|
||||
favorites.push(id);
|
||||
else if ( ! value && idx !== -1 )
|
||||
favorites.splice(idx, 1);
|
||||
else
|
||||
return;
|
||||
|
||||
if ( favorites.length )
|
||||
p.set(key, favorites);
|
||||
else
|
||||
p.delete(key);
|
||||
|
||||
this.emit(':change-favorite', source, id, value);
|
||||
}
|
||||
|
||||
isFavorite(source, id) {
|
||||
const favorites = this.settings.provider.get(`favorite-emotes.${source}`);
|
||||
return favorites && favorites.includes(id);
|
||||
}
|
||||
|
||||
getFavorites(source) {
|
||||
return this.settings.provider.get(`favorite-emotes.${source}`) || [];
|
||||
}
|
||||
|
||||
|
||||
handleClick(event) {
|
||||
const target = event.target,
|
||||
ds = target && target.dataset;
|
||||
|
||||
if ( ! ds )
|
||||
return;
|
||||
|
||||
const provider = ds.provider;
|
||||
|
||||
if ( event[MOD_KEY] ) {
|
||||
// Favoriting Emotes
|
||||
let source, id;
|
||||
|
||||
if ( provider === 'twitch' ) {
|
||||
source = 'twitch';
|
||||
id = parseInt(ds.id, 10);
|
||||
|
||||
} else if ( provider === 'ffz' ) {
|
||||
const emote_set = this.emote_sets[ds.set],
|
||||
emote = emote_set && emote_set.emotes[ds.id];
|
||||
|
||||
if ( ! emote )
|
||||
return;
|
||||
|
||||
source = emote_set.source || 'ffz';
|
||||
id = emote.id;
|
||||
|
||||
} else
|
||||
return;
|
||||
|
||||
this.toggleFavorite(source, id);
|
||||
const tt = target._ffz_tooltip$0;
|
||||
if ( tt && tt.visible ) {
|
||||
tt.hide();
|
||||
setTimeout(() => document.contains(target) && tt.show(), 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( event.shiftKey && this.parent.context.get('chat.click-emotes') ) {
|
||||
let url;
|
||||
|
||||
if ( provider === 'twitch' )
|
||||
url = `https://twitchemotes.com/emotes/${ds.id}`;
|
||||
|
||||
else if ( provider === 'ffz' ) {
|
||||
const emote_set = this.emote_sets[ds.set],
|
||||
emote = emote_set && emote_set.emotes[ds.id];
|
||||
|
||||
if ( ! emote )
|
||||
return;
|
||||
|
||||
if ( emote.click_url )
|
||||
url = emote.click_url;
|
||||
|
||||
else if ( ! emote_set.source )
|
||||
url = `https://www.frankerfacez.com/emoticons/${emote.id}`;
|
||||
}
|
||||
|
||||
if ( url ) {
|
||||
const win = window.open();
|
||||
win.opener = null;
|
||||
win.location = url;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Access
|
||||
// ========================================================================
|
||||
|
@ -413,7 +528,14 @@ export default class Emotes extends Module {
|
|||
data.id = set_id;
|
||||
data.emoticons = undefined;
|
||||
|
||||
const bad_emotes = [];
|
||||
|
||||
for(const emote of ems) {
|
||||
if ( ! emote.id || ! emote.name || ! emote.urls ) {
|
||||
bad_emotes.push(emote);
|
||||
continue;
|
||||
}
|
||||
|
||||
emote.set_id = set_id;
|
||||
emote.srcSet = `${emote.urls[1]} 1x`;
|
||||
if ( emote.urls[2] )
|
||||
|
@ -443,6 +565,9 @@ export default class Emotes extends Module {
|
|||
new_ems[emote.id] = emote;
|
||||
}
|
||||
|
||||
if ( bad_emotes.length )
|
||||
this.log.warn(`Bad Emote Data for Set #${set_id}`, bad_emotes);
|
||||
|
||||
data.count = count;
|
||||
|
||||
if ( this.style && (css.length || data.css) )
|
||||
|
|
|
@ -259,6 +259,16 @@ export default class Chat extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.click-emotes', {
|
||||
default: true,
|
||||
|
||||
ui: {
|
||||
path: 'Chat > Behavior >> General',
|
||||
title: 'Open emote information pages by Shift-Clicking them.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
this.context.on('changed:theme.is-dark', () => {
|
||||
for(const room of this.iterateRooms())
|
||||
room.buildBitsCSS();
|
||||
|
|
|
@ -7,27 +7,12 @@
|
|||
import {sanitize, createElement} from 'utilities/dom';
|
||||
import {has, split_chars} from 'utilities/object';
|
||||
|
||||
import {TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
|
||||
|
||||
|
||||
const EMOTE_CLASS = 'chat-line__message--emote',
|
||||
LINK_REGEX = /([^\w@#%\-+=:~])?((?:(https?:\/\/)?(?:[\w@#%\-+=:~]+\.)+[a-z]{2,6}(?:\/[\w./@#%&()\-+=:?~]*)?))([^\w./@#%&()\-+=:?~]|\s|$)/g,
|
||||
MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g,
|
||||
|
||||
TWITCH_BASE = '//static-cdn.jtvnw.net/emoticons/v1/',
|
||||
REPLACEMENT_BASE = '//cdn.frankerfacez.com/script/replacements/',
|
||||
REPLACEMENTS = {
|
||||
15: '15-JKanStyle.png',
|
||||
16: '16-OptimizePrime.png',
|
||||
17: '17-StoneLightning.png',
|
||||
18: '18-TheRinger.png',
|
||||
//19: '19-PazPazowitz.png',
|
||||
//20: '20-EagleEye.png',
|
||||
//21: '21-CougarHunt.png',
|
||||
22: '22-RedCoat.png',
|
||||
26: '26-JonCarnage.png',
|
||||
//27: '27-PicoMause.png',
|
||||
30: '30-BCWarrior.png',
|
||||
33: '33-DansGame.png',
|
||||
36: '36-PJSalt.png'
|
||||
};
|
||||
MENTION_REGEX = /([^\w@#%\-+=:~])?(@([^\u0000-\u007F]+|\w+)+)([^\w./@#%&()\-+=:?~]|\s|$)/g;
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
@ -462,6 +447,7 @@ export const AddonEmotes = {
|
|||
data-set={token.set}
|
||||
data-modifiers={ml ? mods.map(x => x.id).join(' ') : null}
|
||||
data-modifier-info={ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null}
|
||||
onClick={this.emotes.handleClick}
|
||||
/>);
|
||||
|
||||
if ( ! ml )
|
||||
|
@ -472,6 +458,7 @@ export const AddonEmotes = {
|
|||
data-provider={token.provider}
|
||||
data-id={token.id}
|
||||
data-set={token.set}
|
||||
onClick={this.emotes.handleClick}
|
||||
>
|
||||
{emote}
|
||||
{mods.map(t => <span key={t.text}>{this.tokenizers.emote.render(t, createElement)}</span>)}
|
||||
|
@ -483,7 +470,7 @@ export const AddonEmotes = {
|
|||
provider = ds.provider,
|
||||
modifiers = ds.modifierInfo;
|
||||
|
||||
let preview, source, owner, mods;
|
||||
let preview, source, owner, mods, fav_source, emote_id;
|
||||
|
||||
if ( modifiers && modifiers !== 'null' ) {
|
||||
mods = JSON.parse(modifiers).map(([set_id, emote_id]) => {
|
||||
|
@ -499,11 +486,12 @@ export const AddonEmotes = {
|
|||
}
|
||||
|
||||
if ( provider === 'twitch' ) {
|
||||
const emote_id = parseInt(ds.id, 10),
|
||||
set_id = this.emotes.getTwitchEmoteSet(emote_id, tip.rerender),
|
||||
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);
|
||||
|
||||
preview = `//static-cdn.jtvnw.net/emoticons/v1/${emote_id}/4.0?_=preview`;
|
||||
fav_source = 'twitch';
|
||||
|
||||
if ( emote_set ) {
|
||||
source = emote_set.c_name;
|
||||
|
@ -525,10 +513,14 @@ export const AddonEmotes = {
|
|||
const emote_set = this.emotes.emote_sets[ds.set],
|
||||
emote = emote_set && emote_set.emotes[ds.id];
|
||||
|
||||
if ( emote_set )
|
||||
if ( emote_set ) {
|
||||
source = emote_set.source_line || (`${emote_set.source || 'FFZ'} ${emote_set.title || 'Global'}`);
|
||||
fav_source = emote_set.source || 'ffz';
|
||||
}
|
||||
|
||||
if ( emote ) {
|
||||
emote_id = emote.id;
|
||||
|
||||
if ( emote.owner )
|
||||
owner = this.i18n.t(
|
||||
'emote.owner', 'By: %{owner}',
|
||||
|
@ -539,9 +531,12 @@ export const AddonEmotes = {
|
|||
else if ( emote.urls[2] )
|
||||
preview = emote.urls[2];
|
||||
}
|
||||
}
|
||||
|
||||
} else
|
||||
return;
|
||||
|
||||
const name = ds.name || target.alt,
|
||||
favorite = fav_source && this.emotes.isFavorite(fav_source, emote_id),
|
||||
hide_source = ds.noSource === 'true';
|
||||
|
||||
return [
|
||||
|
@ -563,7 +558,9 @@ export const AddonEmotes = {
|
|||
|
||||
ds.sellout && (<div class="tw-mg-t-05 tw-border-t tw-pd-t-05">{ds.sellout}</div>),
|
||||
|
||||
mods && (<div class="tw-pd-t-1">{mods}</div>)
|
||||
mods && (<div class="tw-pd-t-1">{mods}</div>),
|
||||
|
||||
favorite && (<figure class="ffz--favorite ffz-i-star" />)
|
||||
];
|
||||
},
|
||||
|
||||
|
@ -729,8 +726,8 @@ export const TwitchEmotes = {
|
|||
srcSet = '';
|
||||
|
||||
} else {
|
||||
src = `${TWITCH_BASE}${e_id}/1.0`;
|
||||
srcSet = `${TWITCH_BASE}${e_id}/1.0 1x, ${TWITCH_BASE}${e_id}/2.0 2x`;
|
||||
src = `${TWITCH_EMOTE_BASE}${e_id}/1.0`;
|
||||
srcSet = `${TWITCH_EMOTE_BASE}${e_id}/1.0 1x, ${TWITCH_EMOTE_BASE}${e_id}/2.0 2x`;
|
||||
}
|
||||
|
||||
out.push({
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// ============================================================================
|
||||
|
||||
import {has, get, once, set_equals} from 'utilities/object';
|
||||
import {KNOWN_CODES, TWITCH_EMOTE_BASE} from 'utilities/constants';
|
||||
import {IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
|
||||
|
||||
import Twilight from 'site';
|
||||
import Module from 'utilities/module';
|
||||
|
@ -24,6 +24,34 @@ function maybe_date(val) {
|
|||
}
|
||||
|
||||
|
||||
const EMOTE_SORTERS = [
|
||||
function id_asc(a, b) {
|
||||
return a.id - b.id;
|
||||
},
|
||||
function id_desc(a, b) {
|
||||
return b.id - a.id;
|
||||
},
|
||||
function name_asc(a, b) {
|
||||
const a_n = a.name.toLowerCase(),
|
||||
b_n = b.name.toLowerCase();
|
||||
|
||||
if ( a_n < b_n ) return -1;
|
||||
if ( a_n > b_n ) return 1;
|
||||
|
||||
return a.id - b.id;
|
||||
},
|
||||
function name_desc(a, b) {
|
||||
const a_n = a.name.toLowerCase(),
|
||||
b_n = b.name.toLowerCase();
|
||||
|
||||
if ( a_n > b_n ) return -1;
|
||||
if ( a_n < b_n ) return 1;
|
||||
|
||||
return b.id - a.id;
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
function sort_sets(a, b) {
|
||||
const a_sk = a.sort_key,
|
||||
b_sk = b.sort_key;
|
||||
|
@ -123,13 +151,39 @@ export default class EmoteMenu extends Module {
|
|||
title: 'Default Tab',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: 'fav', title: 'Favorites'},
|
||||
{value: 'channel', title: 'Channel'},
|
||||
{value: 'all', title: 'All'}
|
||||
{value: 'all', title: 'My Emotes'}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('chat.emote-menu.sort-emotes', {
|
||||
default: 0,
|
||||
ui: {
|
||||
path: 'Chat > Emote Menu >> Sorting',
|
||||
title: 'Sort Emotes By',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: 0, title: 'Order Added (ID), Ascending'},
|
||||
{value: 1, title: 'Order Added (ID), Descending'},
|
||||
{value: 2, title: 'Name, Ascending'},
|
||||
{value: 3, title: 'Name, Descending'}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
this.settings.add('chat.emote-menu.sort-tiers-last', {
|
||||
default: true,
|
||||
ui: {
|
||||
path: 'Chat > Emote Menu >> Sorting',
|
||||
title: 'List emotes from higher sub tiers last.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.EmoteMenu = this.fine.define(
|
||||
'chat-emote-menu',
|
||||
n => n.subscriptionProductHasEmotes,
|
||||
|
@ -147,18 +201,24 @@ export default class EmoteMenu extends Module {
|
|||
this.on('chat.emotes:update-default-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:change-favorite', 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());
|
||||
const fup = () => this.MenuWrapper.forceUpdate();
|
||||
const rebuild = () => {
|
||||
for(const inst of this.MenuWrapper.instances)
|
||||
inst.componentWillReceiveProps(inst.props);
|
||||
}
|
||||
|
||||
this.chat.context.on('changed:chat.emote-menu.show-search', () =>
|
||||
this.MenuWrapper.forceUpdate());
|
||||
this.chat.context.on('changed:chat.fix-bad-emotes', rebuild);
|
||||
this.chat.context.on('changed:chat.emote-menu.sort-emotes', rebuild);
|
||||
this.chat.context.on('changed:chat.emote-menu.sort-tiers-last', rebuild);
|
||||
|
||||
this.chat.context.on('changed:chat.emote-menu.reduced-padding', () =>
|
||||
this.MenuWrapper.forceUpdate());
|
||||
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.icon', val =>
|
||||
this.css_tweaks.toggle('emote-menu', val));
|
||||
|
@ -214,17 +274,19 @@ export default class EmoteMenu extends Module {
|
|||
this.MenuEmote = class FFZMenuEmote extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleClick = props.onClickEmote.bind(this, props.data.name)
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
}
|
||||
|
||||
componentWillUpdate() {
|
||||
this.handleClick = this.props.onClickEmote.bind(this, this.props.data.name);
|
||||
handleClick(event) {
|
||||
if ( ! t.emotes.handleClick(event) )
|
||||
this.props.onClickEmote(this.props.data.name);
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.props.data,
|
||||
lock = this.props.lock,
|
||||
locked = this.props.locked,
|
||||
favorite = data.favorite,
|
||||
|
||||
sellout = lock ?
|
||||
this.props.all_locked ?
|
||||
|
@ -253,6 +315,7 @@ export default class EmoteMenu extends Module {
|
|||
alt={data.name}
|
||||
/>
|
||||
</figure>
|
||||
{favorite && (<figure class="ffz--favorite ffz-i-star" />)}
|
||||
{locked && (<figure class="ffz-i-lock" />)}
|
||||
</button>);
|
||||
}
|
||||
|
@ -307,7 +370,7 @@ export default class EmoteMenu extends Module {
|
|||
const data = this.props.data,
|
||||
filtered = this.props.filtered;
|
||||
|
||||
let show_heading = t.chat.context.get('chat.emote-menu.show-heading');
|
||||
let show_heading = ! data.is_favorites && t.chat.context.get('chat.emote-menu.show-heading');
|
||||
if ( show_heading === 2 )
|
||||
show_heading = ! filtered;
|
||||
else
|
||||
|
@ -541,6 +604,7 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
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);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
@ -587,7 +651,8 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
data = state.set_data || {},
|
||||
channel = state.channel_sets = [],
|
||||
all = state.all_sets = [];
|
||||
all = state.all_sets = [],
|
||||
favorites = state.favorites = [];
|
||||
|
||||
// If we're still loading, don't set any data.
|
||||
if ( props.loading || props.error || state.loading )
|
||||
|
@ -598,10 +663,32 @@ export default class EmoteMenu extends Module {
|
|||
if ( state.set_data && this.loadData(false, props, state) )
|
||||
return state;
|
||||
|
||||
// Sorters
|
||||
const sorter = EMOTE_SORTERS[t.chat.context.get('chat.emote-menu.sort-emotes')],
|
||||
sort_tiers = t.chat.context.get('chat.emote-menu.sort-tiers-last'),
|
||||
sort_emotes = (a,b) => {
|
||||
if ( a.inventory || b.inventory )
|
||||
return sorter(a,b);
|
||||
|
||||
if ( ! a.locked && b.locked ) return -1;
|
||||
if ( a.locked && ! b.locked ) return 1;
|
||||
|
||||
if ( sort_tiers || a.locked || b.locked ) {
|
||||
if ( a.set_id < b.set_id ) return -1;
|
||||
if ( a.set_id > b.set_id ) return 1;
|
||||
}
|
||||
|
||||
return sorter(a,b);
|
||||
}
|
||||
|
||||
// Start with the All tab. Some data calculated for
|
||||
// all is re-used for the Channel tab.
|
||||
|
||||
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_seen_favorites = new Set,
|
||||
|
||||
inventory = t.emotes.twitch_inventory_sets || new Set,
|
||||
grouped_sets = {},
|
||||
set_ids = new Set;
|
||||
|
@ -612,6 +699,7 @@ export default class EmoteMenu extends Module {
|
|||
continue;
|
||||
|
||||
const set_id = parseInt(emote_set.id, 10),
|
||||
is_inventory = inventory.has(set_id),
|
||||
set_data = data[set_id] || {},
|
||||
more_data = t.emotes.getTwitchSetChannel(set_id),
|
||||
image = set_data.image,
|
||||
|
@ -637,7 +725,7 @@ export default class EmoteMenu extends Module {
|
|||
key = `twitch-${chan.id}`;
|
||||
|
||||
else {
|
||||
if ( inventory.has(set_id) ) {
|
||||
if ( is_inventory ) {
|
||||
title = t.i18n.t('emote-menu.inventory', 'Inventory');
|
||||
key = 'twitch-inventory';
|
||||
icon = 'inventory';
|
||||
|
@ -698,21 +786,47 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
for(const emote of emote_set.emotes) {
|
||||
const id = parseInt(emote.id, 10),
|
||||
base = `${TWITCH_EMOTE_BASE}${id}`,
|
||||
name = KNOWN_CODES[emote.token] || emote.token;
|
||||
name = KNOWN_CODES[emote.token] || emote.token,
|
||||
mapped = emote_map && emote_map[name],
|
||||
overridden = mapped && mapped.id != id,
|
||||
replacement = REPLACEMENTS[id],
|
||||
is_fav = twitch_favorites.includes(id);
|
||||
|
||||
emotes.push({
|
||||
let src, srcSet;
|
||||
|
||||
if ( replacement && t.chat.context.get('chat.fix-bad-emotes') )
|
||||
src = `${REPLACEMENT_BASE}${replacement}`;
|
||||
else {
|
||||
const base = `${TWITCH_EMOTE_BASE}${id}`;
|
||||
src = `${base}/1.0`;
|
||||
srcSet = `${src} 1x, ${base}/2.0 2x`
|
||||
}
|
||||
|
||||
const em = {
|
||||
provider: 'twitch',
|
||||
id,
|
||||
set_id,
|
||||
name,
|
||||
src: `${base}/1.0`,
|
||||
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`
|
||||
});
|
||||
src,
|
||||
srcSet,
|
||||
overridden: overridden ? parseInt(mapped.id,10) : null,
|
||||
inventory: is_inventory,
|
||||
favorite: is_fav
|
||||
};
|
||||
|
||||
emotes.push(em);
|
||||
if ( is_fav && ! twitch_seen_favorites.has(id) ) {
|
||||
favorites.push(em);
|
||||
twitch_seen_favorites.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if ( emotes.length && ! all.includes(section) )
|
||||
all.push(section);
|
||||
if ( emotes.length ) {
|
||||
emotes.sort(sort_emotes);
|
||||
|
||||
if ( ! all.includes(section) )
|
||||
all.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -772,25 +886,36 @@ export default class EmoteMenu extends Module {
|
|||
for(const emote of product.emotes) {
|
||||
const id = parseInt(emote.id, 10),
|
||||
base = `${TWITCH_EMOTE_BASE}${id}`,
|
||||
name = KNOWN_CODES[emote.token] || emote.token;
|
||||
name = KNOWN_CODES[emote.token] || emote.token,
|
||||
is_fav = twitch_favorites.includes(id);
|
||||
|
||||
emotes.push({
|
||||
const em = {
|
||||
provider: 'twitch',
|
||||
id,
|
||||
set_id,
|
||||
name,
|
||||
locked,
|
||||
src: `${base}/1.0`,
|
||||
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`
|
||||
});
|
||||
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
|
||||
favorite: is_fav
|
||||
};
|
||||
|
||||
emotes.push(em);
|
||||
|
||||
if ( ! locked && is_fav && ! twitch_seen_favorites.has(id) ) {
|
||||
favorites.push(em);
|
||||
twitch_seen_favorites.add(id);
|
||||
}
|
||||
|
||||
if ( lock_set )
|
||||
lock_set.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if ( emotes.length )
|
||||
if ( emotes.length ) {
|
||||
emotes.sort(sort_emotes);
|
||||
channel.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -798,18 +923,23 @@ export default class EmoteMenu extends Module {
|
|||
const me = t.site.getUser();
|
||||
if ( me ) {
|
||||
const ffz_room = t.emotes.getRoomSetsWithSources(me.id, me.login, props.channel_id, null),
|
||||
ffz_global = t.emotes.getGlobalSetsWithSources(me.id, me.login);
|
||||
ffz_global = t.emotes.getGlobalSetsWithSources(me.id, me.login),
|
||||
seen_favorites = {};
|
||||
|
||||
for(const [emote_set, provider] of ffz_room) {
|
||||
const section = this.processFFZSet(emote_set, provider);
|
||||
if ( section )
|
||||
const section = this.processFFZSet(emote_set, provider, favorites, seen_favorites);
|
||||
if ( section ) {
|
||||
section.emotes.sort(sort_emotes);
|
||||
channel.push(section);
|
||||
}
|
||||
}
|
||||
|
||||
for(const [emote_set, provider] of ffz_global) {
|
||||
const section = this.processFFZSet(emote_set, provider);
|
||||
if ( section )
|
||||
const section = this.processFFZSet(emote_set, provider, favorites, seen_favorites);
|
||||
if ( section ) {
|
||||
section.emotes.sort(sort_emotes);
|
||||
all.push(section);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -820,14 +950,27 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
state.has_channel_tab = channel.length > 0;
|
||||
|
||||
state.fav_sets = [{
|
||||
key: 'favorites',
|
||||
is_favorites: true,
|
||||
emotes: favorites
|
||||
}];
|
||||
|
||||
// We use this sorter because we don't want things grouped by sets.
|
||||
favorites.sort(sorter);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
|
||||
processFFZSet(emote_set, provider) { // eslint-disable-line class-methods-use-this
|
||||
processFFZSet(emote_set, provider, favorites, seen_favorites) { // eslint-disable-line class-methods-use-this
|
||||
if ( ! emote_set || ! emote_set.emotes )
|
||||
return null;
|
||||
|
||||
const fav_key = emote_set.source || 'ffz',
|
||||
known_favs = t.emotes.getFavorites(fav_key),
|
||||
seen_favs = seen_favorites[fav_key] = seen_favorites[fav_key] || new Set;
|
||||
|
||||
const pdata = t.emotes.providers.get(provider),
|
||||
source = pdata && pdata.name ?
|
||||
(pdata.i18n_key ?
|
||||
|
@ -856,16 +999,22 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
for(const emote of Object.values(emote_set.emotes))
|
||||
if ( ! emote.hidden ) {
|
||||
const em = {
|
||||
provider: 'ffz',
|
||||
id: emote.id,
|
||||
set_id: emote_set.id,
|
||||
src: emote.urls[1],
|
||||
srcSet: emote.srcSet,
|
||||
name: emote.name
|
||||
};
|
||||
const is_fav = known_favs.includes(emote.id),
|
||||
em = {
|
||||
provider: 'ffz',
|
||||
id: emote.id,
|
||||
set_id: emote_set.id,
|
||||
src: emote.urls[1],
|
||||
srcSet: emote.srcSet,
|
||||
name: emote.name,
|
||||
favorite: is_fav
|
||||
};
|
||||
|
||||
emotes.push(em);
|
||||
if ( is_fav && ! seen_favs.has(emote.id) ) {
|
||||
favorites.push(em);
|
||||
seen_favs.add(emote.id);
|
||||
}
|
||||
}
|
||||
|
||||
if ( emotes.length )
|
||||
|
@ -886,7 +1035,7 @@ export default class EmoteMenu extends Module {
|
|||
renderError() {
|
||||
return (<div class="tw-align-center tw-pd-1">
|
||||
<div class="tw-mg-b-1">
|
||||
<div class="tw-mg-5">
|
||||
<div class="tw-mg-2">
|
||||
<img
|
||||
src="//cdn.frankerfacez.com/emoticon/26608/2"
|
||||
srcSet="//cdn.frankerfacez.com/emoticon/26608/2 1x, //cdn.frankerfacez.com/emoticon/26608/4 2x"
|
||||
|
@ -904,7 +1053,7 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
renderEmpty() { // eslint-disable-line class-methods-use-this
|
||||
return (<div class="tw-align-center tw-pd-1">
|
||||
<div class="tw-mg-5">
|
||||
<div class="tw-mg-2">
|
||||
<img
|
||||
src="//cdn.frankerfacez.com/emoticon/26608/2"
|
||||
srcSet="//cdn.frankerfacez.com/emoticon/26608/2 1x, //cdn.frankerfacez.com/emoticon/26608/4 2x"
|
||||
|
@ -912,7 +1061,9 @@ export default class EmoteMenu extends Module {
|
|||
</div>
|
||||
{this.state.filtered ?
|
||||
t.i18n.t('emote-menu.empty-search', 'There are no matching emotes.') :
|
||||
t.i18n.t('emote-menu.empty', "There's nothing here.")}
|
||||
this.state.tab === 'fav' ?
|
||||
t.i18n.t('emote-menu.empty-favs', "You don't have any favorite emotes. To favorite an emote, find it and %{hotkey}-Click it.", {hotkey: IS_OSX ? '⌘' : 'Ctrl'}) :
|
||||
t.i18n.t('emote-menu.empty', "There's nothing here.")}
|
||||
</div>)
|
||||
}
|
||||
|
||||
|
@ -935,6 +1086,9 @@ export default class EmoteMenu extends Module {
|
|||
tab = 'all';
|
||||
|
||||
switch(tab) {
|
||||
case 'fav':
|
||||
sets = this.state.filtered_fav_sets;
|
||||
break;
|
||||
case 'channel':
|
||||
sets = this.state.filtered_channel_sets;
|
||||
break;
|
||||
|
@ -984,9 +1138,16 @@ export default class EmoteMenu extends Module {
|
|||
</div>
|
||||
</div>)}
|
||||
<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">
|
||||
<div
|
||||
class={`ffz-tooltip emote-picker__tab tw-pd-x-1${tab === 'fav' ? ' emote-picker__tab--active' : ''}`}
|
||||
id="emote-picker__fav"
|
||||
data-tab="fav"
|
||||
data-tooltip-type="html"
|
||||
data-title={t.i18n.t('emote-menu.favorites', 'Favorites')}
|
||||
onClick={this.clickTab}
|
||||
>
|
||||
<figure class="ffz-i-star" />
|
||||
</div>)}
|
||||
</div>
|
||||
{this.state.has_channel_tab && <div
|
||||
class={`emote-picker__tab tw-pd-x-1${tab === 'channel' ? ' emote-picker__tab--active' : ''}`}
|
||||
id="emote-picker__channel"
|
||||
|
@ -1001,7 +1162,7 @@ export default class EmoteMenu extends Module {
|
|||
data-tab="all"
|
||||
onClick={this.clickTab}
|
||||
>
|
||||
{t.i18n.t('emote-menu.all', 'All')}
|
||||
{t.i18n.t('emote-menu.my-emotes', 'My Emotes')}
|
||||
</div>
|
||||
<div class="tw-flex-grow-1" />
|
||||
{!loading && (<div
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
padding-top: calc(.5rem - 1px) !important;
|
||||
|
||||
border-top: 1px solid #aaa;
|
||||
border-bottom-color: rgba(255,255,255,0.5);
|
||||
border-bottom-color: var rgba(255,255,255,0.5);
|
||||
|
||||
.tw-theme--dark & {
|
||||
border-top-color: #000;
|
||||
|
|
|
@ -8,12 +8,7 @@
|
|||
.chat-line__raid,
|
||||
.chat-line__subscribe {
|
||||
padding-top: calc(.5rem - 1px) !important;
|
||||
|
||||
border-top: 1px solid #dad8de;
|
||||
|
||||
.tw-theme--dark & {
|
||||
border-top-color: #2c2541;
|
||||
}
|
||||
border-top: 1px solid var(--ffz-border-color);
|
||||
|
||||
&:first-child {
|
||||
border-top-color: transparent !important;
|
||||
|
|
|
@ -8,12 +8,7 @@
|
|||
.chat-line__raid,
|
||||
.chat-line__subscribe {
|
||||
padding-bottom: calc(.5rem - 1px) !important;
|
||||
|
||||
border-bottom: 1px solid #dad8de;
|
||||
|
||||
.tw-theme--dark & {
|
||||
border-bottom-color: #2c2541;
|
||||
}
|
||||
border-bottom: 1px solid var(--ffz-border-color);
|
||||
|
||||
&:last-child:nth-child(odd) {
|
||||
border-bottom-color: transparent !important;
|
||||
|
|
|
@ -13,9 +13,11 @@ import THEME_CSS_URL from 'site/styles/theme.scss';
|
|||
export default class ThemeEngine extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
this.inject('site');
|
||||
this.inject('settings');
|
||||
|
||||
this.inject('site');
|
||||
this.inject('site.css_tweaks');
|
||||
|
||||
this.should_enable = true;
|
||||
|
||||
this.settings.add('theme.dark', {
|
||||
|
@ -40,7 +42,7 @@ export default class ThemeEngine extends Module {
|
|||
process(ctx) {
|
||||
return ctx.get('context.ui.theme') === 1;
|
||||
},
|
||||
changed: val => document.body.classList.toggle('tw-theme--dark', val)
|
||||
changed: () => this.updateCSS()
|
||||
});
|
||||
|
||||
this.settings.add('theme.tooltips-dark', {
|
||||
|
@ -53,6 +55,18 @@ export default class ThemeEngine extends Module {
|
|||
this._style = null;
|
||||
}
|
||||
|
||||
|
||||
updateCSS() {
|
||||
const dark = this.settings.get('theme.is-dark'),
|
||||
gray = this.settings.get('theme.dark');
|
||||
|
||||
document.body.classList.toggle('tw-theme--dark', dark);
|
||||
document.body.classList.toggle('tw-theme--ffz', gray);
|
||||
|
||||
this.css_tweaks.setVariable('border-color', dark ? (gray ? '#2a2a2a' : '#2c2541') : '#dad8de');
|
||||
}
|
||||
|
||||
|
||||
toggleStyle(enable) {
|
||||
if ( ! this._style ) {
|
||||
if ( ! enable )
|
||||
|
@ -74,11 +88,10 @@ export default class ThemeEngine extends Module {
|
|||
|
||||
updateSetting(enable) {
|
||||
this.toggleStyle(enable);
|
||||
document.body.classList.toggle('tw-theme--ffz', enable);
|
||||
this.updateCSS();
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.updateSetting(this.settings.get('theme.dark'));
|
||||
document.body.classList.toggle('tw-theme--dark', this.settings.get('theme.is-dark'));
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
import {has} from 'utilities/object';
|
||||
|
||||
export function hue2rgb(p, q, t) {
|
||||
if ( t < 0 ) t += 1;
|
||||
if ( t > 1 ) t -= 1;
|
||||
|
@ -565,7 +563,7 @@ export class ColorAdjuster {
|
|||
|
||||
|
||||
rebuildContrast() {
|
||||
this._cache = {};
|
||||
this._cache = new Map;
|
||||
|
||||
const base = RGBAColor.fromCSS(this._base),
|
||||
lum = base.luminance();
|
||||
|
@ -606,8 +604,8 @@ export class ColorAdjuster {
|
|||
if ( ! color )
|
||||
return null;
|
||||
|
||||
if ( has(this._cache, color) )
|
||||
return this._cache[color];
|
||||
if ( this._cache.has(color) )
|
||||
return this._cache.get(color);
|
||||
|
||||
let rgb = RGBAColor.fromCSS(color);
|
||||
|
||||
|
@ -650,7 +648,8 @@ export class ColorAdjuster {
|
|||
rgb = rgb.brighten(-1);
|
||||
}
|
||||
|
||||
const out = this._cache[color] = rgb.toHex();
|
||||
const out = rgb.toHex();
|
||||
this._cache.set(color, out);
|
||||
return out;
|
||||
}
|
||||
}
|
|
@ -33,6 +33,25 @@ export const KNOWN_CODES = {
|
|||
'Gr(a|e)yFace': 'GrayFace'
|
||||
};
|
||||
|
||||
export const REPLACEMENT_BASE = '//cdn.frankerfacez.com/script/replacements/';
|
||||
|
||||
export const REPLACEMENTS = {
|
||||
15: '15-JKanStyle.png',
|
||||
16: '16-OptimizePrime.png',
|
||||
17: '17-StoneLightning.png',
|
||||
18: '18-TheRinger.png',
|
||||
//19: '19-PazPazowitz.png',
|
||||
//20: '20-EagleEye.png',
|
||||
//21: '21-CougarHunt.png',
|
||||
22: '22-RedCoat.png',
|
||||
26: '26-JonCarnage.png',
|
||||
//27: '27-PicoMause.png',
|
||||
30: '30-BCWarrior.png',
|
||||
33: '33-DansGame.png',
|
||||
36: '36-PJSalt.png'
|
||||
};
|
||||
|
||||
|
||||
export const WS_CLUSTERS = {
|
||||
Production: [
|
||||
['wss://catbag.frankerfacez.com/', 0.25],
|
||||
|
|
|
@ -29,6 +29,33 @@
|
|||
}
|
||||
|
||||
|
||||
.ffz-i-lock {
|
||||
position: absolute;
|
||||
bottom: 0; right: 0;
|
||||
border-radius: .2rem;
|
||||
font-size: 1rem;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.ffz--favorite {
|
||||
position: absolute;
|
||||
bottom: 0; right: 0;
|
||||
|
||||
font-size: 1rem;
|
||||
border-radius: .5rem;
|
||||
color: gold;
|
||||
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
|
||||
.ffz__tooltip & {
|
||||
bottom: .1rem; right: .1rem;
|
||||
padding: .2rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.modified-emote {
|
||||
~ .chat-line__message--emote {
|
||||
position: relative;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue