1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* Added: Warning on the Profile Manager about profiles not matching when viewing the FFZ Control Center in a pop-out.
* API Added: Badges can now have custom CSS.
* API Added: Badges can now have a `click_handler()` method for running JS when clicked.
This commit is contained in:
SirStendec 2021-02-17 15:24:07 -05:00
parent 165e17c014
commit 3fb6d5957a
6 changed files with 183 additions and 10 deletions

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.20.66",
"version": "4.20.67",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0",
"scripts": {

View file

@ -162,7 +162,7 @@ export function generateBadgeCSS(badge, version, data, style, is_dark, badge_ver
image,
image_set,
svg
})}`;
})}${data.css || ''}`;
}
@ -464,8 +464,11 @@ export default class Badges extends Module {
if ( ! container.dataset.roomId )
container = target.closest('[data-room-id]');
const room_id = container?.dataset?.roomId,
room_login = container?.dataset?.room,
const ds = container?.dataset,
room_id = ds?.roomId,
room_login = ds?.room,
user_id = ds?.userId,
user_login = ds?.user,
data = JSON.parse(target.dataset.badgeData);
if ( data == null )
@ -494,6 +497,11 @@ export default class Badges extends Module {
} else if ( p === 'ffz' ) {
const badge = this.badges[target.dataset.badge];
if ( badge?.click_handler ) {
url = badge.click_handler(user_id, user_login, room_id, room_login, data, event);
break;
}
if ( badge?.click_url ) {
url = badge.click_url;
break;

View file

@ -1,5 +1,40 @@
<template lang="html">
<div class="ffz--widget ffz--profile-manager tw-border-t tw-pd-y-1">
<section v-if="context.exclusive" class="tw-pd-b-2">
<div class="tw-c-background-accent tw-c-text-overlay tw-pd-1">
<h3 class="ffz-i-info">
{{ t('setting.context-difference', 'Your Profiles might not match.') }}
</h3>
{{ t('setting.context-difference.description',
'Since the Control Center is open in a new window, profiles may match differently here than on other Twitch windows.')
}}
<div v-if="context.can_proxy" class="tw-flex tw-align-items-center tw-mg-t-1">
<div class="ffz-checkbox tw-relative tw-tooltip__container">
<input
id="proxied"
ref="proxied"
type="checkbox"
class="ffz-checkbox__input"
:checked="context.proxied"
@change="onProxyCheck"
>
<label for="proxied" class="ffz-checkbox__label">
<span class="tw-mg-l-1">
{{ t('setting.context-difference.use', 'Use Original Windows\'s Context') }}
</span>
</label>
<div class="tw-tooltip ffz-balloon--md tw-tooltip--wrap tw-tooltip--down tw-tooltip--align-left">
{{ t('setting.context-difference.tip', 'Checking this will use the context from the original window, causing profiles and thier rules to match like they would in the window that opened this Control Center.') }}
</div>
</div>
</div>
</div>
</section>
<div class="tw-flex tw-align-items-center tw-pd-b-05">
<div class="tw-flex-grow-1">
{{ t('setting.profiles.drag', 'Drag profiles to change their priority.') }}
@ -237,6 +272,11 @@ export default {
},
methods: {
onProxyCheck() {
const val = this.$refs.proxied.checked;
this.context.setProxied(val);
},
edit(profile) {
const item = {
full_key: 'data_management.profiles.edit_profile',

View file

@ -13,7 +13,7 @@ import Dialog from 'utilities/dialog';
import SettingMixin from './setting-mixin';
import ProviderMixin from './provider-mixin';
import {parse_path} from 'src/settings';
import {NO_SYNC_KEYS, parse_path} from 'src/settings';
function format_term(term) {
return term.replace(/<[^>]*>/g, '').toLocaleLowerCase();
@ -49,6 +49,8 @@ export default class MainMenu extends Module {
this.opened = false;
this.showing = false;
this.context = this.settings.context();
this.settings.addUI('profiles', {
path: 'Data Management @{"sort": 1000, "profile_warning": false} > Profiles @{"profile_warning": false}',
component: 'profile-manager'
@ -213,7 +215,64 @@ export default class MainMenu extends Module {
this.off('site.menu_button:clicked', this.dialog.toggleVisible, this.dialog);
}
updateContext(context) {
if ( ! context )
context = this._context;
this._context = context;
if ( this.use_context ) {
if ( ! this.context._updateContext ) {
this.context._updateContext = this.context.updateContext;
this.context.updateContext = function(context) {
const accepted = {};
for(const key of NO_SYNC_KEYS)
if ( has(context, key) )
accepted[key] = context[key];
this._updateContext(accepted);
}
}
this.context._context = {};
this.context._updateContext({
...context,
can_proxy: context?.proxied || false
});
} else {
if ( this.context._updateContext ) {
this.context.updateContext = this.context._updateContext;
this.context._updateContext = null;
}
this.context.setContext({can_proxy: context?.proxied || false});
}
}
openExclusive() {
window.addEventListener('message', event => {
const type = event.data?.ffz_type;
if ( type === 'context-update' )
this.updateContext({...event.data.ctx, proxied: true});
else if ( type === 'context-gone' ) {
this.log.info('Context proxy gone.');
this.updateContext({proxied: false});
}
});
try {
window.opener.postMessage({
ffz_type: 'request-context'
}, '*');
} catch(err) {
this.log.info('Unable to request settings context from opener.');
}
this.exclusive = true;
this.dialog.exclusive = true;
this.enable().then(() => this.dialog.show());
@ -594,7 +653,7 @@ export default class MainMenu extends Module {
const profiles = [],
keys = {};
context = context || this.settings.main_context;
context = context || this.context;
for(const profile of this.settings.__profiles)
profiles.push(keys[profile.id] = this.getProfileProxy(profile, context));
@ -648,7 +707,7 @@ export default class MainMenu extends Module {
Vue = this.vue.Vue,
settings = this.settings,
provider = settings.provider,
context = settings.main_context,
context = this.context,
[profiles, profile_keys] = this.getProfiles();
let currentProfile = profile_keys[0];
@ -669,8 +728,16 @@ export default class MainMenu extends Module {
profile_keys,
currentProfile: profile_keys[0] || profiles[0],
exclusive: this.exclusive,
can_proxy: context._context.can_proxy,
proxied: context._context.proxied,
has_update: this.has_update,
setProxied: val => {
this.use_context = val;
this.updateContext();
},
createProfile: data => {
const profile = settings.createProfile(data);
return t.getProfileProxy(profile, context);
@ -761,6 +828,9 @@ export default class MainMenu extends Module {
const profiles = context.manager.__profiles,
ids = this.profiles = context.__profiles.map(profile => profile.id);
this.proxied = this.context.proxied;
this.can_proxy = this.context.can_proxy;
for(let i=0; i < profiles.length; i++) {
const id = profiles[i].id,
profile = profile_keys[id];

View file

@ -5,7 +5,7 @@
// ============================================================================
import {EventEmitter} from 'utilities/events';
import {has, get as getter, array_equals, set_equals, map_equals} from 'utilities/object';
import {has, get as getter, array_equals, set_equals, map_equals, deep_equals} from 'utilities/object';
import * as DEFINITIONS from './types';
@ -217,8 +217,17 @@ export default class SettingsContext extends EventEmitter {
let changed = false;
for(const key in context)
if ( has(context, key) && context[key] !== this._context[key] ) {
this._context[key] = context[key];
if ( has(context, key) ) {
const val = context[key];
try {
if ( deep_equals(val, this._context[key]) )
continue;
} catch(err) {
/* no-op */
// This can catch a recursive structure error.
}
this._context[key] = val;
changed = true;
}

View file

@ -16,6 +16,18 @@ import * as FILTERS from './filters';
import * as CLEARABLES from './clearables';
function postMessage(target, msg) {
try {
target.postMessage(msg, '*');
return true;
} catch(err) {
return false;
}
}
export const NO_SYNC_KEYS = ['session'];
// ============================================================================
// SettingsManager
// ============================================================================
@ -107,12 +119,46 @@ export default class SettingsManager extends Module {
this.emit(`:uses_changed:${key}`, new_uses, old_uses);
});
this.main_context.on('context_changed', () => this._updateContextProxies());
this._context_proxies = new Set;
window.addEventListener('message', event => {
const type = event.data?.ffz_type;
if ( type === 'request-context' ) {
this._context_proxies.add(event.source);
this._updateContextProxies(event.source);
}
});
window.addEventListener('beforeunload', () => {
for(const proxy of this._context_proxies)
postMessage(proxy, {ffz_type: 'context-gone'});
});
// Don't wait around to be required.
this._start_time = performance.now();
this.enable();
}
_updateContextProxies(proxy) {
if ( ! proxy && ! this._context_proxies.size )
return;
const ctx = JSON.parse(JSON.stringify(this.main_context._context));
for(const key of NO_SYNC_KEYS)
if ( has(ctx, key) )
delete ctx[key];
if ( proxy )
postMessage(proxy, {ffz_type: 'context-update', ctx});
else
for(const proxy of this._context_proxies)
postMessage(proxy, {ffz_type: 'context-update', ctx});
}
addFilter(key, data) {
if ( this.filters[key] )