1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* Changed: Refactor player code to be shared between the embed/popout player and the main player.
* Changed: Place metadata above the player controls in embed/popout player to better handle very narrow players.
* Changed: Support clicking Stream Uptime metadata in the embed/popout player to copy a link to the past broadcast at the current time.
* Fixed: The player being an incorrect height in portrait mode in some cases.
* Fixed: Schedule data disappearing when FFZ boots while viewing the Schedule page due to how we send layout update events.
This commit is contained in:
SirStendec 2021-02-26 15:35:26 -05:00
parent ab4f72c345
commit 0d433c3ebd
13 changed files with 1788 additions and 2864 deletions

View file

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

View file

@ -9,7 +9,7 @@
<h3 class="ffz-i-attention">
{{ t('setting.dev-warning', "It's dangerous to go at all.") }}
</h3>
<markdown :source="t('setting.dev-warning.explain', 'Be careful, this is an advanced feature intended for developer use only. Normal users should steer clear. Adjusting your experiments can have unexpected impacts on your Twitch experience. FrankerFaceZ is not responsible for any issues you encounter as a result of tampering with experiments, and we will not provide support.\n\nIf you\'re sure about this, please type `sv_cheats 1` into the box below and hit enter.')" />
<markdown :source="t('setting.dev-warning.explain', 'Be careful, this is an advanced feature intended for developer use only. Normal users should steer clear. Adjusting your experiments can have unexpected impacts on your Twitch experience. FrankerFaceZ is not responsible for any issues you encounter as a result of tampering with experiments, and we will not provide support.\n\nIf you\'re sure about this, please type `{code}` into the box below and hit enter.', {code})" />
</div>
<div class="tw-flex tw-align-items-center">
@ -214,7 +214,7 @@
<script>
import {has} from 'utilities/object';
import {has, pick_random} from 'utilities/object';
function matches(exp, filter) {
return (exp.key && exp.key.toLowerCase().includes(filter)) ||
@ -224,12 +224,20 @@ function matches(exp, filter) {
));
}
const CODES = [
'sv_cheats 1',
'idspispopd',
'rosebud',
'how do you turn this on'
];
export default {
props: ['item', 'filter'],
data() {
return {
experiments_locked: this.item.is_locked(),
code: pick_random(CODES),
experiments_locked: true, //this.item.is_locked(),
sort_by: 1,
unused: false,
unique_id: this.item.unique_id(),
@ -301,7 +309,7 @@ export default {
methods: {
enterCode() {
if ( this.$refs.code.value !== 'sv_cheats 1' )
if ( this.$refs.code.value !== this.code )
return;
this.experiments_locked = false;

View file

@ -136,6 +136,7 @@ export default class Metadata extends Module {
this.definitions.uptime = {
inherit: true,
no_arrow: true,
player: true,
refresh() { return this.settings.get('metadata.uptime') > 0 },
@ -147,9 +148,12 @@ export default class Metadata extends Module {
if ( ! created_at )
return {};
created = new Date(created_at);
created = created_at;
}
if ( !(created instanceof Date) )
created = new Date(created);
const now = Date.now() - socket._time_drift;
return {
@ -292,6 +296,7 @@ export default class Metadata extends Module {
button: true,
inherit: true,
modview: true,
player: true,
refresh() {
return this.settings.get('metadata.player-stats')
@ -417,10 +422,10 @@ export default class Metadata extends Module {
return;
if ( data.delay > (setting * 2) )
return '#ec1313';
return data.is_player ? '#f9b6b6' : '#ec1313';
else if ( data.delay > setting )
return '#fc7835';
return data.is_player ? '#fcb896' : '#fc7835';
},
tooltip(data) {
@ -553,17 +558,10 @@ export default class Metadata extends Module {
for(const el of channel.InfoBar.instances)
channel.updateMetadata(el, keys);
/*const bar = this.resolve('site.channel_bar');
if ( bar ) {
for(const inst of bar.ChannelBar.instances)
bar.updateMetadata(inst, keys);
}
const legacy_bar = this.resolve('site.legacy_channel_bar');
if ( legacy_bar ) {
for(const inst of legacy_bar.ChannelBar.instances)
legacy_bar.updateMetadata(inst, keys);
}*/
const player = this.resolve('site.player');
if ( player )
for(const inst of player.Player.instances)
player.updateMetadata(inst, keys);
}
async renderLegacy(key, data, container, timers, refresh_fn) {
@ -596,6 +594,7 @@ export default class Metadata extends Module {
const ref_fn = () => refresh_fn(key);
data = {
...data,
is_player: false,
refresh: ref_fn
};
@ -793,40 +792,7 @@ export default class Metadata extends Module {
if ( order != null )
el.style.order = order;
let subcontainer = container;
/*if ( button )
subcontainer = container.querySelector('.tw-flex:last-child') || container;
else
subcontainer = container.querySelector('.tw-flex:first-child') || container;*/
subcontainer.appendChild(el);
/*if ( def.tooltip ) {
const parent = document.body.querySelector('#root>div') || document.body;
el.tooltip = new Tooltip(parent, el, {
logger: this.log,
live: false,
html: true,
content: () => maybe_call(def.tooltip, this, el._ffz_data),
onShow: (t, tip) => el.tip = tip,
onHide: () => {
el.tip = null;
el.tip_content = null;
},
popper: {
placement: 'bottom',
modifiers: {
flip: {
behavior: ['bottom', 'top']
},
preventOverflow: {
boundariesElement: parent
}
}
}
});
}*/
container.appendChild(el);
} else {
stat = el.querySelector('.ffz-stat-text');

View file

@ -90,6 +90,42 @@ export default class PlayerSite extends BaseSite {
}));
}
awaitTwitchData() {
if ( this.twitch_data )
return Promise.resolve(this.twitch_data);
if ( this._loading_td )
return new Promise((s,f) => {
this._loading_td.push([s,f]);
});
const loads = this._loading_td = [];
return new Promise((s,f) => {
loads.push([s,f]);
Promise.all([
import(/* webpackChunkName: 'tdata' */ 'utilities/compat/apollo'),
import(/* webpackChunkName: 'tdata' */ 'utilities/twitch-data')
]).then(modules => {
const Apollo = modules[0].default,
TwitchData = modules[1].default;
this.inject('apollo', Apollo);
this.inject('twitch_data', TwitchData);
this.twitch_data.enable().then(() => {
this._loading_td = null;
for(const pair of loads)
pair[0](this.twitch_data);
}).catch(err => {
this._loading_td = null;
for(const pair of loads)
pair[1](err);
});
})
})
}
get data() {
return this.DataSource.first;
}

View file

@ -89,6 +89,60 @@ export default class Metadata extends Module {
)}
</div>
];
},
async popup(data, tip) {
const [permission, broadcast_id] = await Promise.all([
navigator?.permissions?.query?.({name: 'clipboard-write'}).then(perm => perm?.state).catch(() => null),
data.getBroadcastID()
]);
if ( ! broadcast_id )
return (<div>
{ this.i18n.t('metadata.uptime-no-id', 'Sorry, we couldn\'t find an archived video for the current broadcast.') }
</div>);
const url = `https://www.twitch.tv/videos/${broadcast_id}${data.uptime > 0 ? `?t=${durationForURL(data.uptime)}` : ''}`,
can_copy = permission === 'granted' || permission === 'prompt';
const copy = can_copy ? e => {
navigator.clipboard.writeText(url);
e.preventDefault();
return false;
} : null;
tip.element.classList.add('ffz-balloon--lg');
return (<div>
<div class="tw-pd-b-1 tw-mg-b-1 tw-border-b tw-semibold">
{ this.i18n.t('metadata.uptime.link-to', 'Link to {time}', {
time: duration_to_string(data.uptime, false, false, false, false)
}) }
</div>
<div class="tw-flex tw-align-items-center">
<input
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input tw-full-width"
type="text"
value={url}
onFocus={e => e.target.select()}
/>
{can_copy && <div class="tw-relative tw-tooltip__container tw-mg-l-1">
<button
class="tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon ffz-core-button ffz-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative"
aria-label={ this.i18n.t('metadata.uptime.copy', 'Copy to Clipboard') }
onClick={copy}
>
<div class="tw-align-items-center tw-flex tw-flex-grow-0">
<span class="tw-button-icon__icon">
<figure class="ffz-i-docs" />
</span>
</div>
</button>
<div class="tw-tooltip tw-tooltip--align-right tw-tooltip--up">
{ this.i18n.t('metadata.uptime.copy', 'Copy to Clipboard') }
</div>
</div>}
</div>
</div>);
}
}
@ -351,7 +405,7 @@ export default class Metadata extends Module {
player.updateMetadata(inst, keys);
}
async render(key, data, container, timers, refresh_fn) {
async renderPlayer(key, data, container, timers, refresh_fn) {
if ( timers[key] )
clearTimeout(timers[key]);
@ -428,7 +482,7 @@ export default class Metadata extends Module {
if ( def.popup && def.click ) {
el = (<div
class={`tw-align-items-center tw-inline-flex tw-relative tw-tooltip__container ffz-stat tw-stat ffz-stat--fix-padding ${border ? 'tw-mg-r-1' : 'tw-mg-r-05 ffz-mg-l--05'}`}
class={`tw-align-items-center tw-inline-flex tw-relative tw-tooltip__container ffz-stat tw-stat ffz-stat--fix-padding ${border ? 'tw-mg-r-1' : 'tw-mg-r-05'}`}
data-key={key}
tip_content={null}
>
@ -455,7 +509,7 @@ export default class Metadata extends Module {
} else
btn = popup = el = (<button
class={`ffz-stat tw-align-items-center tw-align-middle ${inherit ? 'ffz-c-text-inherit' : ''} tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-stat--fix-padding ${border ? 'tw-border tw-mg-r-1' : 'tw-regular tw-mg-r-05 ffz-mg-l--05'}${def.tooltip ? ' ffz-tooltip ffz-tooltip--no-mouse' : ''}`}
class={`ffz-stat tw-align-items-center tw-align-middle ${inherit ? 'ffz-c-text-inherit' : ''} tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-stat--fix-padding ${border ? 'tw-border tw-mg-r-1' : 'tw-regular tw-mg-r-05'}${def.tooltip ? ' ffz-tooltip ffz-tooltip--no-mouse' : ''}`}
data-tooltip-type="metadata"
data-key={key}
tip_content={null}
@ -514,7 +568,7 @@ export default class Metadata extends Module {
el._ffz_destroy = el._ffz_outside = null;
};
const parent = document.fullscreenElement || document.body.querySelector('#root>div') || document.body,
const parent = el.closest('.video-player__overlay') || document.fullscreenElement || document.body.querySelector('#root>div') || document.body,
tt = el._ffz_popup = new Tooltip(parent, el, {
logger: this.log,
i18n: this.i18n,

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,12 @@
@import 'styles/main.scss';
.video-player__inactive,
.video-player__overlay[data-controls="false"] {
.ffz-metadata-balloon {
opacity: 0;
}
}
.ffz--player-pip,
.ffz--player-reset {
&:before {
@ -25,26 +32,4 @@ div[data-a-target="player-settings-menu"] {
.ffz--cc-button {
order: 999;
}
.ffz--player-meta-tray {
display: inline-flex;
color: var(--color-text-overlay);
}
.ffz-stat {
background-color: var(--color-background-pill);
color: var(--color-text-overlay);
display: inline-block;
position: relative;
line-height: 1;
text-align: center;
white-space: nowrap;
border-radius: 1000px;
padding: 0.3rem 0.5rem;
font-size: 75%;
}
.ffz-stat-text {
font-variant-numeric: tabular-nums;
}

1575
src/sites/shared/player.jsx Normal file

File diff suppressed because it is too large Load diff

View file

@ -16,6 +16,10 @@
height: var(--ffz-player-height) !important;
}
.channel-root__player--with-chat {
max-height: var(--ffz-player-height) !important;
}
.persistent-player.persistent-player__border--mini {
pointer-events: none;

View file

@ -312,7 +312,8 @@ export default class Layout extends Module {
this._needs_resize = true;
else {
for(const inst of this.ResizeDetector.instances) {
inst?.props?.onResize?.();
inst?.onScroll?.();
//inst?.props?.onResize?.();
}
this.emit('site.player:fix-player');

File diff suppressed because it is too large Load diff

View file

@ -77,7 +77,6 @@ export default class TwitchData extends Module {
this.site = this.parent;
this.inject('site.apollo');
this.inject('site.web_munch');
this._waiting_stream_ids = new Map;
this._waiting_stream_logins = new Map;
@ -133,14 +132,20 @@ export default class TwitchData extends Module {
return session && session.locale || 'en-US'
}
get searchClient() {
async searchClient() {
if ( this._search )
return this._search;
const apollo = this.apollo.client,
core = this.site.getCore();
core = this.site.getCore(),
web_munch = this.resolve('web_munch');
const SearchClient = this.web_munch.getModule('algolia-search');
if ( ! web_munch )
return null;
await web_munch.enable();
const SearchClient = await this.web_munch.findModule('algolia-search');
if ( ! SearchClient || ! apollo || ! core )
return null;
@ -914,12 +919,14 @@ export default class TwitchData extends Module {
if ( ! locale )
locale = this.locale;
const client = await this.searchClient();
locale = getAlgoliaLanguage(locale);
let nodes;
if ( category ) {
const data = await this.searchClient.queryForType(
const data = await client.queryForType(
'stream_tag', query, generateUUID(), {
hitsPerPage: 100,
faceFilters: [
@ -935,7 +942,7 @@ export default class TwitchData extends Module {
nodes = get('streamTags.hits', data);
} else {
const data = await this.searchClient.queryForType(
const data = await client.queryForType(
'tag', query, generateUUID(), {
hitsPerPage: 100,
facetFilters: [

View file

@ -15,6 +15,35 @@
100% { transform: rotateY(90deg) rotateX(0deg) }
}
.ffz--player-meta-tray {
position: absolute;
bottom: 100%;
margin-bottom: 0.5rem;
display: inline-flex;
color: var(--color-text-overlay);
.ffz-stat {
background-color: var(--color-background-pill);
color: var(--color-text-overlay);
display: inline-block;
position: relative;
line-height: 1;
text-align: center;
white-space: nowrap;
border-radius: 1000px;
padding: 0.3rem 0.5rem;
font-size: 75%;
}
.ffz-stat-text {
font-family: "Helvetica Neue",sans-serif;
font-variant-numeric: tabular-nums;
}
}
.ffz-i-t-reset.loading,
.ffz-i-t-reset-clicked.loading,
.ffz-i-zreknarf.loading {