mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-28 05:15:54 +00:00
4.20.79
* Added: Setting to control the display of animated emotes. Before you all get excited, this is for better integration with the `BetterTTV Emotes` add-on as well as any future add-ons with animated emotes. * Added: Support for "Native Sort" for the emote menu, which uses the order from the API response. * Added: Quick Navigation for the emote menu, which places a list of emote sets along the right side. * Fixed: Skin tone picker for emoji in the emote menu not appearing correctly. * Fixed: Center the FFZ Control Center correctly when opening it. * Fixed: Modify the DOM we're emitting on clips pages for chat lines. Fixes night/betterttv#4416 * API Added: Support for animated images for emotes.
This commit is contained in:
parent
5a5a68adb6
commit
fc359d53e0
12 changed files with 602 additions and 101 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.78",
|
"version": "4.20.79",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
|
@ -12,6 +12,9 @@ import {NEW_API, API_SERVER, IS_OSX, EmoteTypes, TWITCH_GLOBAL_SETS, TWITCH_POIN
|
||||||
import GET_EMOTE from './emote_info.gql';
|
import GET_EMOTE from './emote_info.gql';
|
||||||
import GET_EMOTE_SET from './emote_set_info.gql';
|
import GET_EMOTE_SET from './emote_set_info.gql';
|
||||||
|
|
||||||
|
const HoverRAF = Symbol('FFZ:Hover:RAF');
|
||||||
|
const HoverState = Symbol('FFZ:Hover:State');
|
||||||
|
|
||||||
const MOD_KEY = IS_OSX ? 'metaKey' : 'ctrlKey';
|
const MOD_KEY = IS_OSX ? 'metaKey' : 'ctrlKey';
|
||||||
|
|
||||||
const MODIFIERS = {
|
const MODIFIERS = {
|
||||||
|
@ -133,6 +136,8 @@ export default class Emotes extends Module {
|
||||||
|
|
||||||
// Because this may be used elsewhere.
|
// Because this may be used elsewhere.
|
||||||
this.handleClick = this.handleClick.bind(this);
|
this.handleClick = this.handleClick.bind(this);
|
||||||
|
this.animHover = this.animHover.bind(this);
|
||||||
|
this.animLeave = this.animLeave.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
|
@ -249,6 +254,68 @@ export default class Emotes extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ========================================================================
|
||||||
|
// Animation Hover
|
||||||
|
// ========================================================================
|
||||||
|
|
||||||
|
animHover(event) { // eslint-disable-line class-methods-use-this
|
||||||
|
const target = event.currentTarget;
|
||||||
|
if ( target[HoverState] )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( target[HoverRAF] )
|
||||||
|
cancelAnimationFrame(target[HoverRAF]);
|
||||||
|
|
||||||
|
target[HoverRAF] = requestAnimationFrame(() => {
|
||||||
|
target[HoverRAF] = null;
|
||||||
|
if ( target[HoverState] )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( ! target.matches(':hover') )
|
||||||
|
return;
|
||||||
|
|
||||||
|
target[HoverState] = true;
|
||||||
|
const emotes = target.querySelectorAll('.ffz-hover-emote');
|
||||||
|
for(const em of emotes) {
|
||||||
|
const ds = em.dataset;
|
||||||
|
if ( ds.normalSrc && ds.hoverSrc ) {
|
||||||
|
em.src = ds.hoverSrc;
|
||||||
|
em.srcset = ds.hoverSrcSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
animLeave(event) { // eslint-disable-line class-methods-use-this
|
||||||
|
const target = event.currentTarget;
|
||||||
|
if ( ! target[HoverState] )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( target[HoverRAF] )
|
||||||
|
cancelAnimationFrame(target[HoverRAF]);
|
||||||
|
|
||||||
|
target[HoverRAF] = requestAnimationFrame(() => {
|
||||||
|
target[HoverRAF] = null;
|
||||||
|
if ( ! target[HoverState] )
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ( target.matches(':hover') )
|
||||||
|
return;
|
||||||
|
|
||||||
|
target[HoverState] = false;
|
||||||
|
const emotes = target.querySelectorAll('.ffz-hover-emote');
|
||||||
|
for(const em of emotes) {
|
||||||
|
const ds = em.dataset;
|
||||||
|
if ( ds.normalSrc ) {
|
||||||
|
em.src = ds.normalSrc;
|
||||||
|
em.srcset = ds.normalSrcSet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Favorite Checking
|
// Favorite Checking
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
@ -724,6 +791,7 @@ export default class Emotes extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
emote.set_id = set_id;
|
emote.set_id = set_id;
|
||||||
|
emote.src = emote.urls[1];
|
||||||
emote.srcSet = `${emote.urls[1]} 1x`;
|
emote.srcSet = `${emote.urls[1]} 1x`;
|
||||||
if ( emote.urls[2] )
|
if ( emote.urls[2] )
|
||||||
emote.srcSet += `, ${emote.urls[2]} 2x`;
|
emote.srcSet += `, ${emote.urls[2]} 2x`;
|
||||||
|
@ -738,16 +806,35 @@ export default class Emotes extends Module {
|
||||||
emote.srcSet2 += `, ${emote.urls[4]} 2x`;
|
emote.srcSet2 += `, ${emote.urls[4]} 2x`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ( emote.animated?.[1] ) {
|
||||||
|
emote.animSrc = emote.animated[1];
|
||||||
|
emote.animSrcSet = `${emote.animated[1]} 1x`;
|
||||||
|
if ( emote.animated[2] ) {
|
||||||
|
emote.animSrcSet += `, ${emote.animated[2]} 2x`;
|
||||||
|
emote.animSrc2 = emote.animated[2];
|
||||||
|
emote.animSrcSet2 = `${emote.animated[2]} 1x`;
|
||||||
|
|
||||||
|
if ( emote.animated[4] ) {
|
||||||
|
emote.animSrcSet += `, ${emote.animated[4]} 4x`;
|
||||||
|
emote.animSrcSet2 += `, ${emote.animated[4]} 2x`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emote.token = {
|
emote.token = {
|
||||||
type: 'emote',
|
type: 'emote',
|
||||||
id: emote.id,
|
id: emote.id,
|
||||||
set: set_id,
|
set: set_id,
|
||||||
provider: 'ffz',
|
provider: 'ffz',
|
||||||
src: emote.urls[1],
|
src: emote.src,
|
||||||
srcSet: emote.srcSet,
|
srcSet: emote.srcSet,
|
||||||
can_big: !! emote.urls[2],
|
can_big: !! emote.urls[2],
|
||||||
src2: emote.src2,
|
src2: emote.src2,
|
||||||
srcSet2: emote.srcSet2,
|
srcSet2: emote.srcSet2,
|
||||||
|
animSrc: emote.animSrc,
|
||||||
|
animSrcSet: emote.animSrcSet,
|
||||||
|
animSrc2: emote.animSrc2,
|
||||||
|
animSrcSet2: emote.animSrcSet2,
|
||||||
text: emote.hidden ? '???' : emote.name,
|
text: emote.hidden ? '???' : emote.name,
|
||||||
length: emote.name.length,
|
length: emote.name.length,
|
||||||
height: emote.height
|
height: emote.height
|
||||||
|
|
|
@ -918,8 +918,49 @@ export default class Chat extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.emotes.animated', {
|
||||||
|
default: null,
|
||||||
|
process(ctx, val) {
|
||||||
|
if ( val == null )
|
||||||
|
val = ctx.get('ffzap.betterttv.gif_emoticons_mode') === 2 ? 1 : 0;
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Appearance >> Emotes',
|
||||||
|
title: 'Animated Emotes',
|
||||||
|
description: 'This controls whether or not animated emotes are allowed to play in chat. When this is `Disabled`, emotes will appear as static images. Setting this to `Enable on Hover` may cause performance issues.',
|
||||||
|
component: 'setting-select-box',
|
||||||
|
data: [
|
||||||
|
{value: 0, title: 'Disabled'},
|
||||||
|
{value: 1, title: 'Enabled'},
|
||||||
|
{value: 2, title: 'Enable on Hover'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.settings.add('tooltip.emote-images.animated', {
|
||||||
|
requires: ['chat.emotes.animated'],
|
||||||
|
default: null,
|
||||||
|
process(ctx, val) {
|
||||||
|
if ( val == null )
|
||||||
|
val = ctx.get('chat.emotes.animated') ? true : false;
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Tooltips >> Emotes',
|
||||||
|
title: 'Display animated images of emotes.',
|
||||||
|
description: 'If this is not overridden, animated images are only shown in emote tool-tips if [Chat > Appearance >> Emotes > Animated Emotes](~chat.appearance.emotes) is not disabled.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('chat.bits.animated', {
|
this.settings.add('chat.bits.animated', {
|
||||||
default: true,
|
requires: ['chat.emotes.animated'],
|
||||||
|
default: null,
|
||||||
|
process(ctx, val) {
|
||||||
|
if ( val == null )
|
||||||
|
val = ctx.get('chat.emotes.animated') ? true : false
|
||||||
|
},
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Chat > Bits and Cheering >> Appearance',
|
path: 'Chat > Bits and Cheering >> Appearance',
|
||||||
|
|
|
@ -1091,12 +1091,30 @@ export const CheerEmotes = {
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
const render_emote = (token, createElement, wrapped) => {
|
const render_emote = (token, createElement, wrapped) => {
|
||||||
|
const hover = token.anim === 2;
|
||||||
|
let src, srcSet, hoverSrc, hoverSrcSet, normalSrc, normalSrcSet;
|
||||||
|
|
||||||
|
if ( token.anim === 1 && token.animSrc ) {
|
||||||
|
src = token.big ? token.animSrc2 : token.animSrc;
|
||||||
|
srcSet = token.big ? token.animSrcSet2 : token.animSrcSet;
|
||||||
|
} else {
|
||||||
|
src = token.big ? token.src2 : token.src;
|
||||||
|
srcSet = token.big ? token.srcSet2 : token.srcSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( hover && token.animSrc ) {
|
||||||
|
normalSrc = src;
|
||||||
|
normalSrcSet = srcSet;
|
||||||
|
hoverSrc = token.big ? token.animSrc2 : token.animSrc;
|
||||||
|
hoverSrcSet = token.big ? token.animSrcSet2 : token.animSrcSet;
|
||||||
|
}
|
||||||
|
|
||||||
const mods = token.modifiers || [], ml = mods.length,
|
const mods = token.modifiers || [], ml = mods.length,
|
||||||
emote = createElement('img', {
|
emote = createElement('img', {
|
||||||
class: `${EMOTE_CLASS} ffz-tooltip${token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`,
|
class: `${EMOTE_CLASS} ffz-tooltip${hoverSrc ? ' ffz-hover-emote' : ''}${token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`,
|
||||||
attrs: {
|
attrs: {
|
||||||
src: token.big && token.src2 || token.src,
|
src,
|
||||||
srcSet: token.big && token.srcSet2 || token.srcSet,
|
srcSet,
|
||||||
alt: token.text,
|
alt: token.text,
|
||||||
height: (token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined,
|
height: (token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined,
|
||||||
'data-tooltip-type': 'emote',
|
'data-tooltip-type': 'emote',
|
||||||
|
@ -1105,6 +1123,10 @@ const render_emote = (token, createElement, wrapped) => {
|
||||||
'data-set': token.set,
|
'data-set': token.set,
|
||||||
'data-code': token.code,
|
'data-code': token.code,
|
||||||
'data-variant': token.variant,
|
'data-variant': token.variant,
|
||||||
|
'data-normal-src': normalSrc,
|
||||||
|
'data-normal-src-set': normalSrcSet,
|
||||||
|
'data-hover-src': hoverSrc,
|
||||||
|
'data-hover-src-set': hoverSrcSet,
|
||||||
'data-modifiers': ml ? mods.map(x => x.id).join(' ') : null,
|
'data-modifiers': ml ? mods.map(x => x.id).join(' ') : null,
|
||||||
'data-modifier-info': ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null
|
'data-modifier-info': ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null
|
||||||
}
|
}
|
||||||
|
@ -1147,11 +1169,29 @@ export const AddonEmotes = {
|
||||||
},
|
},
|
||||||
|
|
||||||
render(token, createElement, wrapped) {
|
render(token, createElement, wrapped) {
|
||||||
|
const hover = token.anim === 2;
|
||||||
|
let src, srcSet, hoverSrc, hoverSrcSet, normalSrc, normalSrcSet;
|
||||||
|
|
||||||
|
if ( token.anim === 1 && token.animSrc ) {
|
||||||
|
src = token.big ? token.animSrc2 : token.animSrc;
|
||||||
|
srcSet = token.big ? token.animSrcSet2 : token.animSrcSet;
|
||||||
|
} else {
|
||||||
|
src = token.big ? token.src2 : token.src;
|
||||||
|
srcSet = token.big ? token.srcSet2 : token.srcSet;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( hover && token.animSrc ) {
|
||||||
|
normalSrc = src;
|
||||||
|
normalSrcSet = srcSet;
|
||||||
|
hoverSrc = token.big ? token.animSrc2 : token.animSrc;
|
||||||
|
hoverSrcSet = token.big ? token.animSrcSet2 : token.animSrcSet;
|
||||||
|
}
|
||||||
|
|
||||||
const mods = token.modifiers || [], ml = mods.length,
|
const mods = token.modifiers || [], ml = mods.length,
|
||||||
emote = (<img
|
emote = (<img
|
||||||
class={`${EMOTE_CLASS} ffz--pointer-events ffz-tooltip${token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`}
|
class={`${EMOTE_CLASS} ffz--pointer-events ffz-tooltip${hoverSrc ? ' ffz-hover-emote' : ''}${token.provider === 'ffz' ? ' ffz-emote' : token.provider === 'emoji' ? ' ffz-emoji' : ''}`}
|
||||||
src={token.big && token.src2 || token.src}
|
src={src}
|
||||||
srcSet={token.big && token.srcSet2 || token.srcSet}
|
srcSet={srcSet}
|
||||||
height={(token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined}
|
height={(token.big && ! token.can_big && token.height) ? `${token.height * 2}px` : undefined}
|
||||||
alt={token.text}
|
alt={token.text}
|
||||||
data-tooltip-type="emote"
|
data-tooltip-type="emote"
|
||||||
|
@ -1160,6 +1200,10 @@ export const AddonEmotes = {
|
||||||
data-set={token.set}
|
data-set={token.set}
|
||||||
data-code={token.code}
|
data-code={token.code}
|
||||||
data-variant={token.variant}
|
data-variant={token.variant}
|
||||||
|
data-normal-src={normalSrc}
|
||||||
|
data-normal-src-set={normalSrcSet}
|
||||||
|
data-hover-src={hoverSrc}
|
||||||
|
data-hover-src-set={hoverSrcSet}
|
||||||
data-modifiers={ml ? mods.map(x => x.id).join(' ') : null}
|
data-modifiers={ml ? mods.map(x => x.id).join(' ') : null}
|
||||||
data-modifier-info={ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null}
|
data-modifier-info={ml ? JSON.stringify(mods.map(x => [x.set, x.id])) : null}
|
||||||
onClick={this.emotes.handleClick}
|
onClick={this.emotes.handleClick}
|
||||||
|
@ -1265,11 +1309,20 @@ export const AddonEmotes = {
|
||||||
'emote.owner', 'By: {owner}',
|
'emote.owner', 'By: {owner}',
|
||||||
{owner: emote.owner.display_name});
|
{owner: emote.owner.display_name});
|
||||||
|
|
||||||
|
const anim = this.context.get('tooltip.emote-images.animated');
|
||||||
|
if ( anim && emote.animated?.[1] ) {
|
||||||
|
if ( emote.animated[4] )
|
||||||
|
preview = emote.animated[4];
|
||||||
|
else if ( emote.animated[2] )
|
||||||
|
preview = emote.animated[2];
|
||||||
|
|
||||||
|
} else {
|
||||||
if ( emote.urls[4] )
|
if ( emote.urls[4] )
|
||||||
preview = emote.urls[4];
|
preview = emote.urls[4];
|
||||||
else if ( emote.urls[2] )
|
else if ( emote.urls[2] )
|
||||||
preview = emote.urls[2];
|
preview = emote.urls[2];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else if ( provider === 'emoji' ) {
|
} else if ( provider === 'emoji' ) {
|
||||||
const emoji = this.emoji.emoji[ds.code],
|
const emoji = this.emoji.emoji[ds.code],
|
||||||
|
@ -1341,6 +1394,7 @@ export const AddonEmotes = {
|
||||||
return tokens;
|
return tokens;
|
||||||
|
|
||||||
const big = this.context.get('chat.emotes.2x'),
|
const big = this.context.get('chat.emotes.2x'),
|
||||||
|
anim = this.context.get('chat.emotes.animated'),
|
||||||
out = [];
|
out = [];
|
||||||
|
|
||||||
let last_token, emote;
|
let last_token, emote;
|
||||||
|
@ -1391,7 +1445,8 @@ export const AddonEmotes = {
|
||||||
|
|
||||||
const t = Object.assign({
|
const t = Object.assign({
|
||||||
modifiers: [],
|
modifiers: [],
|
||||||
big
|
big,
|
||||||
|
anim
|
||||||
}, emote.token);
|
}, emote.token);
|
||||||
out.push(t);
|
out.push(t);
|
||||||
last_token = t;
|
last_token = t;
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default class Line extends Module {
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
this.chat.context.on('changed:chat.emotes.2x', this.updateLines, this);
|
this.chat.context.on('changed:chat.emotes.2x', this.updateLines, this);
|
||||||
|
this.chat.context.on('changed:chat.emotes.animated', this.updateLines, this);
|
||||||
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
|
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
|
||||||
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
|
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
|
||||||
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);
|
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);
|
||||||
|
@ -64,6 +65,7 @@ export default class Line extends Module {
|
||||||
|
|
||||||
|
|
||||||
const msg = t.standardizeMessage(this.props.node, this.props.video),
|
const msg = t.standardizeMessage(this.props.node, this.props.video),
|
||||||
|
anim_hover = t.chat.context.get('chat.emotes.animated') === 2,
|
||||||
is_action = msg.is_action,
|
is_action = msg.is_action,
|
||||||
user = msg.user,
|
user = msg.user,
|
||||||
color = t.parent.colors.process(user.color),
|
color = t.parent.colors.process(user.color),
|
||||||
|
@ -72,23 +74,26 @@ export default class Line extends Module {
|
||||||
|
|
||||||
const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u);
|
const tokens = msg.ffz_tokens = msg.ffz_tokens || t.chat.tokenizeMessage(msg, u);
|
||||||
|
|
||||||
return (<div
|
return (<div class="tw-mg-b-1" style={{marginBottom:'0 !important'}}>
|
||||||
|
<div
|
||||||
data-a-target="tw-animation-target"
|
data-a-target="tw-animation-target"
|
||||||
class="ffz--clip-chat-line tw-animation tw-animation--animate tw-animation--duration-short tw-animation--fill-mode-both tw-animation--slide-in-bottom tw-animation--timing-ease"
|
class="ffz--clip-chat-line tw-animation tw-animation--animate tw-animation--duration-short tw-animation--fill-mode-both tw-animation--slide-in-bottom tw-animation--timing-ease"
|
||||||
data-room-id={msg.roomID}
|
data-room-id={msg.roomID}
|
||||||
data-room={msg.roomLogin}
|
data-room={msg.roomLogin}
|
||||||
data-user-id={user.id}
|
data-user-id={user.id}
|
||||||
data-user={user.login}
|
data-user={user.login}
|
||||||
|
onMouseOver={anim_hover ? t.chat.emotes.animHover : null}
|
||||||
|
onMouseOut={anim_hover ? t.chat.emotes.animLeave : null}
|
||||||
>
|
>
|
||||||
<span class="chat-line__message--badges">{
|
<span class="chat-line__message--badges">{
|
||||||
t.chat.badges.render(msg, createElement)
|
t.chat.badges.render(msg, createElement)
|
||||||
}</span>
|
}</span>
|
||||||
<a
|
<a
|
||||||
class="clip-chat__message-author tw-font-size-5 tw-strong tw-link notranslate"
|
class="clip-chat__message-author tw-font-size-5 tw-link notranslate"
|
||||||
href={`https://www.twitch.tv/${user.login}/clips`}
|
href={`https://www.twitch.tv/${user.login}/clips`}
|
||||||
style={{color}}
|
style={{color}}
|
||||||
>
|
>
|
||||||
<span class="chat-author__display_name">{ user.displayName }</span>
|
<span class="tw-strong chat-author__display_name">{ user.displayName }</span>
|
||||||
{user.isIntl && <span class="chat-author__intl-login"> ({user.login}) </span>}
|
{user.isIntl && <span class="chat-author__intl-login"> ({user.login}) </span>}
|
||||||
</a>
|
</a>
|
||||||
<div class="tw-inline-block tw-mg-r-05">{
|
<div class="tw-inline-block tw-mg-r-05">{
|
||||||
|
@ -97,6 +102,7 @@ export default class Line extends Module {
|
||||||
<span class="message" style={{color: is_action ? color : null}}>{
|
<span class="message" style={{color: is_action ? color : null}}>{
|
||||||
t.chat.renderTokens(tokens, createElement)
|
t.chat.renderTokens(tokens, createElement)
|
||||||
}</span>
|
}</span>
|
||||||
|
</div>
|
||||||
</div>);
|
</div>);
|
||||||
|
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
|
|
@ -93,6 +93,38 @@ const EMOTE_SORTERS = [
|
||||||
if ( a.id > b.id ) return -1;
|
if ( a.id > b.id ) return -1;
|
||||||
if ( a.id < b.id ) return 1;
|
if ( a.id < b.id ) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
|
},
|
||||||
|
function native_asc(a, b) {
|
||||||
|
if ( a.order || b.order ) {
|
||||||
|
if ( a.order && ! b.order ) return -1;
|
||||||
|
if ( b.order && ! a.order ) return 1;
|
||||||
|
|
||||||
|
if ( a.order < b.order ) return -1;
|
||||||
|
if ( a.order > b.order ) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( COLLATOR )
|
||||||
|
return COLLATOR.compare(a.id, b.id);
|
||||||
|
|
||||||
|
if ( a.id < b.id ) return -1;
|
||||||
|
if ( a.id > b.id ) return 1;
|
||||||
|
return 0;
|
||||||
|
},
|
||||||
|
function native_desc(a, b) {
|
||||||
|
if ( a.order || b.order ) {
|
||||||
|
if ( a.order && ! b.order ) return 1;
|
||||||
|
if ( b.order && ! a.order ) return -1;
|
||||||
|
|
||||||
|
if ( a.order < b.order ) return 1;
|
||||||
|
if ( a.order > b.order ) return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( COLLATOR )
|
||||||
|
return COLLATOR.compare(a.id, b.id);
|
||||||
|
|
||||||
|
if ( a.id < b.id ) return 1;
|
||||||
|
if ( a.id > b.id ) return -1;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -167,6 +199,15 @@ export default class EmoteMenu extends Module {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.add('chat.emote-menu.show-quick-nav', {
|
||||||
|
default: false,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Emote Menu >> Appearance',
|
||||||
|
title: 'Show a quick navigation bar along the side of the menu.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('chat.emote-menu.show-heading', {
|
this.settings.add('chat.emote-menu.show-heading', {
|
||||||
default: 1,
|
default: 1,
|
||||||
ui: {
|
ui: {
|
||||||
|
@ -227,12 +268,14 @@ export default class EmoteMenu extends Module {
|
||||||
|
|
||||||
|
|
||||||
this.settings.add('chat.emote-menu.sort-emotes', {
|
this.settings.add('chat.emote-menu.sort-emotes', {
|
||||||
default: 0,
|
default: 4,
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Chat > Emote Menu >> Sorting',
|
path: 'Chat > Emote Menu >> Sorting',
|
||||||
title: 'Sort Emotes By',
|
title: 'Sort Emotes By',
|
||||||
component: 'setting-select-box',
|
component: 'setting-select-box',
|
||||||
data: [
|
data: [
|
||||||
|
{value: 4, title: 'Native Order, Ascending'},
|
||||||
|
{value: 5, title: 'Native Order, Descending'},
|
||||||
{value: 0, title: 'Order Added (ID), Ascending'},
|
{value: 0, title: 'Order Added (ID), Ascending'},
|
||||||
{value: 1, title: 'Order Added (ID), Descending'},
|
{value: 1, title: 'Order Added (ID), Descending'},
|
||||||
{value: 2, title: 'Name, Ascending'},
|
{value: 2, title: 'Name, Ascending'},
|
||||||
|
@ -275,7 +318,7 @@ export default class EmoteMenu extends Module {
|
||||||
this.chat.context.on('changed:chat.emote-menu.enabled', () =>
|
this.chat.context.on('changed:chat.emote-menu.enabled', () =>
|
||||||
this.EmoteMenu.forceUpdate());
|
this.EmoteMenu.forceUpdate());
|
||||||
|
|
||||||
const fup = () => this.MenuWrapper.forceUpdate();
|
//const fup = () => this.MenuWrapper.forceUpdate();
|
||||||
const rebuild = () => {
|
const rebuild = () => {
|
||||||
for(const inst of this.MenuWrapper.instances)
|
for(const inst of this.MenuWrapper.instances)
|
||||||
inst.rebuildData();
|
inst.rebuildData();
|
||||||
|
@ -284,10 +327,10 @@ export default class EmoteMenu extends Module {
|
||||||
this.chat.context.on('changed:chat.fix-bad-emotes', rebuild);
|
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-emotes', rebuild);
|
||||||
this.chat.context.on('changed:chat.emote-menu.sort-tiers-last', rebuild);
|
this.chat.context.on('changed:chat.emote-menu.sort-tiers-last', rebuild);
|
||||||
this.chat.context.on('changed:chat.emote-menu.show-heading', fup);
|
//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.show-search', fup);
|
||||||
this.chat.context.on('changed:chat.emote-menu.reduced-padding', 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.emote-menu.combine-tabs', fup);
|
||||||
|
|
||||||
this.chat.context.on('changed:chat.emoji.style', this.updateEmojiVariables, this);
|
this.chat.context.on('changed:chat.emoji.style', this.updateEmojiVariables, this);
|
||||||
|
|
||||||
|
@ -380,6 +423,40 @@ export default class EmoteMenu extends Module {
|
||||||
React = this.web_munch.getModule('react'),
|
React = this.web_munch.getModule('react'),
|
||||||
createElement = React && React.createElement;
|
createElement = React && React.createElement;
|
||||||
|
|
||||||
|
this.EmoteModifierPicker = class FFZEmoteModifierPicker extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.onClickOutside = () => this.props.close();
|
||||||
|
|
||||||
|
this.element = null;
|
||||||
|
this.saveRef = element => this.element = element;
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if ( this.element )
|
||||||
|
this._clicker = new ClickOutside(this.element, this.onClickOutside);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if ( this._clicker ) {
|
||||||
|
this._clicker.destroy();
|
||||||
|
this._clicker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (<div ref={this.saveRef} class="ffz--modifier-picker tw-absolute ffz-balloon tw-tooltip-down tw-tooltip--align-center ffz-balloon tw-block">
|
||||||
|
<div class="tw-border-b tw-border-l tw-border-r tw-border-t tw-border-radius-medium tw-c-background-base tw-elevation-1">
|
||||||
|
</div>
|
||||||
|
</div>)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.EmojiTonePicker = class FFZEmojiTonePicker extends React.Component {
|
this.EmojiTonePicker = class FFZEmojiTonePicker extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -446,7 +523,7 @@ export default class EmoteMenu extends Module {
|
||||||
|
|
||||||
const tones = Object.entries(emoji.variants).map(([tone, emoji]) => this.renderTone(emoji, tone));
|
const tones = Object.entries(emoji.variants).map(([tone, emoji]) => this.renderTone(emoji, tone));
|
||||||
|
|
||||||
return (<div class="tw-absolute ffz-balloon ffz-balloon--up ffz-balloon--right ffz-balloon tw-block">
|
return (<div class="tw-absolute ffz-balloon tw-tooltip--up tw-tooltip--align-right ffz-balloon tw-block">
|
||||||
<div class="tw-border-b tw-border-l tw-border-r tw-border-t tw-border-radius-medium tw-c-background-base tw-elevation-1">
|
<div class="tw-border-b tw-border-l tw-border-r tw-border-t tw-border-radius-medium tw-c-background-base tw-elevation-1">
|
||||||
{this.renderTone(emoji, null)}
|
{this.renderTone(emoji, null)}
|
||||||
{tones}
|
{tones}
|
||||||
|
@ -514,6 +591,7 @@ export default class EmoteMenu extends Module {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
active: false,
|
active: false,
|
||||||
|
open_menu: null,
|
||||||
activeEmote: -1,
|
activeEmote: -1,
|
||||||
hidden: hidden && props.data && hidden.includes(props.data.hide_key || 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),
|
collapsed: collapsed && props.data && collapsed.includes(props.data.key),
|
||||||
|
@ -523,6 +601,8 @@ export default class EmoteMenu extends Module {
|
||||||
this.keyHeading = this.keyHeading.bind(this);
|
this.keyHeading = this.keyHeading.bind(this);
|
||||||
this.clickHeading = this.clickHeading.bind(this);
|
this.clickHeading = this.clickHeading.bind(this);
|
||||||
this.clickEmote = this.clickEmote.bind(this);
|
this.clickEmote = this.clickEmote.bind(this);
|
||||||
|
this.contextEmote = this.contextEmote.bind(this);
|
||||||
|
this.closeEmoteModMenu = this.closeEmoteModMenu.bind(this);
|
||||||
|
|
||||||
this.mouseEnter = () => this.state.intersecting || this.setState({intersecting: true});
|
this.mouseEnter = () => this.state.intersecting || this.setState({intersecting: true});
|
||||||
|
|
||||||
|
@ -578,6 +658,29 @@ export default class EmoteMenu extends Module {
|
||||||
this.props.onClickToken(event.currentTarget.dataset.name)
|
this.props.onClickToken(event.currentTarget.dataset.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contextEmote(event) {
|
||||||
|
if ( event.ctrlKey || event.shiftKey )
|
||||||
|
return;
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const ds = event.currentTarget.dataset;
|
||||||
|
if ( ds.provider !== 'twitch' )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const modifiers = this.props.emote_modifiers[ds.id];
|
||||||
|
if ( Array.isArray(modifiers) && modifiers.length )
|
||||||
|
this.setState({
|
||||||
|
open_menu: ds.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
closeEmoteModMenu() {
|
||||||
|
this.setState({
|
||||||
|
open_menu: null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
keyHeading(event) {
|
keyHeading(event) {
|
||||||
if ( event.keyCode === KEYS.Enter || event.keyCode === KEYS.Space )
|
if ( event.keyCode === KEYS.Enter || event.keyCode === KEYS.Space )
|
||||||
this.clickHeading();
|
this.clickHeading();
|
||||||
|
@ -639,7 +742,7 @@ export default class EmoteMenu extends Module {
|
||||||
filtered = this.props.filtered,
|
filtered = this.props.filtered,
|
||||||
visibility = this.props.visibility_control;
|
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');
|
let show_heading = ! (data.is_favorites && ! this.props.combineTabs) && this.props.showHeading;
|
||||||
if ( show_heading === 2 )
|
if ( show_heading === 2 )
|
||||||
show_heading = ! filtered;
|
show_heading = ! filtered;
|
||||||
else
|
else
|
||||||
|
@ -758,8 +861,21 @@ export default class EmoteMenu extends Module {
|
||||||
return <span key={emote.id} class="emote-picker__placeholder" style={{width: `${emote.width||28}px`, height: `${emote.height||28}px`}} />;
|
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,
|
const visibility = this.props.visibility_control,
|
||||||
|
modifiers = this.props.emote_modifiers[emote.id],
|
||||||
|
has_modifiers = Array.isArray(modifiers) && modifiers.length > 0,
|
||||||
|
has_menu = has_modifiers && this.state.open_menu == emote.id,
|
||||||
|
animated = this.props.animated,
|
||||||
hidden = visibility && emote.hidden;
|
hidden = visibility && emote.hidden;
|
||||||
|
|
||||||
|
let src, srcSet;
|
||||||
|
if ( animated && emote.animSrc ) {
|
||||||
|
src = emote.animSrc;
|
||||||
|
srcSet = emote.animSrcSet;
|
||||||
|
} else {
|
||||||
|
src = emote.src;
|
||||||
|
srcSet = emote.srcSet;
|
||||||
|
}
|
||||||
|
|
||||||
return (<button
|
return (<button
|
||||||
key={emote.id}
|
key={emote.id}
|
||||||
class={`ffz-tooltip emote-picker__emote-link${!visibility && locked ? ' locked' : ''}${hidden ? ' emote-hidden' : ''}`}
|
class={`ffz-tooltip emote-picker__emote-link${!visibility && locked ? ' locked' : ''}${hidden ? ' emote-hidden' : ''}`}
|
||||||
|
@ -775,20 +891,27 @@ export default class EmoteMenu extends Module {
|
||||||
data-locked={emote.locked}
|
data-locked={emote.locked}
|
||||||
data-sellout={sellout}
|
data-sellout={sellout}
|
||||||
onClick={(this.props.visibility_control || !emote.locked) && this.clickEmote}
|
onClick={(this.props.visibility_control || !emote.locked) && this.clickEmote}
|
||||||
|
onContextMenu={this.contextEmote}
|
||||||
>
|
>
|
||||||
<figure class="emote-picker__emote-figure">
|
<figure class="emote-picker__emote-figure">
|
||||||
<img
|
<img
|
||||||
class={`emote-picker__emote-image${emote.emoji ? ' ffz-emoji' : ''}`}
|
class={`emote-picker__emote-image${emote.emoji ? ' ffz-emoji' : ''}`}
|
||||||
src={emote.src}
|
src={src}
|
||||||
srcSet={emote.srcSet}
|
srcSet={srcSet}
|
||||||
alt={emote.name}
|
alt={emote.name}
|
||||||
height={emote.height ? `${emote.height}px` : null}
|
height={emote.height ? `${emote.height}px` : null}
|
||||||
width={emote.width ? `${emote.width}px` : null}
|
width={emote.width ? `${emote.width}px` : null}
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
|
{! visibility && has_modifiers && <div class="emote-button__options" />}
|
||||||
{! visibility && emote.favorite && <figure class="ffz--favorite ffz-i-star" />}
|
{! visibility && emote.favorite && <figure class="ffz--favorite ffz-i-star" />}
|
||||||
{! visibility && locked && <figure class="ffz-i-lock" />}
|
{! visibility && locked && <figure class="ffz-i-lock" />}
|
||||||
{hidden && <figure class="ffz-i-eye-off" />}
|
{hidden && <figure class="ffz-i-eye-off" />}
|
||||||
|
{has_menu && <t.EmoteModifierPicker
|
||||||
|
emote={emote}
|
||||||
|
modifiers={modifiers}
|
||||||
|
close={this.closeEmoteModMenu}
|
||||||
|
/>}
|
||||||
</button>)
|
</button>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -944,6 +1067,11 @@ export default class EmoteMenu extends Module {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.nav_ref = null;
|
||||||
|
this.saveNavRef = ref => {
|
||||||
|
this.nav_ref = ref;
|
||||||
|
}
|
||||||
|
|
||||||
this.ref = null;
|
this.ref = null;
|
||||||
this.saveScrollRef = ref => {
|
this.saveScrollRef = ref => {
|
||||||
this.ref = ref;
|
this.ref = ref;
|
||||||
|
@ -955,6 +1083,13 @@ export default class EmoteMenu extends Module {
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
tab: null,
|
tab: null,
|
||||||
|
active_nav: null,
|
||||||
|
quickNav: t.chat.context.get('chat.emote-menu.show-quick-nav'),
|
||||||
|
animated: t.chat.context.get('chat.emotes.animated'),
|
||||||
|
showHeading: t.chat.context.get('chat.emote-menu.show-heading'),
|
||||||
|
reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'),
|
||||||
|
combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'),
|
||||||
|
showSearch: t.chat.context.get('chat.emote-menu.show-search'),
|
||||||
tone: t.settings.provider.get('emoji-tone', null)
|
tone: t.settings.provider.get('emoji-tone', null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -986,6 +1121,7 @@ export default class EmoteMenu extends Module {
|
||||||
this.handleObserve = this.handleObserve.bind(this);
|
this.handleObserve = this.handleObserve.bind(this);
|
||||||
this.pickTone = this.pickTone.bind(this);
|
this.pickTone = this.pickTone.bind(this);
|
||||||
this.clickTab = this.clickTab.bind(this);
|
this.clickTab = this.clickTab.bind(this);
|
||||||
|
this.clickSideNav = this.clickSideNav.bind(this);
|
||||||
//this.clickRefresh = this.clickRefresh.bind(this);
|
//this.clickRefresh = this.clickRefresh.bind(this);
|
||||||
this.handleFilterChange = this.handleFilterChange.bind(this);
|
this.handleFilterChange = this.handleFilterChange.bind(this);
|
||||||
this.handleKeyDown = this.handleKeyDown.bind(this);
|
this.handleKeyDown = this.handleKeyDown.bind(this);
|
||||||
|
@ -1032,22 +1168,39 @@ export default class EmoteMenu extends Module {
|
||||||
this.observer = this._observed = null;
|
this.observer = this._observed = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleObserve(event) {
|
scrollNavIntoView() {
|
||||||
let changed = false;
|
requestAnimationFrame(() => {
|
||||||
|
const el = this.nav_ref?.querySelector?.(`button[data-key="${this.state.active_nav}"]`);
|
||||||
for(const entry of event) {
|
if ( el )
|
||||||
const inst = this.observing.get(entry.target);
|
el.scrollIntoView({block: 'nearest'});
|
||||||
if ( ! inst || inst.state.intersecting === entry.isIntersecting )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
changed = true;
|
|
||||||
inst.setState({
|
|
||||||
intersecting: entry.isIntersecting
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( changed )
|
handleObserve(event) {
|
||||||
|
let changed = false,
|
||||||
|
active = this.state.active_nav;
|
||||||
|
|
||||||
|
for(const entry of event) {
|
||||||
|
const inst = this.observing.get(entry.target),
|
||||||
|
intersecting = entry.isIntersecting;
|
||||||
|
if ( ! inst || inst.state.intersecting === intersecting )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
changed = true;
|
||||||
|
inst.setState({intersecting});
|
||||||
|
|
||||||
|
if ( intersecting )
|
||||||
|
active = inst.props?.data?.key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( changed ) {
|
||||||
requestAnimationFrame(clearTooltips);
|
requestAnimationFrame(clearTooltips);
|
||||||
|
|
||||||
|
if ( ! this.lock_active && active !== this.state.active_nav )
|
||||||
|
this.setState({
|
||||||
|
active_nav: active
|
||||||
|
}, () => this.scrollNavIntoView());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startObserving(element, inst) {
|
startObserving(element, inst) {
|
||||||
|
@ -1078,16 +1231,41 @@ export default class EmoteMenu extends Module {
|
||||||
if ( this.ref )
|
if ( this.ref )
|
||||||
this.createObserver();
|
this.createObserver();
|
||||||
|
|
||||||
|
t.chat.context.on('changed:chat.emotes.animated', this.updateSettingState, this);
|
||||||
|
t.chat.context.on('changed:chat.emote-menu.show-quick-nav', this.updateSettingState, this);
|
||||||
|
t.chat.context.on('changed:chat.emote-menu.reduced-padding', this.updateSettingState, this);
|
||||||
|
t.chat.context.on('changed:chat.emote-menu.show-heading', this.updateSettingState, this);
|
||||||
|
t.chat.context.on('changed:chat.emote-menu.combine-tabs', this.updateSettingState, this);
|
||||||
|
t.chat.context.on('changed:chat.emote-menu.show-search', this.updateSettingState, this);
|
||||||
|
|
||||||
window.ffz_menu = this;
|
window.ffz_menu = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
this.destroyObserver();
|
this.destroyObserver();
|
||||||
|
|
||||||
|
t.chat.context.off('changed:chat.emotes.animated', this.updateSettingState, this);
|
||||||
|
t.chat.context.off('changed:chat.emote-menu.show-quick-nav', this.updateSettingState, this);
|
||||||
|
t.chat.context.off('changed:chat.emote-menu.show-heading', this.updateSettingState, this);
|
||||||
|
t.chat.context.off('changed:chat.emote-menu.reduced-padding', this.updateSettingState, this);
|
||||||
|
t.chat.context.off('changed:chat.emote-menu.combine-tabs', this.updateSettingState, this);
|
||||||
|
t.chat.context.off('changed:chat.emote-menu.show-search', this.updateSettingState, this);
|
||||||
|
|
||||||
if ( window.ffz_menu === this )
|
if ( window.ffz_menu === this )
|
||||||
window.ffz_menu = null;
|
window.ffz_menu = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSettingState() {
|
||||||
|
this.setState({
|
||||||
|
quickNav: t.chat.context.get('chat.emote-menu.show-quick-nav'),
|
||||||
|
animated: t.chat.context.get('chat.emotes.animated'),
|
||||||
|
showHeading: t.chat.context.get('chat.emote-menu.show-heading'),
|
||||||
|
reducedPadding: t.chat.context.get('chat.emote-menu.reduced-padding'),
|
||||||
|
combineTabs: t.chat.context.get('chat.emote-menu.combine-tabs'),
|
||||||
|
showSearch: t.chat.context.get('chat.emote-menu.show-search')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pickTone(tone) {
|
pickTone(tone) {
|
||||||
tone = tone || null;
|
tone = tone || null;
|
||||||
t.settings.provider.set('emoji-tone', tone);
|
t.settings.provider.set('emoji-tone', tone);
|
||||||
|
@ -1100,9 +1278,22 @@ export default class EmoteMenu extends Module {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clickSideNav(event) {
|
||||||
|
const key = event.currentTarget.dataset.key;
|
||||||
|
const el = this.ref?.querySelector?.(`section[data-key="${key}"]`);
|
||||||
|
if ( el ) {
|
||||||
|
this.lock_active = true;
|
||||||
|
el.scrollIntoView();
|
||||||
|
this.setState({
|
||||||
|
active_nav: key
|
||||||
|
});
|
||||||
|
setTimeout(() => this.lock_active = false, 250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
clickTab(event) {
|
clickTab(event) {
|
||||||
const tab = event.currentTarget.dataset.tab;
|
const tab = event.currentTarget.dataset.tab;
|
||||||
if ( t.chat.context.get('chat.emote-menu.combine-tabs') ) {
|
if ( this.state.combineTabs ) {
|
||||||
let sets;
|
let sets;
|
||||||
switch(tab) {
|
switch(tab) {
|
||||||
case 'fav':
|
case 'fav':
|
||||||
|
@ -1427,6 +1618,7 @@ export default class EmoteMenu extends Module {
|
||||||
const state = Object.assign({}, old_state),
|
const state = Object.assign({}, old_state),
|
||||||
|
|
||||||
data = state.set_data || {},
|
data = state.set_data || {},
|
||||||
|
modifiers = state.emote_modifiers = {},
|
||||||
channel = state.channel_sets = [],
|
channel = state.channel_sets = [],
|
||||||
all = state.all_sets = [],
|
all = state.all_sets = [],
|
||||||
favorites = state.favorites = [];
|
favorites = state.favorites = [];
|
||||||
|
@ -1634,6 +1826,8 @@ export default class EmoteMenu extends Module {
|
||||||
section.bad = true;
|
section.bad = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let order = 0;
|
||||||
|
|
||||||
for(const emote of emote_set.emotes) {
|
for(const emote of emote_set.emotes) {
|
||||||
// Validate emotes, because apparently Twitch is handing
|
// Validate emotes, because apparently Twitch is handing
|
||||||
// out bad emote data.
|
// out bad emote data.
|
||||||
|
@ -1657,6 +1851,9 @@ export default class EmoteMenu extends Module {
|
||||||
srcSet = `${src} 1x, ${base}/2.0 2x`
|
srcSet = `${src} 1x, ${base}/2.0 2x`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
|
||||||
|
modifiers[id] = emote.modifiers;*/
|
||||||
|
|
||||||
const em = {
|
const em = {
|
||||||
provider: 'twitch',
|
provider: 'twitch',
|
||||||
id,
|
id,
|
||||||
|
@ -1664,6 +1861,7 @@ export default class EmoteMenu extends Module {
|
||||||
name,
|
name,
|
||||||
src,
|
src,
|
||||||
srcSet,
|
srcSet,
|
||||||
|
order: order++,
|
||||||
overridden: overridden ? mapped.id : null,
|
overridden: overridden ? mapped.id : null,
|
||||||
misc: ! chan,
|
misc: ! chan,
|
||||||
bits: is_bits,
|
bits: is_bits,
|
||||||
|
@ -1751,6 +1949,8 @@ export default class EmoteMenu extends Module {
|
||||||
else
|
else
|
||||||
section.all_locked = false;
|
section.all_locked = false;
|
||||||
|
|
||||||
|
let order = 0;
|
||||||
|
|
||||||
for(const emote of product.emotes) {
|
for(const emote of product.emotes) {
|
||||||
// Validate emotes, because apparently Twitch is handing
|
// Validate emotes, because apparently Twitch is handing
|
||||||
// out bad emote data.
|
// out bad emote data.
|
||||||
|
@ -1763,11 +1963,15 @@ export default class EmoteMenu extends Module {
|
||||||
seen = twitch_seen.has(id),
|
seen = twitch_seen.has(id),
|
||||||
is_fav = twitch_favorites.includes(id);
|
is_fav = twitch_favorites.includes(id);
|
||||||
|
|
||||||
|
/*if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
|
||||||
|
modifiers[id] = emote.modifiers;*/
|
||||||
|
|
||||||
const em = {
|
const em = {
|
||||||
provider: 'twitch',
|
provider: 'twitch',
|
||||||
id,
|
id,
|
||||||
set_id,
|
set_id,
|
||||||
name,
|
name,
|
||||||
|
order: order++,
|
||||||
locked: locked && ! seen,
|
locked: locked && ! seen,
|
||||||
src: `${base}/1.0`,
|
src: `${base}/1.0`,
|
||||||
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
|
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
|
||||||
|
@ -1791,6 +1995,7 @@ export default class EmoteMenu extends Module {
|
||||||
const seen_bits = new Set;
|
const seen_bits = new Set;
|
||||||
|
|
||||||
if ( Array.isArray(bits) ) {
|
if ( Array.isArray(bits) ) {
|
||||||
|
let order;
|
||||||
for(const emote of bits) {
|
for(const emote of bits) {
|
||||||
if ( ! emote || ! emote.id || ! emote.bitsBadgeTierSummary )
|
if ( ! emote || ! emote.id || ! emote.bitsBadgeTierSummary )
|
||||||
continue;
|
continue;
|
||||||
|
@ -1816,12 +2021,16 @@ export default class EmoteMenu extends Module {
|
||||||
const base = `${TWITCH_EMOTE_BASE}${id}`,
|
const base = `${TWITCH_EMOTE_BASE}${id}`,
|
||||||
is_fav = twitch_favorites.includes(id);
|
is_fav = twitch_favorites.includes(id);
|
||||||
|
|
||||||
|
/*if ( Array.isArray(emote.modifiers) && emote.modifiers.length )
|
||||||
|
modifiers[id] = emote.modifiers;*/
|
||||||
|
|
||||||
const em = {
|
const em = {
|
||||||
provider: 'twitch',
|
provider: 'twitch',
|
||||||
id,
|
id,
|
||||||
set_id,
|
set_id,
|
||||||
name: emote.token,
|
name: emote.token,
|
||||||
locked,
|
locked,
|
||||||
|
order: order++,
|
||||||
src: `${base}/1.0`,
|
src: `${base}/1.0`,
|
||||||
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
|
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
|
||||||
bits: true,
|
bits: true,
|
||||||
|
@ -1963,8 +2172,10 @@ export default class EmoteMenu extends Module {
|
||||||
provider: 'ffz',
|
provider: 'ffz',
|
||||||
id: emote.id,
|
id: emote.id,
|
||||||
set_id: emote_set.id,
|
set_id: emote_set.id,
|
||||||
src: emote.urls[1],
|
src: emote.src,
|
||||||
srcSet: emote.srcSet,
|
srcSet: emote.srcSet,
|
||||||
|
animSrc: emote.animSrc,
|
||||||
|
animSrcSet: emote.animSrcSet,
|
||||||
name: emote.name,
|
name: emote.name,
|
||||||
favorite: is_fav,
|
favorite: is_fav,
|
||||||
hidden: known_hidden.includes(emote.id),
|
hidden: known_hidden.includes(emote.id),
|
||||||
|
@ -2050,10 +2261,10 @@ export default class EmoteMenu extends Module {
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
const loading = this.state.loading || this.props.loading,
|
const loading = this.state.loading || this.props.loading,
|
||||||
padding = t.chat.context.get('chat.emote-menu.reduced-padding'),
|
padding = this.state.reducedPadding, //t.chat.context.get('chat.emote-menu.reduced-padding'),
|
||||||
no_tabs = t.chat.context.get('chat.emote-menu.combine-tabs');
|
no_tabs = this.state.combineTabs; //t.chat.context.get('chat.emote-menu.combine-tabs');
|
||||||
|
|
||||||
let tab, sets, is_emoji;
|
let tab, sets, is_emoji, is_favs;
|
||||||
|
|
||||||
if ( no_tabs ) {
|
if ( no_tabs ) {
|
||||||
sets = [
|
sets = [
|
||||||
|
@ -2069,6 +2280,7 @@ export default class EmoteMenu extends Module {
|
||||||
tab = 'all';
|
tab = 'all';
|
||||||
|
|
||||||
is_emoji = tab === 'emoji';
|
is_emoji = tab === 'emoji';
|
||||||
|
is_favs = tab === 'fav';
|
||||||
|
|
||||||
switch(tab) {
|
switch(tab) {
|
||||||
case 'fav':
|
case 'fav':
|
||||||
|
@ -2097,19 +2309,25 @@ export default class EmoteMenu extends Module {
|
||||||
role="dialog"
|
role="dialog"
|
||||||
>
|
>
|
||||||
<div class="emote-picker">
|
<div class="emote-picker">
|
||||||
|
<div class="tw-flex">
|
||||||
<div
|
<div
|
||||||
class="emote-picker__tab-content scrollable-area"
|
class="emote-picker__tab-content tw-full-width scrollable-area scrollable-area--suppress-scroll-x"
|
||||||
data-test-selector="scrollable-area-wrapper"
|
data-test-selector="scrollable-area-wrapper"
|
||||||
data-simplebar
|
data-simplebar
|
||||||
>
|
>
|
||||||
<div ref={this.saveScrollRef} class="simplebar-scroll-content">
|
<div ref={this.saveScrollRef} class="simplebar-scroll-content">
|
||||||
<div class="simplebar-content">
|
<div class="simplebar-content">
|
||||||
{loading && this.renderLoading()}
|
{loading && this.renderLoading()}
|
||||||
{!loading && sets && sets.map(data => data && (! visibility || (! data.emoji && ! data.is_favorites)) && createElement(
|
{!loading && sets && sets.map((data,idx) => data && (! visibility || (! data.emoji && ! data.is_favorites)) && createElement(
|
||||||
data.emoji ? t.EmojiSection : t.MenuSection,
|
data.emoji ? t.EmojiSection : t.MenuSection,
|
||||||
{
|
{
|
||||||
key: data.key,
|
key: data.key,
|
||||||
|
idx,
|
||||||
data,
|
data,
|
||||||
|
emote_modifiers: this.state.emote_modifiers,
|
||||||
|
animated: this.state.animated,
|
||||||
|
combineTabs: this.state.combineTabs,
|
||||||
|
showHeading: this.state.showHeading,
|
||||||
filtered: this.state.filtered,
|
filtered: this.state.filtered,
|
||||||
visibility_control: visibility,
|
visibility_control: visibility,
|
||||||
onClickToken: this.props.onClickToken,
|
onClickToken: this.props.onClickToken,
|
||||||
|
@ -2123,8 +2341,57 @@ export default class EmoteMenu extends Module {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{(! loading && this.state.quickNav && ! is_favs) && (<div class="emote-picker__nav_content tw-block tw-border-radius-none tw-c-background-alt-2">
|
||||||
|
<div
|
||||||
|
class="emote-picker__nav-content-overflow scrollable-area scrollable-area--suppress-scroll-x"
|
||||||
|
data-test-selector="scrollable-area-wrapper"
|
||||||
|
data-simplebar
|
||||||
|
>
|
||||||
|
<div ref={this.saveNavRef} class="simplebar-scroll-content">
|
||||||
|
<div class="simplebar-content">
|
||||||
|
{!loading && sets && sets.map(data => {
|
||||||
|
if ( ! data || (visibility && (data.is_favorites || data.emoji)) )
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const active = this.state.active_nav === data.key;
|
||||||
|
|
||||||
|
return (<button
|
||||||
|
key={data.key}
|
||||||
|
class={`${active ? 'emote-picker-tab-item-wrapper__active ' : ''}${padding ? 'tw-mg-y-05' : 'tw-mg-y-1'} tw-c-text-inherit tw-interactable ffz-interactive ffz-interactable--hover-enabled ffz-interactable--default tw-block tw-full-width ffz-tooltip ffz-tooltip--no-mouse`}
|
||||||
|
data-key={data.key}
|
||||||
|
data-title={`${data.i18n ? t.i18n.t(data.i18n, data.title) : data.title}\n${data.source_i18n ? t.i18n.t(data.source_i18n, data.source) : data.source}`}
|
||||||
|
data-tooltip-side="left"
|
||||||
|
onClick={this.clickSideNav}
|
||||||
|
>
|
||||||
|
<div class={`tw-align-items-center tw-flex tw-justify-content-center ${padding ? '' : 'tw-pd-x-05 '}tw-pd-y-05${active ? ' emote-picker-tab-item-avatar__active tw-c-text-link' : ''}`}>
|
||||||
|
{data.image ? <figure class="ffz-avatar ffz-avatar--size-20">
|
||||||
|
<img
|
||||||
|
class="tw-block tw-border-radius-rounded tw-img tw-image-avatar"
|
||||||
|
src={data.image}
|
||||||
|
srcSet={data.image_set}
|
||||||
|
/>
|
||||||
|
</figure> : <figure class={`ffz-emote-picker--nav-icon ffz-i-${data.icon || 'zreknarf'}`} />}
|
||||||
|
</div>
|
||||||
|
</button>);
|
||||||
|
})}
|
||||||
|
{no_tabs && <div class="tw-mg-y-1 tw-mg-x-05 tw-border-t" />}
|
||||||
|
{no_tabs && (<button
|
||||||
|
class="tw-mg-y-1 tw-c-text-inherit tw-interactable ffz-interactive ffz-interactable--hover-enabled ffz-interactable--default tw-block tw-full-width ffz-tooltip ffz-tooltip--no-mouse"
|
||||||
|
data-title={t.i18n.t('emote-menu.settings', 'Open Settings')}
|
||||||
|
data-tooltip-side="left"
|
||||||
|
onClick={this.clickSettings}
|
||||||
|
>
|
||||||
|
<div class={`tw-align-items-center tw-flex tw-justify-content-center ${padding ? '' : 'tw-pd-x-05 '}tw-pd-y-05`}>
|
||||||
|
<figure class="ffz-emote-picker--nav-icon ffz-i-cog" />
|
||||||
|
</div>
|
||||||
|
</button>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
</div>
|
||||||
<div class="emote-picker__controls-container tw-relative">
|
<div class="emote-picker__controls-container tw-relative">
|
||||||
{(is_emoji || t.chat.context.get('chat.emote-menu.show-search')) && (<div class="tw-border-t tw-pd-1">
|
{(is_emoji || this.state.showSearch) && (<div class="tw-border-t tw-pd-1">
|
||||||
<div class="tw-flex">
|
<div class="tw-flex">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -2141,6 +2408,11 @@ export default class EmoteMenu extends Module {
|
||||||
onChange={this.handleFilterChange}
|
onChange={this.handleFilterChange}
|
||||||
onKeyDown={this.handleKeyDown}
|
onKeyDown={this.handleKeyDown}
|
||||||
/>
|
/>
|
||||||
|
{(no_tabs || is_emoji) && ! visibility && this.state.has_emoji_tab && <t.EmojiTonePicker
|
||||||
|
tone={this.state.tone}
|
||||||
|
choices={this.state.tone_emoji}
|
||||||
|
pickTone={this.pickTone}
|
||||||
|
/>}
|
||||||
{(no_tabs || ! is_emoji) && <div class="tw-relative tw-tooltip__container tw-mg-l-1">
|
{(no_tabs || ! is_emoji) && <div class="tw-relative tw-tooltip__container tw-mg-l-1">
|
||||||
<button
|
<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 ffz-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative${this.state.visibility_control ? ' ffz-core-button--primary' : ' tw-button-icon'}`}
|
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 ffz-core-button tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative${this.state.visibility_control ? ' ffz-core-button--primary' : ' tw-button-icon'}`}
|
||||||
|
@ -2160,14 +2432,9 @@ export default class EmoteMenu extends Module {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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}
|
|
||||||
/>}
|
|
||||||
</div>
|
</div>
|
||||||
</div>)}
|
</div>)}
|
||||||
<div class="emote-picker__tab-nav-container tw-flex tw-border-t tw-c-background-alt">
|
{(no_tabs && this.state.quickNav) ? null : (<div class="emote-picker__tab-nav-container tw-flex tw-border-t tw-c-background-alt">
|
||||||
{! visibility && <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
|
<button
|
||||||
class={`ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'fav' ? ' ffz-interactable--selected' : ''}`}
|
class={`ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'fav' ? ' ffz-interactable--selected' : ''}`}
|
||||||
|
@ -2237,7 +2504,7 @@ export default class EmoteMenu extends Module {
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -61,6 +61,7 @@ export default class ChatLine extends Module {
|
||||||
this.on('i18n:update', this.updateLines, this);
|
this.on('i18n:update', this.updateLines, this);
|
||||||
|
|
||||||
this.chat.context.on('changed:chat.emotes.2x', this.updateLines, this);
|
this.chat.context.on('changed:chat.emotes.2x', this.updateLines, this);
|
||||||
|
this.chat.context.on('changed:chat.emotes.animated', this.updateLines, this);
|
||||||
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
|
this.chat.context.on('changed:chat.emoji.style', this.updateLines, this);
|
||||||
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
|
this.chat.context.on('changed:chat.bits.stack', this.updateLines, this);
|
||||||
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);
|
this.chat.context.on('changed:chat.badges.style', this.updateLines, this);
|
||||||
|
@ -337,6 +338,7 @@ export default class ChatLine extends Module {
|
||||||
const types = t.parent.message_types || {},
|
const types = t.parent.message_types || {},
|
||||||
deleted_count = this.props.deletedCount,
|
deleted_count = this.props.deletedCount,
|
||||||
reply_mode = t.chat.context.get('chat.replies.style'),
|
reply_mode = t.chat.context.get('chat.replies.style'),
|
||||||
|
anim_hover = t.chat.context.get('chat.emotes.animated') === 2,
|
||||||
override_mode = t.chat.context.get('chat.filtering.display-deleted'),
|
override_mode = t.chat.context.get('chat.filtering.display-deleted'),
|
||||||
|
|
||||||
msg = t.chat.standardizeMessage(this.props.message),
|
msg = t.chat.standardizeMessage(this.props.message),
|
||||||
|
@ -892,6 +894,8 @@ other {# messages were deleted by a moderator.}
|
||||||
'data-room': room,
|
'data-room': room,
|
||||||
'data-user-id': user.userID,
|
'data-user-id': user.userID,
|
||||||
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
'data-user': user.userLogin && user.userLogin.toLowerCase(),
|
||||||
|
onMouseOver: anim_hover ? t.chat.emotes.animHover : null,
|
||||||
|
onMouseOut: anim_hover ? t.chat.emotes.animLeave : null
|
||||||
}, out);
|
}, out);
|
||||||
|
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
|
|
@ -117,10 +117,15 @@
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emote-picker__nav-content-overflow,
|
||||||
.emote-picker__tab-content {
|
.emote-picker__tab-content {
|
||||||
max-height: calc(var(--ffz-chat-height) - 26rem);
|
max-height: calc(var(--ffz-chat-height) - 26rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.emote-picker__nav-content-overflow {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
&.right-column--theatre {
|
&.right-column--theatre {
|
||||||
.ffz--portrait-invert & {
|
.ffz--portrait-invert & {
|
||||||
bottom: unset !important;
|
bottom: unset !important;
|
||||||
|
@ -132,6 +137,7 @@
|
||||||
|
|
||||||
height: calc(100vh - var(--ffz-theater-height)) !important;
|
height: calc(100vh - var(--ffz-theater-height)) !important;
|
||||||
|
|
||||||
|
.emote-picker__nav-content-overflow,
|
||||||
.emote-picker__tab-content {
|
.emote-picker__tab-content {
|
||||||
max-height: calc(calc(100vh - var(--ffz-theater-height)) - 26rem);
|
max-height: calc(calc(100vh - var(--ffz-theater-height)) - 26rem);
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,6 +269,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz-emote-picker--nav-icon {
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1em;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
height: 2rem;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
width: unset !important;
|
||||||
|
font-size: unset !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ffz--emote-picker {
|
.ffz--emote-picker {
|
||||||
section:not(.filtered) heading {
|
section:not(.filtered) heading {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -311,6 +330,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-height: 750px) {
|
@media only screen and (max-height: 750px) {
|
||||||
|
.emote-picker__nav-content-overflow {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emote-picker__nav-content-overflow,
|
||||||
.emote-picker__tab-content {
|
.emote-picker__tab-content {
|
||||||
#root & {
|
#root & {
|
||||||
max-height: calc(100vh - 31rem);
|
max-height: calc(100vh - 31rem);
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
.clmgr-table__row,
|
.clmgr-table__row,
|
||||||
.sunlight-expanded-nav-drop-down-menu-layout__scrollable-area,
|
.sunlight-expanded-nav-drop-down-menu-layout__scrollable-area,
|
||||||
.stream-manager--page-view .mosaic-window-body,
|
.stream-manager--page-view .mosaic-window-body,
|
||||||
|
.emote-grid-section__header-title,
|
||||||
.ach-card,
|
.ach-card,
|
||||||
.ach-card--expanded .ach-card__inner,
|
.ach-card--expanded .ach-card__inner,
|
||||||
.room-upsell,
|
.room-upsell,
|
||||||
|
|
|
@ -6,18 +6,20 @@
|
||||||
|
|
||||||
z-index: 99999999;
|
z-index: 99999999;
|
||||||
|
|
||||||
height: 50vh;
|
--width: #{"min(75vw, 128rem)"};
|
||||||
width: 50vw;
|
--height: #{"min(75vh, calc(0.75 * var(--width)))"};
|
||||||
|
|
||||||
|
top: #{"calc(calc(100vh - var(--height)) / 2)"};
|
||||||
|
left: #{"calc(calc(100vw - var(--width)) / 2)"};
|
||||||
|
|
||||||
min-width: 64rem;
|
min-width: 64rem;
|
||||||
min-height: 30rem;
|
min-height: 30rem;
|
||||||
|
|
||||||
--width: #{"min(75vw, 128rem)"};
|
|
||||||
width: 75vw;
|
width: 75vw;
|
||||||
width: var(--width);
|
width: var(--width);
|
||||||
|
|
||||||
height: 50vh;
|
height: 50vh;
|
||||||
height: #{"min(75vh, calc(0.75 * var(--width)))"};
|
height: var(--height);
|
||||||
|
|
||||||
> header {
|
> header {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
|
|
@ -221,12 +221,20 @@
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--error {
|
||||||
|
box-shadow: var(--shadow-input-error);
|
||||||
|
|
||||||
|
&,&:focus {
|
||||||
|
border: var(--border-width-input) solid var(--color-border-input-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ffz-textarea--error:focus {
|
&:focus {
|
||||||
box-shadow: var(--shadow-input-error-focus);
|
box-shadow: var(--shadow-input-error-focus);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ffz-textarea--no-resize {
|
&--no-resize {
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue