mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-02 16:08:31 +00:00
4.47.0
* Added: `/ffz reload` command to reload emote and badge data without reloading the page. (Note: Add-ons will need to update to add support for the command.) * Changed: Improve monitor support for Current Monitor, specifically when a user has multiple monitors with the same name. * Fixed: Track the loaded status of global FFZ badges when loading chat data. * Fixed: The page scrolling incorrectly when using the FFZ emote menu in some situations.
This commit is contained in:
parent
9381c17e71
commit
e6ad12c937
11 changed files with 219 additions and 18 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.46.2",
|
"version": "4.47.0",
|
||||||
"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",
|
||||||
|
|
|
@ -185,6 +185,7 @@ export default class Badges extends Module {
|
||||||
this.inject('tooltips');
|
this.inject('tooltips');
|
||||||
this.inject('experiments');
|
this.inject('experiments');
|
||||||
this.inject('staging');
|
this.inject('staging');
|
||||||
|
this.inject('load_tracker');
|
||||||
|
|
||||||
this.style = new ManagedStyle('badges');
|
this.style = new ManagedStyle('badges');
|
||||||
|
|
||||||
|
@ -424,6 +425,11 @@ export default class Badges extends Module {
|
||||||
this.rebuildAllCSS();
|
this.rebuildAllCSS();
|
||||||
this.loadGlobalBadges();
|
this.loadGlobalBadges();
|
||||||
|
|
||||||
|
this.on('chat:reload-data', flags => {
|
||||||
|
if ( ! flags || flags.badges )
|
||||||
|
this.loadGlobalBadges();
|
||||||
|
});
|
||||||
|
|
||||||
this.tooltips.types.badge = (target, tip) => {
|
this.tooltips.types.badge = (target, tip) => {
|
||||||
tip.add_class = 'ffz__tooltip--badges';
|
tip.add_class = 'ffz__tooltip--badges';
|
||||||
|
|
||||||
|
@ -951,6 +957,8 @@ export default class Badges extends Module {
|
||||||
|
|
||||||
|
|
||||||
async loadGlobalBadges(tries = 0) {
|
async loadGlobalBadges(tries = 0) {
|
||||||
|
this.load_tracker.schedule('chat-data', 'ffz-global-badges');
|
||||||
|
|
||||||
let response, data;
|
let response, data;
|
||||||
|
|
||||||
if ( this.experiments.getAssignment('api_load') && tries < 1 )
|
if ( this.experiments.getAssignment('api_load') && tries < 1 )
|
||||||
|
@ -966,16 +974,20 @@ export default class Badges extends Module {
|
||||||
return setTimeout(() => this.loadGlobalBadges(tries), 500 * tries);
|
return setTimeout(() => this.loadGlobalBadges(tries), 500 * tries);
|
||||||
|
|
||||||
this.log.error('Error loading global badge data.', err);
|
this.log.error('Error loading global badge data.', err);
|
||||||
|
this.load_tracker.notify('chat-data', 'ffz-global-badges', false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! response.ok )
|
if ( ! response.ok ) {
|
||||||
|
this.load_tracker.notify('chat-data', 'ffz-global-badges', false);
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
data = await response.json();
|
data = await response.json();
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
this.log.error('Error parsing global badge data.', err);
|
this.log.error('Error parsing global badge data.', err);
|
||||||
|
this.load_tracker.notify('chat-data', 'ffz-global-badges', false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1017,6 +1029,7 @@ export default class Badges extends Module {
|
||||||
|
|
||||||
this.log.info(`Loaded ${badges} badges and assigned them to ${users} users.`);
|
this.log.info(`Loaded ${badges} badges and assigned them to ${users} users.`);
|
||||||
this.buildBadgeCSS();
|
this.buildBadgeCSS();
|
||||||
|
this.load_tracker.notify('chat-data', 'ffz-global-badges');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -639,6 +639,11 @@ export default class Emotes extends Module {
|
||||||
|
|
||||||
this.on('socket:command:follow_sets', this.updateFollowSets, this);
|
this.on('socket:command:follow_sets', this.updateFollowSets, this);
|
||||||
|
|
||||||
|
this.on('chat:reload-data', flags => {
|
||||||
|
if ( ! flags || flags.emotes )
|
||||||
|
this.loadGlobalSets();
|
||||||
|
});
|
||||||
|
|
||||||
this.loadGlobalSets();
|
this.loadGlobalSets();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1419,6 +1424,14 @@ export default class Emotes extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeDefaultSet(provider, set_id) {
|
removeDefaultSet(provider, set_id) {
|
||||||
|
if ( ! set_id ) {
|
||||||
|
const sets = this.default_sets.get(provider);
|
||||||
|
if ( sets )
|
||||||
|
for(const set_id of Array.from(sets))
|
||||||
|
this.removeDefaultSet(provider, set_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ( typeof set_id === 'number' )
|
if ( typeof set_id === 'number' )
|
||||||
set_id = `${set_id}`;
|
set_id = `${set_id}`;
|
||||||
|
|
||||||
|
@ -1530,6 +1543,9 @@ export default class Emotes extends Module {
|
||||||
|
|
||||||
const sets = data.sets || {};
|
const sets = data.sets || {};
|
||||||
|
|
||||||
|
// Remove existing global sets, in case we have any.
|
||||||
|
this.removeDefaultSet('ffz-global');
|
||||||
|
|
||||||
for(const set_id of data.default_sets)
|
for(const set_id of data.default_sets)
|
||||||
this.addDefaultSet('ffz-global', set_id);
|
this.addDefaultSet('ffz-global', set_id);
|
||||||
|
|
||||||
|
|
|
@ -1263,6 +1263,44 @@ export default class Chat extends Module {
|
||||||
for(const key in LINK_PROVIDERS)
|
for(const key in LINK_PROVIDERS)
|
||||||
if ( has(LINK_PROVIDERS, key) )
|
if ( has(LINK_PROVIDERS, key) )
|
||||||
this.addLinkProvider(LINK_PROVIDERS[key]);
|
this.addLinkProvider(LINK_PROVIDERS[key]);
|
||||||
|
|
||||||
|
this.on('chat:reload-data', flags => {
|
||||||
|
for(const room of this.iterateRooms())
|
||||||
|
room.load_data();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('chat:get-tab-commands', event => {
|
||||||
|
event.commands.push({
|
||||||
|
name: 'ffz reload',
|
||||||
|
description: this.i18n.t('chat.command.reload', 'Reload FFZ and add-on chat data (emotes, badges, etc.)'),
|
||||||
|
permissionLevel: 0,
|
||||||
|
ffz_group: 'FrankerFaceZ'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.triggered_reload = false;
|
||||||
|
|
||||||
|
this.on('chat:ffz-command:reload', event => {
|
||||||
|
if ( this.triggered_reload )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const sc = this.resolve('site.chat');
|
||||||
|
if ( sc?.addNotice )
|
||||||
|
sc.addNotice('*', this.i18n.t('chat.command.reload.starting', 'FFZ is reloading data...'));
|
||||||
|
|
||||||
|
this.triggered_reload = true;
|
||||||
|
this.emit('chat:reload-data');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on('load_tracker:complete:chat-data', () => {
|
||||||
|
if ( this.triggered_reload ) {
|
||||||
|
const sc = this.resolve('site.chat');
|
||||||
|
if ( sc?.addNotice )
|
||||||
|
sc.addNotice('*', this.i18n.t('chat.command.reload.done', 'FFZ has finished reloading data.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.triggered_reload = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -314,10 +314,10 @@ export default class Room {
|
||||||
|
|
||||||
this.data = d;
|
this.data = d;
|
||||||
|
|
||||||
|
this.removeAllSets('main');
|
||||||
|
|
||||||
if ( d.set )
|
if ( d.set )
|
||||||
this.addSet('main', d.set);
|
this.addSet('main', d.set);
|
||||||
else
|
|
||||||
this.removeAllSets('main');
|
|
||||||
|
|
||||||
if ( data.sets )
|
if ( data.sets )
|
||||||
for(const set_id in data.sets)
|
for(const set_id in data.sets)
|
||||||
|
|
|
@ -7,21 +7,26 @@
|
||||||
|
|
||||||
<select
|
<select
|
||||||
:id="'label$' + id"
|
:id="'label$' + id"
|
||||||
v-model="value.data.label"
|
v-model="selected"
|
||||||
class="tw-flex-grow-1 tw-mg-l-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-select"
|
class="tw-flex-grow-1 tw-mg-l-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-select"
|
||||||
>
|
>
|
||||||
<template v-for="mon in monitors">
|
<template v-for="(mon, idx) in monitors">
|
||||||
<option :value="mon.label">
|
<option :value="mon">
|
||||||
{{ mon.label }} ({{ mon.width }}×{{ mon.height }})
|
{{ idx + 1 }}. {{ mon.label }} ({{ mon.width }}×{{ mon.height }})
|
||||||
</option>
|
</option>
|
||||||
</template>
|
</template>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="tw-c-text-alt-2">
|
||||||
|
{{ t('setting.filter.monitor.about', 'This setting requires that this site has the Window Management permission. Please be sure that it is allowed.') }}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import { sortScreens, matchScreen } from 'utilities/object';
|
||||||
|
|
||||||
let last_id = 0;
|
let last_id = 0;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -31,7 +36,9 @@ export default {
|
||||||
return {
|
return {
|
||||||
id: last_id++,
|
id: last_id++,
|
||||||
has_monitors: true,
|
has_monitors: true,
|
||||||
monitors: []
|
monitors: [],
|
||||||
|
ready: false,
|
||||||
|
selected: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -39,6 +46,22 @@ export default {
|
||||||
this.detectMonitors();
|
this.detectMonitors();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
selected() {
|
||||||
|
if ( ! this.ready || ! this.selected )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const data = this.value.data = this.value.data || {};
|
||||||
|
|
||||||
|
data.label = this.selected.label;
|
||||||
|
data.index = this.monitors.indexOf(this.selected);
|
||||||
|
data.top = this.selected.top;
|
||||||
|
data.left = this.selected.left;
|
||||||
|
data.width = this.selected.width;
|
||||||
|
data.height = this.selected.height;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
async detectMonitors() {
|
async detectMonitors() {
|
||||||
let data;
|
let data;
|
||||||
|
@ -54,10 +77,21 @@ export default {
|
||||||
this.monitors = [];
|
this.monitors = [];
|
||||||
for(const mon of data.screens)
|
for(const mon of data.screens)
|
||||||
this.monitors.push({
|
this.monitors.push({
|
||||||
|
top: mon.top,
|
||||||
|
left: mon.left,
|
||||||
label: mon.label,
|
label: mon.label,
|
||||||
width: mon.width,
|
width: mon.width,
|
||||||
height: mon.height
|
height: mon.height
|
||||||
});
|
});
|
||||||
|
|
||||||
|
sortScreens(this.monitors);
|
||||||
|
if ( this.value.data )
|
||||||
|
this.selected = matchScreen(this.monitors, this.value.data);
|
||||||
|
|
||||||
|
this.ready = true;
|
||||||
|
|
||||||
|
if ( ! this.selected )
|
||||||
|
this.selected = this.monitors[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// Profile Filters for Settings
|
// Profile Filters for Settings
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import {glob_to_regex, escape_regex} from 'utilities/object';
|
import {glob_to_regex, escape_regex, matchScreen, sortScreens} from 'utilities/object';
|
||||||
import {createTester} from 'utilities/filtering';
|
import {createTester} from 'utilities/filtering';
|
||||||
import { DEBUG } from 'utilities/constants';
|
import { DEBUG } from 'utilities/constants';
|
||||||
|
|
||||||
|
@ -425,7 +425,10 @@ if ( window.getScreenDetails ) {
|
||||||
if ( ! screen )
|
if ( ! screen )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return screen.label === config.label;
|
const sorted = sortScreens(Array.from(details.screens)),
|
||||||
|
matched = matchScreen(sorted, config);
|
||||||
|
|
||||||
|
return matched === screen;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,25 @@ const TONE_EMOJI = [
|
||||||
'love_you_gesture'
|
'love_you_gesture'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
function scrollIntoView(el, container) {
|
||||||
|
if ( ! container )
|
||||||
|
container = el.closest('.simplebar-scroll-content') ?? el.parentElement;
|
||||||
|
|
||||||
|
const height = container.offsetHeight,
|
||||||
|
el_height = el.offsetHeight,
|
||||||
|
el_top = el.offsetTop;
|
||||||
|
|
||||||
|
if ( el_height >= height ) {
|
||||||
|
container.scrollTop = el_top;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pos = el_top + (el_height / 2) - (height / 2);
|
||||||
|
container.scrollTop = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function maybe_date(val) {
|
function maybe_date(val) {
|
||||||
if ( ! val )
|
if ( ! val )
|
||||||
return val;
|
return val;
|
||||||
|
@ -1212,7 +1231,8 @@ export default class EmoteMenu extends Module {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const el = this.nav_ref?.querySelector?.(`button[data-key="${this.state.active_nav}"]`);
|
const el = this.nav_ref?.querySelector?.(`button[data-key="${this.state.active_nav}"]`);
|
||||||
if ( el )
|
if ( el )
|
||||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
scrollIntoView(el);
|
||||||
|
//el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1342,7 +1362,8 @@ export default class EmoteMenu extends Module {
|
||||||
const el = this.ref?.querySelector?.(`section[data-key="${key}"]`);
|
const el = this.ref?.querySelector?.(`section[data-key="${key}"]`);
|
||||||
if ( el ) {
|
if ( el ) {
|
||||||
this.lock_active = true;
|
this.lock_active = true;
|
||||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
scrollIntoView(el);
|
||||||
|
//el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||||
this.setState({
|
this.setState({
|
||||||
active_nav: key
|
active_nav: key
|
||||||
});
|
});
|
||||||
|
@ -1381,7 +1402,8 @@ export default class EmoteMenu extends Module {
|
||||||
el = set && this.ref?.querySelector?.(`section[data-key="${set.key}"]`);
|
el = set && this.ref?.querySelector?.(`section[data-key="${set.key}"]`);
|
||||||
|
|
||||||
if ( el )
|
if ( el )
|
||||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
scrollIntoView(el);
|
||||||
|
//el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2161,7 +2161,7 @@ export default class ChatHook extends Module {
|
||||||
room = room.toLowerCase();
|
room = room.toLowerCase();
|
||||||
|
|
||||||
for(const inst of this.ChatService.instances) {
|
for(const inst of this.ChatService.instances) {
|
||||||
if ( inst.props.channelLogin.toLowerCase() === room ) {
|
if ( room === '*' || inst.props.channelLogin.toLowerCase() === room ) {
|
||||||
inst.addMessage({
|
inst.addMessage({
|
||||||
type: this.chat_types.Notice,
|
type: this.chat_types.Notice,
|
||||||
message
|
message
|
||||||
|
@ -2228,11 +2228,39 @@ export default class ChatHook extends Module {
|
||||||
msg = msg.replace(/\s+/g, ' ');
|
msg = msg.replace(/\s+/g, ' ');
|
||||||
|
|
||||||
if ( msg.startsWith('/ffz') ) {
|
if ( msg.startsWith('/ffz') ) {
|
||||||
inst.addMessage({
|
msg = msg.slice(5).trim();
|
||||||
type: t.chat_types.Notice,
|
const idx = msg.indexOf(' ');
|
||||||
message: 'The /ffz command is not yet re-implemented.'
|
let subcmd;
|
||||||
|
if ( idx === -1 ) {
|
||||||
|
subcmd = msg;
|
||||||
|
msg = '';
|
||||||
|
} else {
|
||||||
|
subcmd = msg.slice(0, idx);
|
||||||
|
msg = msg.slice(idx + 1).trimStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
const event = new FFZEvent({
|
||||||
|
command: subcmd,
|
||||||
|
message: msg,
|
||||||
|
extra,
|
||||||
|
context: t.chat.context,
|
||||||
|
channel: inst.props.channelLogin,
|
||||||
|
_inst: inst,
|
||||||
|
addMessage,
|
||||||
|
sendMessage
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const topic = `chat:ffz-command:${subcmd}`,
|
||||||
|
listeners = t.listeners(topic);
|
||||||
|
|
||||||
|
if ( listeners?.length > 0 )
|
||||||
|
t.emit(topic, event);
|
||||||
|
else
|
||||||
|
inst.addMessage({
|
||||||
|
type: t.chat_types.Notice,
|
||||||
|
message: t.i18n.t('chat.ffz-command.invalid', 'No such command: /ffz {subcmd}', {subcmd})
|
||||||
|
});
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -354,6 +354,7 @@ export default class ChatLine extends Module {
|
||||||
|
|
||||||
this.can_reprocess = true;
|
this.can_reprocess = true;
|
||||||
|
|
||||||
|
this.on('chat:reload-data', () => this.can_reprocess = true);
|
||||||
this.on('chat:room-add', () => this.can_reprocess = true);
|
this.on('chat:room-add', () => this.can_reprocess = true);
|
||||||
|
|
||||||
this.on('load_tracker:complete:chat-data', () => {
|
this.on('load_tracker:complete:chat-data', () => {
|
||||||
|
|
|
@ -45,6 +45,52 @@ export function generateUUID(input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function sortScreens(screens) {
|
||||||
|
screens.sort((a,b) => {
|
||||||
|
if ( a.left < b.left ) return -1;
|
||||||
|
if ( a.left > b.left ) return 1;
|
||||||
|
if ( a.top < b.top ) return -1;
|
||||||
|
if ( a.top > b.top ) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return screens;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function matchScreen(screens, options) {
|
||||||
|
let match = undefined;
|
||||||
|
let mscore = 0;
|
||||||
|
|
||||||
|
for(let i = 0; i < screens.length; i++) {
|
||||||
|
const mon = screens[i];
|
||||||
|
if ( mon.label !== options.label )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let score = 1;
|
||||||
|
if ( options.left && options.left === mon.left )
|
||||||
|
score += 15;
|
||||||
|
if ( options.top && options.top === mon.top )
|
||||||
|
score += 15;
|
||||||
|
|
||||||
|
if ( options.width && options.width === mon.width )
|
||||||
|
score += 10;
|
||||||
|
|
||||||
|
if ( options.height && options.height === mon.height )
|
||||||
|
score += 10;
|
||||||
|
|
||||||
|
if ( options.index )
|
||||||
|
score -= Math.abs(options.index - i);
|
||||||
|
|
||||||
|
if ( score > mscore ) {
|
||||||
|
match = mon;
|
||||||
|
mscore = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export function has(object, key) {
|
export function has(object, key) {
|
||||||
return object ? HOP.call(object, key) : false;
|
return object ? HOP.call(object, key) : false;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue