mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +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",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.46.2",
|
||||
"version": "4.47.0",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
|
|
@ -185,6 +185,7 @@ export default class Badges extends Module {
|
|||
this.inject('tooltips');
|
||||
this.inject('experiments');
|
||||
this.inject('staging');
|
||||
this.inject('load_tracker');
|
||||
|
||||
this.style = new ManagedStyle('badges');
|
||||
|
||||
|
@ -424,6 +425,11 @@ export default class Badges extends Module {
|
|||
this.rebuildAllCSS();
|
||||
this.loadGlobalBadges();
|
||||
|
||||
this.on('chat:reload-data', flags => {
|
||||
if ( ! flags || flags.badges )
|
||||
this.loadGlobalBadges();
|
||||
});
|
||||
|
||||
this.tooltips.types.badge = (target, tip) => {
|
||||
tip.add_class = 'ffz__tooltip--badges';
|
||||
|
||||
|
@ -951,6 +957,8 @@ export default class Badges extends Module {
|
|||
|
||||
|
||||
async loadGlobalBadges(tries = 0) {
|
||||
this.load_tracker.schedule('chat-data', 'ffz-global-badges');
|
||||
|
||||
let response, data;
|
||||
|
||||
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);
|
||||
|
||||
this.log.error('Error loading global badge data.', err);
|
||||
this.load_tracker.notify('chat-data', 'ffz-global-badges', false);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! response.ok )
|
||||
if ( ! response.ok ) {
|
||||
this.load_tracker.notify('chat-data', 'ffz-global-badges', false);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
data = await response.json();
|
||||
} catch(err) {
|
||||
this.log.error('Error parsing global badge data.', err);
|
||||
this.load_tracker.notify('chat-data', 'ffz-global-badges', 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.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('chat:reload-data', flags => {
|
||||
if ( ! flags || flags.emotes )
|
||||
this.loadGlobalSets();
|
||||
});
|
||||
|
||||
this.loadGlobalSets();
|
||||
}
|
||||
|
||||
|
@ -1419,6 +1424,14 @@ export default class Emotes extends Module {
|
|||
}
|
||||
|
||||
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' )
|
||||
set_id = `${set_id}`;
|
||||
|
||||
|
@ -1530,6 +1543,9 @@ export default class Emotes extends Module {
|
|||
|
||||
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)
|
||||
this.addDefaultSet('ffz-global', set_id);
|
||||
|
||||
|
|
|
@ -1263,6 +1263,44 @@ export default class Chat extends Module {
|
|||
for(const key in LINK_PROVIDERS)
|
||||
if ( has(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.removeAllSets('main');
|
||||
|
||||
if ( d.set )
|
||||
this.addSet('main', d.set);
|
||||
else
|
||||
this.removeAllSets('main');
|
||||
|
||||
if ( data.sets )
|
||||
for(const set_id in data.sets)
|
||||
|
|
|
@ -7,21 +7,26 @@
|
|||
|
||||
<select
|
||||
: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"
|
||||
>
|
||||
<template v-for="mon in monitors">
|
||||
<option :value="mon.label">
|
||||
{{ mon.label }} ({{ mon.width }}×{{ mon.height }})
|
||||
<template v-for="(mon, idx) in monitors">
|
||||
<option :value="mon">
|
||||
{{ idx + 1 }}. {{ mon.label }} ({{ mon.width }}×{{ mon.height }})
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { sortScreens, matchScreen } from 'utilities/object';
|
||||
|
||||
let last_id = 0;
|
||||
|
||||
export default {
|
||||
|
@ -31,7 +36,9 @@ export default {
|
|||
return {
|
||||
id: last_id++,
|
||||
has_monitors: true,
|
||||
monitors: []
|
||||
monitors: [],
|
||||
ready: false,
|
||||
selected: null
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -39,6 +46,22 @@ export default {
|
|||
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: {
|
||||
async detectMonitors() {
|
||||
let data;
|
||||
|
@ -54,10 +77,21 @@ export default {
|
|||
this.monitors = [];
|
||||
for(const mon of data.screens)
|
||||
this.monitors.push({
|
||||
top: mon.top,
|
||||
left: mon.left,
|
||||
label: mon.label,
|
||||
width: mon.width,
|
||||
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
|
||||
// ============================================================================
|
||||
|
||||
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 { DEBUG } from 'utilities/constants';
|
||||
|
||||
|
@ -425,7 +425,10 @@ if ( window.getScreenDetails ) {
|
|||
if ( ! screen )
|
||||
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'
|
||||
];
|
||||
|
||||
|
||||
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) {
|
||||
if ( ! val )
|
||||
return val;
|
||||
|
@ -1212,7 +1231,8 @@ export default class EmoteMenu extends Module {
|
|||
requestAnimationFrame(() => {
|
||||
const el = this.nav_ref?.querySelector?.(`button[data-key="${this.state.active_nav}"]`);
|
||||
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}"]`);
|
||||
if ( el ) {
|
||||
this.lock_active = true;
|
||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||
scrollIntoView(el);
|
||||
//el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||
this.setState({
|
||||
active_nav: key
|
||||
});
|
||||
|
@ -1381,7 +1402,8 @@ export default class EmoteMenu extends Module {
|
|||
el = set && this.ref?.querySelector?.(`section[data-key="${set.key}"]`);
|
||||
|
||||
if ( el )
|
||||
el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||
scrollIntoView(el);
|
||||
//el.scrollIntoView({block: 'nearest', inline: 'start'});
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -2161,7 +2161,7 @@ export default class ChatHook extends Module {
|
|||
room = room.toLowerCase();
|
||||
|
||||
for(const inst of this.ChatService.instances) {
|
||||
if ( inst.props.channelLogin.toLowerCase() === room ) {
|
||||
if ( room === '*' || inst.props.channelLogin.toLowerCase() === room ) {
|
||||
inst.addMessage({
|
||||
type: this.chat_types.Notice,
|
||||
message
|
||||
|
@ -2228,11 +2228,39 @@ export default class ChatHook extends Module {
|
|||
msg = msg.replace(/\s+/g, ' ');
|
||||
|
||||
if ( msg.startsWith('/ffz') ) {
|
||||
inst.addMessage({
|
||||
type: t.chat_types.Notice,
|
||||
message: 'The /ffz command is not yet re-implemented.'
|
||||
msg = msg.slice(5).trim();
|
||||
const idx = msg.indexOf(' ');
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -354,6 +354,7 @@ export default class ChatLine extends Module {
|
|||
|
||||
this.can_reprocess = true;
|
||||
|
||||
this.on('chat:reload-data', () => this.can_reprocess = true);
|
||||
this.on('chat:room-add', () => this.can_reprocess = true);
|
||||
|
||||
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) {
|
||||
return object ? HOP.call(object, key) : false;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue