mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-25 20:18:31 +00:00
4.20.3
* Added: Setting to automatically open chat when navigating to an offline channel. * Added: Support for multi-month gift sub messages. * Fixed: The FFZ Control Center, when maximized, disappearing beneath chat on channel pages. (Closes #833) * Fixed: Metadata appearing in theater mode when hovering over the player. * Fixed: Automatic theater mode enabling theater mode when navigating back to a channel's landing page.
This commit is contained in:
parent
339b6fdfbb
commit
b11e4edf2b
10 changed files with 156 additions and 29 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.20.2",
|
||||
"version": "4.20.3",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
|
@ -164,18 +164,27 @@ export default class MainMenu extends Module {
|
|||
this.on('i18n:update', this.scheduleUpdate, this);
|
||||
|
||||
this.dialog.on('show', () => {
|
||||
if ( this.dialog.maximized )
|
||||
this.runFix(1);
|
||||
|
||||
this.showing = true;
|
||||
this.opened = true;
|
||||
this.updateButtonUnseen();
|
||||
this.emit('show')
|
||||
});
|
||||
this.dialog.on('hide', () => {
|
||||
if ( this.dialog.maximized )
|
||||
this.runFix(-1);
|
||||
|
||||
this.showing = false;
|
||||
this.emit('hide');
|
||||
this.destroyDialog();
|
||||
});
|
||||
|
||||
this.dialog.on('resize', () => {
|
||||
if ( this.dialog.visible )
|
||||
this.runFix(this.dialog.maximized ? 1 : -1);
|
||||
|
||||
if ( this._vue )
|
||||
this._vue.$children[0].maximized = this.dialog.maximized
|
||||
});
|
||||
|
@ -189,6 +198,12 @@ export default class MainMenu extends Module {
|
|||
this.off('site.menu_button:clicked', this.dialog.toggleVisible, this.dialog);
|
||||
}
|
||||
|
||||
runFix(amount) {
|
||||
this.settings.updateContext({
|
||||
force_chat_fix: (this.settings.get('context.force_chat_fix') || 0) + amount
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
requestPage(page) {
|
||||
const vue = get('_vue.$children.0', this);
|
||||
|
|
|
@ -21,12 +21,22 @@ export default class Channel extends Module {
|
|||
this.inject('i18n');
|
||||
this.inject('settings');
|
||||
this.inject('site.css_tweaks');
|
||||
this.inject('site.fine');
|
||||
this.inject('site.elemental');
|
||||
this.inject('site.fine');
|
||||
this.inject('site.router');
|
||||
this.inject('site.twitch_data');
|
||||
this.inject('metadata');
|
||||
this.inject('socket');
|
||||
|
||||
this.settings.add('channel.auto-click-chat', {
|
||||
default: false,
|
||||
ui: {
|
||||
path: 'Channel > Behavior >> General',
|
||||
title: 'Automatically open chat when opening an offline channel page.',
|
||||
component: 'setting-check-box'
|
||||
}
|
||||
});
|
||||
|
||||
/*this.SideNav = this.elemental.define(
|
||||
'side-nav', '.side-bar-contents .side-nav-section:first-child',
|
||||
null,
|
||||
|
@ -61,6 +71,20 @@ export default class Channel extends Module {
|
|||
this.InfoBar.on('mutate', this.updateBar, this);
|
||||
this.InfoBar.on('unmount', this.removeBar, this);
|
||||
this.InfoBar.each(el => this.updateBar(el));
|
||||
|
||||
this.router.on(':route', route => {
|
||||
if ( route?.name === 'user' )
|
||||
setTimeout(this.maybeClickChat.bind(this), 1000);
|
||||
}, this);
|
||||
this.maybeClickChat();
|
||||
}
|
||||
|
||||
maybeClickChat() {
|
||||
if ( this.settings.get('channel.auto-click-chat') && this.router.current_name === 'user' ) {
|
||||
const el = document.querySelector('a[data-a-target="channel-home-tab-Chat"]');
|
||||
if ( el )
|
||||
el.click();
|
||||
}
|
||||
}
|
||||
|
||||
/*updateHidden(el) { // eslint-disable-line class-methods-use-this
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
// ============================================================================
|
||||
|
||||
import {has, get, once, maybe_call, set_equals} from 'utilities/object';
|
||||
import {TWITCH_GLOBAL_SETS, EmoteTypes, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants';
|
||||
import {TWITCH_GLOBAL_SETS, EmoteTypes, TWITCH_POINTS_SETS, TWITCH_PRIME_SETS, WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS, KEYS} from 'utilities/constants';
|
||||
import {ClickOutside} from 'utilities/dom';
|
||||
|
||||
import Twilight from 'site';
|
||||
|
@ -506,11 +506,14 @@ export default class EmoteMenu extends Module {
|
|||
hidden = storage.get('emote-menu.hidden-sets');
|
||||
|
||||
this.state = {
|
||||
active: false,
|
||||
activeEmote: -1,
|
||||
hidden: hidden && props.data && hidden.includes(props.data.hide_key || props.data.key),
|
||||
collapsed: collapsed && props.data && collapsed.includes(props.data.key),
|
||||
intersecting: window.IntersectionObserver ? false : true
|
||||
}
|
||||
|
||||
this.keyHeading = this.keyHeading.bind(this);
|
||||
this.clickHeading = this.clickHeading.bind(this);
|
||||
this.clickEmote = this.clickEmote.bind(this);
|
||||
|
||||
|
@ -521,15 +524,23 @@ export default class EmoteMenu extends Module {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.addSection(this);
|
||||
|
||||
if ( this.ref )
|
||||
this.props.startObserving(this.ref, this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.removeSection(this);
|
||||
|
||||
if ( this.ref )
|
||||
this.props.stopObserving(this.ref);
|
||||
}
|
||||
|
||||
keyInteract(code) {
|
||||
|
||||
}
|
||||
|
||||
clickEmote(event) {
|
||||
if ( this.props.visibility_control ) {
|
||||
const ds = event.currentTarget.dataset;
|
||||
|
@ -560,6 +571,11 @@ export default class EmoteMenu extends Module {
|
|||
this.props.onClickToken(event.currentTarget.dataset.name)
|
||||
}
|
||||
|
||||
keyHeading(event) {
|
||||
if ( event.keyCode === KEYS.Enter || event.keyCode === KEYS.Space )
|
||||
this.clickHeading();
|
||||
}
|
||||
|
||||
clickHeading() {
|
||||
if ( this.props.visibility_control ) {
|
||||
const hidden = storage.get('emote-menu.hidden-sets') || [],
|
||||
|
@ -672,7 +688,7 @@ export default class EmoteMenu extends Module {
|
|||
source = 'FrankerFaceZ';
|
||||
|
||||
return (<section ref={this.saveRef} data-key={data.key} class={filtered ? 'filtered' : ''} onMouseEnter={this.mouseEnter}>
|
||||
{show_heading ? (<heading class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onClick={this.clickHeading}>
|
||||
{show_heading ? (<heading tabindex="0" class="tw-pd-1 tw-border-b tw-flex tw-flex-nowrap" onKeyDown={this.keyHeading} onClick={this.clickHeading}>
|
||||
{image}
|
||||
<div class="tw-pd-l-05">
|
||||
{(data.i18n ? t.i18n.t(data.i18n, data.title) : data.title) || t.i18n.t('emote-menu.unknown', 'Unknown Source')}
|
||||
|
@ -928,6 +944,9 @@ export default class EmoteMenu extends Module {
|
|||
this.createObserver();
|
||||
}
|
||||
|
||||
this.sections = [];
|
||||
this.activeSection = -1;
|
||||
|
||||
this.state = {
|
||||
tab: null,
|
||||
tone: t.settings.provider.get('emoji-tone', null)
|
||||
|
@ -940,6 +959,22 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
this.observing = new Map;
|
||||
|
||||
this.addSection = inst => {
|
||||
if ( ! this.sections.includes(inst) )
|
||||
this.sections.push(inst);
|
||||
}
|
||||
|
||||
this.removeSection = inst => {
|
||||
const idx = this.sections.indexOf(inst);
|
||||
if ( idx !== -1 ) {
|
||||
this.sections.splice(idx);
|
||||
if ( idx === this.activeSection )
|
||||
this.activeSection = -1;
|
||||
else if ( idx < this.activeSection )
|
||||
this.activeSection--;
|
||||
}
|
||||
}
|
||||
|
||||
this.startObserving = this.startObserving.bind(this);
|
||||
this.stopObserving = this.stopObserving.bind(this);
|
||||
this.handleObserve = this.handleObserve.bind(this);
|
||||
|
@ -1161,8 +1196,13 @@ export default class EmoteMenu extends Module {
|
|||
}
|
||||
|
||||
handleKeyDown(event) {
|
||||
if ( event.keyCode === 27 )
|
||||
const code = event.keyCode;
|
||||
if ( code === KEYS.Escape )
|
||||
this.props.toggleVisibility();
|
||||
else
|
||||
return;
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
loadData(force = false, props, state) {
|
||||
|
@ -2042,6 +2082,8 @@ export default class EmoteMenu extends Module {
|
|||
filtered: this.state.filtered,
|
||||
visibility_control: visibility,
|
||||
onClickToken: this.props.onClickToken,
|
||||
addSection: this.addSection,
|
||||
removeSection: this.removeSection,
|
||||
startObserving: this.startObserving,
|
||||
stopObserving: this.stopObserving
|
||||
}
|
||||
|
|
|
@ -1818,6 +1818,7 @@ export default class ChatHook extends Module {
|
|||
login: e.recipientLogin,
|
||||
displayName: e.recipientName
|
||||
};
|
||||
out.sub_months = e.giftMonths;
|
||||
out.sub_plan = e.methods;
|
||||
out.sub_total = e.senderCount;
|
||||
|
||||
|
@ -1865,6 +1866,7 @@ export default class ChatHook extends Module {
|
|||
login: e.recipientLogin,
|
||||
displayName: e.recipientName
|
||||
};
|
||||
out.sub_months = e.giftMonths;
|
||||
out.sub_plan = e.methods;
|
||||
out.sub_total = e.senderCount;
|
||||
|
||||
|
|
|
@ -462,9 +462,13 @@ other {# messages were deleted by a moderator.}
|
|||
|
||||
} else if ( msg.ffz_type === 'sub_gift' ) {
|
||||
const plan = msg.sub_plan || {},
|
||||
months = msg.sub_months || 1,
|
||||
tier = SUB_TIERS[plan.plan] || 1;
|
||||
|
||||
const sub_msg = t.i18n.tList('chat.sub.mystery', '{user} gifted a {plan} Sub to {recipient}! ', {
|
||||
let sub_msg;
|
||||
|
||||
const bits = {
|
||||
months,
|
||||
user: (msg.sub_anon || user.username === 'ananonymousgifter') ?
|
||||
t.i18n.t('chat.sub.anonymous-gifter', 'An anonymous gifter') :
|
||||
e('span', {
|
||||
|
@ -484,7 +488,13 @@ other {# messages were deleted by a moderator.}
|
|||
}, e('span', {
|
||||
className: 'tw-c-text-base tw-strong'
|
||||
}, msg.sub_recipient.displayName))
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
if ( months <= 1 )
|
||||
sub_msg = t.i18n.tList('chat.sub.mystery', '{user} gifted a {plan} Sub to {recipient}! ', bits);
|
||||
else
|
||||
sub_msg = t.i18n.tList('chat.sub.gift-months', '{user} gifted {months,number} month{months,en_plural} of {plan} Sub to {recipient}!', bits);
|
||||
|
||||
if ( msg.sub_total === 1 )
|
||||
sub_msg.push(t.i18n.t('chat.sub.gift-first', "It's their first time gifting a Sub in the channel!"));
|
||||
|
|
|
@ -75,11 +75,20 @@ export default class CSSTweaks extends Module {
|
|||
});
|
||||
|
||||
this.settings.add('layout.use-chat-fix', {
|
||||
requires: ['layout.swap-sidebars', 'layout.use-portrait', 'chat.use-width'],
|
||||
requires: ['context.force_chat_fix', 'layout.swap-sidebars', 'layout.use-portrait', 'chat.use-width'],
|
||||
process(ctx) {
|
||||
return ctx.get('layout.swap-sidebars') || ctx.get('layout.use-portrait') || ctx.get('chat.use-width')
|
||||
return ctx.get('context.force_chat_fix') || ctx.get('layout.swap-sidebars') || ctx.get('layout.use-portrait') || ctx.get('chat.use-width')
|
||||
},
|
||||
changed: val => {
|
||||
if ( val )
|
||||
this.toggle('chat-no-animate', true);
|
||||
else if ( ! val && ! this._no_anim_timer )
|
||||
this._no_anim_timer = requestAnimationFrame(() => {
|
||||
this._no_anim_timer = null;
|
||||
if ( ! this.settings.get('layout.use-chat-fix') )
|
||||
this.toggle('chat-no-animate', false);
|
||||
});
|
||||
|
||||
this.toggle('chat-fix', val);
|
||||
this.emit('site.player:fix-player');
|
||||
}
|
||||
|
|
|
@ -1,20 +1,29 @@
|
|||
.channel-root__scroll-area--theatre-mode .channel-info-bar {
|
||||
.channel-root__scroll-area--theatre-mode {
|
||||
.channel-info-content > div:first-child,
|
||||
.channel-info-bar {
|
||||
position: fixed;
|
||||
bottom: 10rem;
|
||||
left: 5rem;
|
||||
right: calc(var(--ffz-chat-width) + 25rem);
|
||||
right: calc(var(--ffz-chat-width) + 40rem);
|
||||
z-index: 3500;
|
||||
opacity: 0;
|
||||
width: unset !important;
|
||||
|
||||
border: 1px solid var(--color-background-alt);
|
||||
border-radius: 1rem;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
.tw-tooltip-wrapper, .tw-stat, .ffz-stat, button, a {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.channel-root__scroll-area--theatre-mode:hover .channel-info-bar {
|
||||
&:hover {
|
||||
.channel-info-content > div:first-child,
|
||||
.channel-info-bar {
|
||||
background-color: var(--color-background-base);
|
||||
opacity: 0.75;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1495,11 +1495,19 @@ export default class Player extends Module {
|
|||
|
||||
|
||||
tryTheatreMode(inst) {
|
||||
if ( ! this.settings.get('player.theatre.auto-enter') )
|
||||
if ( ! inst._ffz_theater_timer )
|
||||
inst._ffz_theater_timer = setTimeout(() => {
|
||||
inst._ffz_theater_timer = null;
|
||||
|
||||
if ( ! this.settings.get('player.theatre.auto-enter') || ! inst._ffz_mounted )
|
||||
return;
|
||||
|
||||
if ( inst.props.channelHomeLive || inst.props.channelHomeCarousel )
|
||||
return;
|
||||
|
||||
if ( inst?.props?.onTheatreModeEnabled )
|
||||
inst.props.onTheatreModeEnabled();
|
||||
}, 250);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -19,9 +19,17 @@ export const LV_SOCKET_SERVER = 'wss://cbenni.com/socket.io/';
|
|||
|
||||
|
||||
export const KEYS = {
|
||||
Space: 32,
|
||||
Enter: 13,
|
||||
Escape: 27,
|
||||
Space: 32,
|
||||
PageUp: 33,
|
||||
PageDown: 34,
|
||||
End: 35,
|
||||
Home: 36,
|
||||
ArrowLeft: 37,
|
||||
ArrowUp: 38,
|
||||
ArrowRight: 39,
|
||||
ArrowDown: 40
|
||||
};
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue