1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 15:27:43 +00:00
* Added: Setting to hide re-runs in the site's side navigation, or to display them faded out to more obviously differentiate them from original live content.
* Added: The `Changelog` will now only display commits with associated releases, by default. Users can optionally display all commits.
* Fixed: The error message when a tool-tip failed to render was not being localized.
* Fixed: Padding for certain metadata elements such as the `Host` button.
* Fixed: The Stream Uptime metadata pop-up not opening in Safari due to the browser's lack of modern JS features.
* Fixed: Tool-tips not working within the FFZ Control Center.
* Fixed: Tool-tip positioning when in Portrait Mode.
* Fixed: The `Current Category` rule type for profiles not matching on directory pages.
This commit is contained in:
SirStendec 2019-10-23 19:30:27 -04:00
parent 3e653c0381
commit 6e93d712bd
10 changed files with 77 additions and 355 deletions

View file

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

View file

@ -4,6 +4,27 @@
<h2>{{ t('home.changelog', 'Changelog') }}</h2> <h2>{{ t('home.changelog', 'Changelog') }}</h2>
</div> </div>
<div class="tw-mg-b-1 tw-flex tw-align-items-center">
<div class="tw-flex-grow-1" />
<div class="tw-checkbox tw-relative tw-tooltip-wrapper">
<input
id="nonversioned"
ref="nonversioned"
v-model="nonversioned"
type="checkbox"
class="tw-checkbox__input"
>
<label for="nonversioned" class="tw-checkbox__label">
{{ t('home.changelog.show-nonversioned', 'Include non-versioned commits.') }}
</label>
<div class="tw-tooltip tw-balloon--md tw-tooltip--wrap tw-tooltip--down tw-tooltip--align-right">
{{ t('home.changelog.about-nonversioned', 'Non-versioned commits are commits to the FrankerFaceZ repository not associated with a release build. They typically represent maintenance or contributions from the community that will be included in a subsequent release.') }}
</div>
</div>
</div>
<ul> <ul>
<li v-for="commit of display" :key="commit.sha" class="tw-mg-b-2"> <li v-for="commit of display" :key="commit.sha" class="tw-mg-b-2">
<div class="tw-flex tw-align-items-center tw-border-b tw-mg-b-05"> <div class="tw-flex tw-align-items-center tw-border-b tw-mg-b-05">
@ -60,6 +81,7 @@ export default {
data() { data() {
return { return {
error: false, error: false,
nonversioned: false,
loading: false, loading: false,
more: true, more: true,
commits: [] commits: []
@ -75,14 +97,16 @@ export default {
let message = commit.commit.message, let message = commit.commit.message,
title = old_commit; title = old_commit;
const match = TITLE_MATCH.exec(message), const match = TITLE_MATCH.exec(message);
date = new Date(commit.commit.author.date),
active = commit.sha === window.FrankerFaceZ.version_info.commit;
if ( match ) { if ( match ) {
title = match[1]; title = match[1];
message = message.slice(match[0].length); message = message.slice(match[0].length);
} } else if ( ! this.nonversioned )
continue;
const date = new Date(commit.commit.author.date),
active = commit.sha === window.FrankerFaceZ.version_info.commit;
out.push({ out.push({
title, title,

View file

@ -126,7 +126,7 @@ export default class Metadata extends Module {
async popup(data, tip) { async popup(data, tip) {
const [permission, broadcast_id] = await Promise.all([ const [permission, broadcast_id] = await Promise.all([
navigator.permissions.query({name: 'clipboard-write'}).then(perm => perm?.state).catch(() => null), navigator?.permissions?.query?.({name: 'clipboard-write'}).then(perm => perm?.state).catch(() => null),
data.getBroadcastID() data.getBroadcastID()
]); ]);
if ( ! broadcast_id ) if ( ! broadcast_id )
@ -351,343 +351,8 @@ export default class Metadata extends Module {
for(const inst of legacy_bar.ChannelBar.instances) for(const inst of legacy_bar.ChannelBar.instances)
legacy_bar.updateMetadata(inst, keys); legacy_bar.updateMetadata(inst, keys);
} }
/*const game_header = this.resolve('site.directory.game');
if ( game_header ) {
for(const inst of game_header.GameHeader.instances)
game_header.updateMetadata(inst, keys);
}*/
} }
/*async render(key, data, container, timers, refresh_fn) {
if ( timers[key] )
clearTimeout(timers[key]);
let el = container.querySelector(`.ffz-stat[data-key="${key}"]`);
const def = this.definitions[key],
destroy = () => {
if ( el ) {
if ( el.tooltip )
el.tooltip.destroy();
if ( el.popper )
el.popper.destroy();
if ( el._ffz_destroy )
el._ffz_destroy();
el._ffz_destroy = el.tooltip = el.popper = null;
el.remove();
}
};
if ( ! def || (data._mt || 'channel') !== (def.type || 'channel') )
return destroy();
try {
// Process the data if a setup method is defined.
if ( def.setup )
data = await def.setup.call(this, data);
// Let's get refresh logic out of the way now.
let refresh = maybe_call(def.refresh, this, data);
let fade_in = maybe_call(def.fade_in, this, data);
// Grab the element again in case it changed, somehow.
el = container.querySelector(`.ffz-sidebar-stat[data-key="${key}"]`);
if ( refresh && typeof refresh !== 'number' )
refresh = 1000;
if ( fade_in && typeof fade_in !== 'number' )
fade_in = 1500;
if ( fade_in && el && el._ffz_fading ) {
// If we have a fade-in and we're still fading in, make sure to
// update the metadata when that completes, if not sooner.
const remaining = fade_in - (Date.now() - el._ffz_created);
if ( remaining <= 0 ) {
el._ffz_fading = false;
el.classList.remove('ffz--fade-in');
} else if ( ! refresh || remaining <= refresh )
refresh = remaining;
}
if ( refresh )
timers[key] = setTimeout(
() => refresh_fn(key),
typeof refresh === 'number' ? refresh : 1000
);
let stat, old_color, sub_el;
const label = maybe_call(def.label, this, data);
if ( ! label )
return destroy();
const tooltip = maybe_call(def.tooltip, this, data),
subtitle = maybe_call(def.subtitle, this, data),
order = maybe_call(def.order, this, data),
color = maybe_call(def.color, this, data) || '';
if ( ! el ) {
let icon = maybe_call(def.icon, this, data);
let button = false;
el = (<div
class="ffz-sidebar-stat tw-flex tw-flex-grow-1 tw-flex-column tw-justify-content-center"
data-key={key}
tip_content={tooltip}
/>);
if ( def.button !== false && (def.popup || def.click) ) {
button = true;
let btn, popup;
let cls = maybe_call(def.button, this, data);
if ( typeof cls !== 'string' )
cls = cls ? 'ffz-button--hollow' : 'tw-button--text';
if ( typeof icon === 'string' )
icon = (<span class="tw-button__icon tw-button__icon--left"><figure class={icon} /></span>);
if ( def.popup && def.click ) {
el.appendChild(<div class="tw-flex tw--full-width tw-flex-no-wrap">
{btn = (<button class={`tw-interactive tw-button tw-button--full-width ${cls} ffz-has-stat-arrow`}>
{icon}
{stat = <div class="tw-button__text ffz-sidebar-stat--label" />}
</button>)}
{popup = (<button class={`tw-button ${cls} ffz-stat-arrow`}>
<span class="tw-button__icon tw-pd-x-0">
<figure class="ffz-i-down-dir" />
</span>
</button>)}
</div>);
} else {
el.appendChild(btn = popup = (<button class={`tw-interactive tw-button tw-button--full-width ${cls}`}>
{icon}
{stat = <div class="tw-button__text ffz-sidebar-stat--label" />}
{def.popup && <span class="tw-button__icon tw-button__icon--right">
<figure class="ffz-i-down-dir" />
</span>}
</button>));
}
if ( def.click )
btn.addEventListener('click', e => {
if ( el._ffz_fading || btn.disabled || btn.classList.contains('disabled') || el.disabled || el.classList.contains('disabled') )
return false;
def.click.call(this, el._ffz_data, e, () => refresh_fn(key));
});
if ( def.popup )
popup.addEventListener('click', () => {
if ( el._ffz_fading || popup.disabled || popup.classList.contains('disabled') || el.disabled || el.classList.contains('disabled') )
return false;
if ( el._ffz_popup )
return el._ffz_destroy();
const listeners = [],
add_close_listener = cb => listeners.push(cb);
const destroy = el._ffz_destroy = () => {
for(const cb of listeners) {
try {
cb();
} catch(err) {
this.log.capture(err, {
tags: {
metadata: key
}
});
this.log.error('Error when running a callback for pop-up destruction for metadata:', key, err);
}
}
if ( el._ffz_outside )
el._ffz_outside.destroy();
if ( el._ffz_popup ) {
const fp = el._ffz_popup;
el._ffz_popup = null;
fp.destroy();
}
el._ffz_destroy = el._ffz_outside = null;
};
const parent = document.body.querySelector('body #root,body'),
tt = el._ffz_popup = new Tooltip(parent, el, {
logger: this.log,
manual: true,
html: true,
live: false,
tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base tw-c-text-base',
// Hide the arrow for now, until we re-do our CSS to make it render correctly.
arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute',
arrowInner: 'tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
innerClass: 'tw-pd-1',
popper: {
placement: 'right-start',
modifiers: {
preventOverflow: {
boundariesElement: parent
},
flip: {
behavior: ['top', 'bottom', 'left', 'right']
}
}
},
content: (t, tip) => def.popup.call(this, el._ffz_data, tip, () => refresh_fn(key), add_close_listener),
onShow: (t, tip) =>
setTimeout(() => {
el._ffz_outside = new ClickOutside(tip.outer, destroy);
}),
onHide: destroy
});
tt._enter(el);
});
} else {
el.appendChild(<div class="tw-align-items-center tw-flex tw-justify-content-center tw-font-size-4">
{stat = <div class="tw-strong ffz-sidebar-stat--label" />}
</div>);
if ( def.click )
el.addEventListener('click', e => {
if ( el._ffz_fading || el.disabled || el.classList.contains('disabled') )
return false;
def.click.call(this, el._ffz_data, e, () => refresh_fn(key));
});
}
el.appendChild(sub_el = <div class="tw-flex tw-justify-content-center tw-c-text-alt-2 tw-font-size-6 ffz-sidebar-stat--subtitle" />);
let subcontainer;
if ( button ) {
subcontainer = container.querySelector('.ffz-sidebar-stats--buttons');
if ( ! subcontainer )
container.appendChild(subcontainer = (<div class="tw-flex tw-flex-wrap tw-justify-content-between ffz-sidebar-stats ffz-sidebar-stats--buttons" />));
} else {
subcontainer = container.querySelector('.ffz-sidebar-stats--stats');
if ( ! subcontainer ) {
subcontainer = (<div class="tw-flex tw-flex-wrap tw-justify-content-between ffz-sidebar-stats ffz-sidebar-stats--stats" />);
const btns = container.querySelector('.ffz-sidebar-stats--buttons');
if ( btns )
container.insertBefore(subcontainer, btns);
else
container.appendChild(subcontainer);
}
}
el._ffz_order = order;
if ( order != null )
el.style.order = order;
el._ffz_created = Date.now();
if ( fade_in ) {
el._ffz_fading = true;
el.classList.add('ffz--fade-in');
el.style.setProperty('--ffz-fade-duration', `${fade_in/1000}s`);
}
subcontainer.appendChild(el);
if ( def.tooltip ) {
const parent = document.body.querySelector('body #root,body');
el.tooltip = new Tooltip(parent, el, {
logger: this.log,
live: false,
html: true,
content: () => el.tip_content,
onShow: (t, tip) => el.tip = tip,
onHide: () => el.tip = null,
popper: {
placement: 'top',
modifiers: {
flip: {
behavior: ['bottom', 'top']
},
preventOverflow: {
boundariesElement: parent
}
}
}
});
}
} else {
stat = el.querySelector('.ffz-sidebar-stat--label');
sub_el = el.querySelector('.ffz-sidebar-stat--subtitle')
old_color = el.dataset.color || '';
if ( el._ffz_order !== order )
el.style.order = el._ffz_order = order;
if ( el.tip_content !== tooltip ) {
el.tip_content = tooltip;
if ( el.tip )
el.tip.element.innerHTML = tooltip;
}
}
if ( old_color !== color )
el.dataset.color = el.style.color = color;
el._ffz_data = data;
setChildren(stat, label, true);
setChildren(sub_el, subtitle, true);
if ( def.disabled !== undefined )
el.disabled = maybe_call(def.disabled, this, data);
if ( typeof def.button === 'function' ) {
const btn = el.querySelector('button');
if ( btn ) {
let cls = maybe_call(def.button, this, data);
if ( typeof cls !== 'string' )
cls = cls ? 'ffz-button--hollow' : 'tw-button--text';
if ( btn._class !== cls ) {
btn._class = cls;
if ( def.popup && def.click )
btn.className = `tw-interactive tw-button tw-button--full-width ${cls} ffz-has-stat-arrow`;
else
btn.className = `tw-interactive tw-button tw-button--full-width ${cls}`;
}
}
}
} catch(err) {
this.log.capture(err, {
tags: {
metadata: key
}
});
this.log.error(`Error rendering metadata for ${key}`, err);
return destroy();
}
}*/
async renderLegacy(key, data, container, timers, refresh_fn) { async renderLegacy(key, data, container, timers, refresh_fn) {
if ( timers[key] ) if ( timers[key] )
clearTimeout(timers[key]); clearTimeout(timers[key]);
@ -767,7 +432,7 @@ export default class Metadata extends Module {
{btn = (<button {btn = (<button
class={`tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-top-left-radius-medium tw-core-button tw-core-button--padded tw-core-button--text ${inherit ? 'ffz-c-text-inherit' : 'tw-c-text-base'} tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ${border ? 'tw-border-l tw-border-t tw-border-b' : 'tw-font-size-5 tw-regular'}`} class={`tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-top-left-radius-medium tw-core-button tw-core-button--padded tw-core-button--text ${inherit ? 'ffz-c-text-inherit' : 'tw-c-text-base'} tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ${border ? 'tw-border-l tw-border-t tw-border-b' : 'tw-font-size-5 tw-regular'}`}
> >
<div class="tw-align-items-center tw-flex tw-flex-grow-0 tw-justify-center"> <div class="tw-align-items-center tw-flex tw-flex-grow-0 tw-justify-center tw-pd-x-1">
{icon} {icon}
{stat = (<span class="ffz-stat-text" />)} {stat = (<span class="ffz-stat-text" />)}
</div> </div>
@ -846,6 +511,7 @@ export default class Metadata extends Module {
const parent = document.body.querySelector('#root>div') || document.body, const parent = document.body.querySelector('#root>div') || document.body,
tt = el._ffz_popup = new Tooltip(parent, el, { tt = el._ffz_popup = new Tooltip(parent, el, {
logger: this.log, logger: this.log,
i18n: this.i18n,
manual: true, manual: true,
live: false, live: false,
html: true, html: true,

View file

@ -15,6 +15,8 @@ export default class TooltipProvider extends Module {
super(...args); super(...args);
this.types = {}; this.types = {};
this.inject('i18n');
this.should_enable = true; this.should_enable = true;
this.types.json = target => { this.types.json = target => {
@ -66,6 +68,8 @@ export default class TooltipProvider extends Module {
this.tips = new Tooltip(container, 'ffz-tooltip', { this.tips = new Tooltip(container, 'ffz-tooltip', {
html: true, html: true,
i18n: this.i18n,
delayHide: this.checkDelayHide.bind(this), delayHide: this.checkDelayHide.bind(this),
delayShow: this.checkDelayShow.bind(this), delayShow: this.checkDelayShow.bind(this),
content: this.process.bind(this), content: this.process.bind(this),

View file

@ -258,4 +258,4 @@ Twilight.ROUTES = {
Twilight.DIALOG_EXCLUSIVE = '.sunlight-root,.twilight-main,.twilight-minimal-root>div,#root>div>.tw-full-height,.clips-root'; Twilight.DIALOG_EXCLUSIVE = '.sunlight-root,.twilight-main,.twilight-minimal-root>div,#root>div>.tw-full-height,.clips-root';
Twilight.DIALOG_MAXIMIZED = '.sunlight-page,.twilight-main,.twilight-minimal-root,#root .dashboard-side-nav+.tw-full-height,.clips-root>.tw-full-height .scrollable-area'; Twilight.DIALOG_MAXIMIZED = '.sunlight-page,.twilight-main,.twilight-minimal-root,#root .dashboard-side-nav+.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
Twilight.DIALOG_SELECTOR = '.sunlight-root,#root,.twilight-minimal-root>.tw-full-height,.clips-root>.tw-full-height .scrollable-area'; Twilight.DIALOG_SELECTOR = '.sunlight-root,#root>div,.twilight-minimal-root>.tw-full-height,.clips-root>.tw-full-height .scrollable-area';

View file

@ -134,14 +134,22 @@ export default class CSSTweaks extends Module {
changed: val => this.toggleHide('side-offline-channels', val) changed: val => this.toggleHide('side-offline-channels', val)
}); });
this.settings.add('layout.side-nav.hide-reruns', { this.settings.add('layout.side-nav.rerun-style', {
default: false, default: 1,
ui: { ui: {
path: 'Appearance > Layout >> Side Navigation', path: 'Appearance > Layout >> Side Navigation',
title: 'Hide Reruns', title: 'Display Reruns',
component: 'setting-check-box' component: 'setting-select-box',
data: [
{value: 0, title: 'Do Not Display'},
{value: 1, title: 'Normally'},
{value: 2, title: 'Faded (33% Opacity)'}
]
}, },
changed: val => this.toggleHide('side-rerun-channels', val) changed: val => {
this.toggleHide('side-rerun-channels', val === 0);
this.toggle('side-rerun-opacity', val === 2);
}
}); });
this.settings.add('layout.swap-sidebars', { this.settings.add('layout.swap-sidebars', {
@ -293,7 +301,6 @@ export default class CSSTweaks extends Module {
this.toggleHide('side-nav', !this.settings.get('layout.side-nav.show')); this.toggleHide('side-nav', !this.settings.get('layout.side-nav.show'));
this.toggleHide('side-rec-friends', !this.settings.get('layout.side-nav.show-rec-friends')); this.toggleHide('side-rec-friends', !this.settings.get('layout.side-nav.show-rec-friends'));
this.toggleHide('side-offline-channels', this.settings.get('layout.side-nav.hide-offline')); this.toggleHide('side-offline-channels', this.settings.get('layout.side-nav.hide-offline'));
this.toggleHide('side-rerun-channels', this.settings.get('layout.side-nav.hide-reruns'));
this.toggleHide('prime-offers', !this.settings.get('layout.prime-offers')); this.toggleHide('prime-offers', !this.settings.get('layout.prime-offers'));
this.toggleHide('top-discover', !this.settings.get('layout.discover')); this.toggleHide('top-discover', !this.settings.get('layout.discover'));
@ -301,6 +308,10 @@ export default class CSSTweaks extends Module {
this.toggleHide('not-live-bar', this.settings.get('channel.hide-not-live-bar')); this.toggleHide('not-live-bar', this.settings.get('channel.hide-not-live-bar'));
this.toggleHide('channel-live-ind', this.settings.get('channel.hide-live-indicator')); this.toggleHide('channel-live-ind', this.settings.get('channel.hide-live-indicator'));
const reruns = this.settings.get('layout.side-nav.rerun-style');
this.toggleHide('side-rerun-channels', reruns === 0);
this.toggle('side-rerun-opacity', reruns === 2);
const recs = this.settings.get('layout.side-nav.show-rec-channels'); const recs = this.settings.get('layout.side-nav.show-rec-channels');
this.toggleHide('side-rec-channels', recs === 0); this.toggleHide('side-rec-channels', recs === 0);
this.toggleHide('side-closed-rec-channels', recs === 2); this.toggleHide('side-closed-rec-channels', recs === 2);

View file

@ -4,7 +4,7 @@
--ffz-theater-height: calc(calc(100vw * 0.5625) + var(--ffz-portrait-extra-height)); --ffz-theater-height: calc(calc(100vw * 0.5625) + var(--ffz-portrait-extra-height));
--ffz-chat-height: calc(100vh - var(--ffz-player-height)); --ffz-chat-height: calc(100vh - var(--ffz-player-height));
& > .tw-flex-column { & > .tw-flex-column > .tw-full-height {
.ffz--portrait-invert & { .ffz--portrait-invert & {
top: var(--ffz-chat-height) !important; top: var(--ffz-chat-height) !important;
} }

View file

@ -0,0 +1,7 @@
.ffz--side-nav-card-rerun {
opacity: 0.33;
&:hover {
opacity: 0.75;
}
}

View file

@ -51,6 +51,12 @@ export default class Game extends SiteModule {
onEnable() { onEnable() {
this.GameHeader.on('mount', this.updateGameHeader, this); this.GameHeader.on('mount', this.updateGameHeader, this);
this.GameHeader.on('update', this.updateGameHeader, this); this.GameHeader.on('update', this.updateGameHeader, this);
this.GameHeader.on('unmount', () => {
this.settings.updateContext({
category: null,
categoryID: null
})
});
this.GameHeader.ready((cls, instances) => { this.GameHeader.ready((cls, instances) => {
for(const inst of instances) for(const inst of instances)
@ -62,11 +68,12 @@ export default class Game extends SiteModule {
updateGameHeader(inst) { updateGameHeader(inst) {
this.updateButtons(inst); this.updateButtons(inst);
/*this.apollo.ensureQuery( const category = inst?.props?.data?.game;
'DirectoryPage_Game',
'data.game.streams.edges.0.node.createdAt'
);*/
this.settings.updateContext({
category: category?.name,
categoryID: category?.id
});
} }

View file

@ -329,7 +329,10 @@ export class Tooltip {
if ( this.options.logger ) if ( this.options.logger )
this.options.logger.error('Error rendering tooltip content.', err); this.options.logger.error('Error rendering tooltip content.', err);
inner.textContent = `There was an error showing this tooltip.\n${err}`; if ( this.options.i18n )
inner.textContent = this.options.i18n.t('tooltip.render-error', 'There was an error rendering this tooltip.\n{err}', {err});
else
inner.textContent = `There was an error rendering this tooltip.\n${err}`;
tip._update(); tip._update();
}); });