1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-28 05:15:54 +00:00
* Added: Option to only hide side navigation when the page is in portrait mode.
* Added: Option to reveal hidden thumbnails after a short delay.
* Changed: Hidden thumbnails are now blurred by default, rather than replaced with a generic live thumbnail.
* Fixed: Directory features.
* Fixed: Position of theater metadata when in portrait mode.
This commit is contained in:
SirStendec 2020-07-16 16:34:03 -04:00
parent 22fd300b28
commit 0003e6e96d
22 changed files with 272 additions and 547 deletions

View file

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

View file

@ -37,11 +37,11 @@ export default class Channel extends Module {
}
});
this.SideNav = this.elemental.define(
/*this.SideNav = this.elemental.define(
'side-nav', '.side-bar-contents .side-nav-section:first-child',
null,
{childNodes: true, subtree: true}, 1
);
);*/
this.ChannelRoot = this.elemental.define(
'channel-root', '.channel-root',
@ -59,9 +59,9 @@ export default class Channel extends Module {
onEnable() {
this.updateChannelColor();
this.SideNav.on('mount', this.updateHidden, this);
this.SideNav.on('mutate', this.updateHidden, this);
this.SideNav.each(el => this.updateHidden(el));
//this.SideNav.on('mount', this.updateHidden, this);
//this.SideNav.on('mutate', this.updateHidden, this);
//this.SideNav.each(el => this.updateHidden(el));
this.ChannelRoot.on('mount', this.updateRoot, this);
this.ChannelRoot.on('mutate', this.updateRoot, this);
@ -88,7 +88,7 @@ export default class Channel extends Module {
}
}
updateHidden(el) { // eslint-disable-line class-methods-use-this
/*updateHidden(el) { // eslint-disable-line class-methods-use-this
if ( ! el._ffz_raf )
el._ffz_raf = requestAnimationFrame(() => {
el._ffz_raf = null;
@ -102,7 +102,7 @@ export default class Channel extends Module {
}
});
}
}*/
updateSubscription(login) {
if ( this._subbed_login === login )

View file

@ -18,7 +18,7 @@ const CLASSES = {
'side-friends': '.side-nav .online-friends',
'side-closed-friends': '.side-nav--collapsed .online-friends',
'side-closed-rec-channels': '.side-nav--collapsed .recommended-channels,.side-nav--collapsed .side-nav-section + .side-nav-section:not(.online-friends)',
'side-offline-channels': '.side-nav-card.ffz--offline-side-nav',
'side-offline-channels': '.ffz--side-nav-card-offline',
'side-rerun-channels': '.side-nav .ffz--side-nav-card-rerun',
'community-highlights': '.community-highlight-stack__card',
@ -34,7 +34,7 @@ const CLASSES = {
'pinned-cheer': '.pinned-cheer,.pinned-cheer-v2,.channel-leaderboard',
'whispers': 'body .whispers-open-threads,.tw-core-button[data-a-target="whisper-box-button"]',
'dir-live-ind': '.preview-card[data-ffz-type="live"] .tw-channel-status-text-indicator,.live-channel-card:not([data-a-target*="host"]) .stream-type-indicator.stream-type-indicator--live,.stream-thumbnail__card .stream-type-indicator.stream-type-indicator--live,.preview-card .stream-type-indicator.stream-type-indicator--live,.preview-card .preview-card-stat.preview-card-stat--live',
'dir-live-ind': '.live-channel-card[data-ffz-type="live"] .tw-channel-status-text-indicator, article[data-ffz-type="live"] .tw-channel-status-text-indicator',
'profile-hover': '.preview-card .tw-relative:hover .ffz-channel-avatar',
'not-live-bar': 'div[data-test-selector="non-live-video-banner-layout"]',
'channel-live-ind': '.channel-header__user .tw-channel-status-text-indicator,.channel-info-content .user-avatar-animated__live',
@ -111,14 +111,28 @@ export default class CSSTweaks extends Module {
});
this.settings.add('layout.side-nav.show', {
default: true,
default: 1,
requires: ['layout.use-portrait'],
process(ctx, val) {
if ( val === 2 )
return ! ctx.get('layout.use-portrait');
return val;
},
ui: {
sort: -1,
path: 'Appearance > Layout >> Side Navigation',
title: 'Display Side Navigation',
component: 'setting-check-box'
component: 'setting-select-box',
data: [
{value: 0, title: 'Never'},
{value: 1, title: 'Always'},
{value: 2, title: 'Hide in Portrait'}
]
},
changed: val => this.toggle('hide-side-nav', !val)
});

View file

@ -0,0 +1,3 @@
.ffz-hide-thumbnail h3 {
filter: blur(0.5rem);
}

View file

@ -0,0 +1,18 @@
.ffz-hide-thumbnail a[data-a-target="preview-card-image-link"] {
.tw-aspect {
&:before {
content: '';
opacity: 1;
position: absolute;
top: 0; left: 0;
height: 100%;
width: 100%;
background: url("https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg") no-repeat;
background-size: cover;
}
img {
opacity: 0;
}
}
}

View file

@ -0,0 +1,17 @@
.ffz-hide-thumbnail:hover {
.tw-aspect:before {
transition: opacity 0.2s;
transition-delay: 1s;
opacity: 0 !important;
}
img {
opacity: 1 !important;
}
img,h3 {
transition: opacity 0.2s, filter 0.2s;
transition-delay: 1s;
filter: blur(0) !important;
}
}

View file

@ -1,3 +1,6 @@
.channel-root__scroll-area--theatre-mode .channel-info-bar {
.channel-root__scroll-area--theatre-mode {
.channel-info-content > div:first-child {
right: 40rem !important;
bottom: calc(10rem + var(--ffz-chat-height)) !important;
}
}

View file

@ -1,5 +0,0 @@
fragment browsePagePopularStreamsWithTagsEdge on StreamEdge {
node {
createdAt
}
}

View file

@ -1,40 +0,0 @@
'use strict';
// ============================================================================
// Directory (Following, for now)
// ============================================================================
import {SiteModule} from 'utilities/module';
import {get} from 'utilities/object';
import BROWSE_POPULAR from './browse_popular.gql';
export default class BrowsePopular extends SiteModule {
constructor(...args) {
super(...args);
this.inject('site.apollo');
this.inject('site.fine');
this.inject('settings');
this.apollo.registerModifier('BrowsePage_Popular', BROWSE_POPULAR);
this.apollo.registerModifier('BrowsePage_Popular', res => this.modifyStreams(res), false);
}
/*onEnable() {
// Popular Directory Channel Cards
this.apollo.ensureQuery(
'BrowsePage_Popular',
'data.streams.edges.0.node.createdAt'
);
}*/
modifyStreams(res) { // eslint-disable-line class-methods-use-this
const edges = get('data.streams.edges', res);
if ( ! edges || ! edges.length )
return res;
res.data.streams.edges = this.parent.processNodes(edges);
return res;
}
}

View file

@ -1,21 +0,0 @@
query FollowedChannels_RENAME2 {
currentUser {
follows {
edges {
node {
stream {
createdAt
type
}
}
}
}
followedLiveUsers {
nodes {
stream {
createdAt
}
}
}
}
}

View file

@ -1,14 +0,0 @@
query FollowingHosts_CurrentUser {
currentUser {
followedHosts {
nodes {
profileImageURL(width: 50)
hosting {
stream {
createdAt
}
}
}
}
}
}

View file

@ -1,21 +0,0 @@
query FollowedIndex_CurrentUser {
currentUser {
followedLiveUsers {
nodes {
stream {
createdAt
}
}
}
followedHosts {
nodes {
profileImageURL(width: 50)
hosting {
stream {
createdAt
}
}
}
}
}
}

View file

@ -1,13 +0,0 @@
query FollowingLive_CurrentUser {
currentUser {
followedLiveUsers {
edges {
node {
stream {
createdAt
}
}
}
}
}
}

View file

@ -11,13 +11,6 @@ import {get} from 'utilities/object';
import Popper from 'popper.js';
import {makeReference} from 'utilities/tooltip';
/*import FOLLOWED_INDEX from './followed_index.gql';
import FOLLOWED_HOSTS from './followed_hosts.gql';
import FOLLOWED_CHANNELS from './followed_channels.gql';
import FOLLOWED_LIVE from './followed_live.gql';
import SUBSCRIBED_CHANNELS from './sidenav_subscribed.gql';
import RECOMMENDED_CHANNELS from './recommended_channels.gql';*/
export default class Following extends SiteModule {
constructor(...args) {
super(...args);
@ -66,39 +59,6 @@ export default class Following extends SiteModule {
changed: () => this.parent.DirectoryCard.forceUpdate()
});
/*this.apollo.registerModifier('FollowedChannels_RENAME2', FOLLOWED_CHANNELS);
this.apollo.registerModifier('SideNav_SubscribedChannels', SUBSCRIBED_CHANNELS);
this.apollo.registerModifier('RecommendedChannels', RECOMMENDED_CHANNELS);
this.apollo.registerModifier('FollowedIndex_CurrentUser', FOLLOWED_INDEX);
this.apollo.registerModifier('FollowingLive_CurrentUser', FOLLOWED_LIVE);
this.apollo.registerModifier('FollowingHosts_CurrentUser', FOLLOWED_HOSTS);
this.apollo.registerModifier('FollowedChannels_RENAME2', res => this.modifyLiveUsers(res), false);
this.apollo.registerModifier('SideNav_SubscribedChannels', res => this.modifyLiveUsers(res, 'subscribedChannels'), false);
this.apollo.registerModifier('RecommendedChannels', res => this.modifyLiveUsers(res, 'recommendations.liveRecommendations'), false);
this.apollo.registerModifier('FollowingLive_CurrentUser', res => this.modifyLiveUsers(res), false);
this.apollo.registerModifier('FollowingHosts_CurrentUser', res => this.modifyLiveHosts(res), false);
this.apollo.registerModifier('FollowedIndex_CurrentUser', res => {
this.modifyLiveUsers(res);
this.modifyLiveHosts(res);
}, false);
this.apollo.registerModifier('Shelves', res => {
const shelves = get('data.shelves.edges', res);
if ( ! Array.isArray(shelves) )
return;
for(const shelf of shelves) {
const edges = get('node.content.edges', shelf);
if ( ! Array.isArray(edges) )
continue;
shelf.node.content.edges = this.parent.processNodes(edges);
}
}, false);*/
this.hosts = new WeakMap;
}
@ -169,51 +129,7 @@ export default class Following extends SiteModule {
return res;
}
ensureQueries () {
/*this.apollo.ensureQuery(
'FollowedChannels_RENAME2',
'data.currentUser.followedLiveUsers.nodes.0.stream.createdAt'
);
this.apollo.ensureQuery(
'SideNav_SubscribedChannels',
'data.currentUser.subscribedChannels.edges.0.node.stream.createdAt'
);
this.apollo.ensureQuery(
'RecommendedChannels',
'data.currentUser.recommendations.liveRecommendations.nodes.0.createdAt'
);*/
/*if ( this.router.current_name !== 'dir-following' )
return;
const bit = this.router.match[1];
if ( ! bit )
this.apollo.ensureQuery(
'FollowedIndex_CurrentUser',
n =>
get('data.currentUser.followedLiveUsers.nodes.0.stream.createdAt', n) !== undefined ||
get('data.currentUser.followedHosts.nodes.0.hosting.stream.createdAt', n) !== undefined
);
/*else if ( bit === 'live' )
this.apollo.ensureQuery(
'FollowingLive_CurrentUser',
'data.currentUser.followedLiveUsers.nodes.0.stream.createdAt'
);*/
/*else if ( bit === 'hosts' )
this.apollo.ensureQuery(
'FollowingHosts_CurrentUser',
'data.currentUser.followedHosts.nodes.0.hosting.stream.createdAt'
);*/
}
onEnable() {
this.ensureQueries();
document.body.addEventListener('click', this.destroyHostMenu.bind(this));
}

View file

@ -1,5 +0,0 @@
fragment directoryPageGameStreamWithTagsEdge on StreamEdge {
node {
createdAt
}
}

View file

@ -8,7 +8,6 @@ import {SiteModule} from 'utilities/module';
import {createElement} from 'utilities/dom';
import { get } from 'utilities/object';
//import GAME_QUERY from './game.gql';
export default class Game extends SiteModule {
constructor(...args) {
@ -17,7 +16,6 @@ export default class Game extends SiteModule {
this.inject('site.fine');
this.inject('site.apollo');
//this.inject('metadata');
this.inject('i18n');
this.inject('settings');
@ -26,28 +24,8 @@ export default class Game extends SiteModule {
n => n.props && n.props.data && n.getBannerImage && n.getFollowButton,
['dir-game-index', 'dir-community', 'dir-game-videos', 'dir-game-clips', 'dir-game-details']
);
/*this.apollo.registerModifier('DirectoryPage_Game', GAME_QUERY);
this.apollo.registerModifier('DirectoryPage_Game', res => {
/*setTimeout(() =>
this.apollo.ensureQuery(
'DirectoryPage_Game',
'data.game.streams.edges.0.node.createdAt'
), 500);* /
this.modifyStreams(res);
}, false);*/
}
/*modifyStreams(res) { // eslint-disable-line class-methods-use-this
const edges = get('data.game.streams.edges', res);
if ( ! edges || ! edges.length )
return res;
res.data.game.streams.edges = this.parent.processNodes(edges, true);
return res;
}*/
onEnable() {
this.GameHeader.on('mount', this.updateGameHeader, this);
this.GameHeader.on('update', this.updateGameHeader, this);
@ -149,67 +127,8 @@ export default class Game extends SiteModule {
values.splice(idx, 1);
this.settings.provider.set(setting, values);
this.parent.DirectoryCard.forceUpdate();
this.parent.updateCards();
update_func();
}
}
/*unmountGameHeader(inst) { // eslint-disable-line class-methods-use-this
const timers = inst._ffz_meta_timers;
if ( timers )
for(const key in timers)
if ( timers[key] )
clearTimeout(timers[key]);
}
updateGameHeader(inst) {
this.updateMetadata(inst);
}
updateMetadata(inst, keys) {
const container = this.fine.getChildNode(inst),
wrapper = container && container.querySelector && container.querySelector('.side-nav-directory-info__info-wrapper > div + div');
if ( ! inst._ffz_mounted || ! wrapper )
return;
const metabar = wrapper;
if ( ! keys )
keys = this.metadata.keys;
else if ( ! Array.isArray(keys) )
keys = [keys];
const timers = inst._ffz_meta_timers = inst._ffz_meta_timers || {},
refresh_func = key => this.updateMetadata(inst, key),
data = {
directory: inst.props.data.directory,
type: inst.props.directoryType,
name: inst.props.directoryName,
_mt: 'directory',
_inst: inst
}
for(const key of keys)
this.metadata.render(key, data, metabar, timers, refresh_func);
}
generateClickHandler(setting) {
return (data, event, update_func) => {
const values = this.settings.provider.get(setting, []),
game = data.name,
idx = values.indexOf(game);
if ( idx === -1 )
values.push(game)
else
values.splice(idx, 1);
this.settings.provider.set(setting, values);
this.parent.DirectoryCard.forceUpdate();
update_func();
}
}*/
}

View file

@ -9,10 +9,7 @@ import {duration_to_string} from 'utilities/time';
import {createElement} from 'utilities/dom';
import {get} from 'utilities/object';
import Following from './following';
import Game from './game';
//import BrowsePopular from './browse_popular';
export const CARD_CONTEXTS = ((e ={}) => {
e[e.SingleGameList = 1] = 'SingleGameList';
@ -33,24 +30,21 @@ export default class Directory extends SiteModule {
this.should_enable = true;
this.inject('site.elemental');
this.inject('site.fine');
this.inject('site.router');
this.inject('site.apollo');
this.inject('site.css_tweaks');
this.inject('site.web_munch');
this.inject('site.twitch_data');
this.inject('i18n');
this.inject('settings');
this.inject(Following);
//this.inject(Following);
this.inject(Game);
//this.inject(BrowsePopular);
this.DirectoryCard = this.fine.define(
'directory-card',
n => n.renderTitles && n.renderIconicImage,
DIR_ROUTES
this.DirectoryCard = this.elemental.define(
'directory-card', '.live-channel-card,article[data-a-target^="followed-vod-"],article[data-a-target^="card-"],div[data-a-target^="video-tower-card-"] article,div[data-a-target^="clips-card-"] article,.shelf-card__impression-wrapper article',
DIR_ROUTES, null, 0, 0
);
this.DirectoryShelf = this.fine.define(
@ -59,23 +53,47 @@ export default class Directory extends SiteModule {
DIR_ROUTES
);
this.DirectorySuggestedVideos = this.fine.define(
'directory-suggested-videos',
n => n.props && n.props.directoryWidth && n.props.data && n.render && n.render.toString().includes('SuggestedVideos'),
DIR_ROUTES
);
this.DirectoryLatestVideos = this.fine.define(
'directory-latest-videos',
n => n.props && n.props.component === 'LatestVideosFromFollowedCarousel',
DIR_ROUTES
);
this.settings.add('directory.hidden.style', {
default: 2,
ui: {
path: 'Directory > Channels >> Blocked and Hidden Categories',
title: 'Hidden Style',
component: 'setting-select-box',
data: [
{value: 0, title: 'Replace Image'},
{value: 1, title: 'Replace Image and Blur Title'},
{value: 2, title: 'Blur Image'},
{value: 3, title: 'Blur Image and Title'}
]
},
changed: val => {
this.css_tweaks.toggle('dir-no-blur', val < 2);
this.css_tweaks.toggle('dir-blur-title', val === 1 || val === 3);
}
});
this.settings.add('directory.hidden.reveal', {
default: false,
ui: {
path: 'Directory > Channels >> Blocked and Hidden Categories',
title: 'Reveal hidden entries on mouse hover.',
component: 'setting-check-box'
},
changed: val => this.css_tweaks.toggle('dir-reveal', val)
});
this.settings.add('directory.uptime', {
default: 1,
ui: {
path: 'Directory > Channels @{"description": "**Note:** These settings do not currently work due to changes made by Twitch to how the directory works."} >> Appearance',
path: 'Directory > Channels >> Appearance',
title: 'Stream Uptime',
description: 'Display the stream uptime on the channel cards.',
component: 'setting-select-box',
@ -87,11 +105,11 @@ export default class Directory extends SiteModule {
]
},
changed: () => this.DirectoryCard.forceUpdate()
changed: () => this.updateCards()
});
this.settings.add('directory.show-channel-avatars', {
/*this.settings.add('directory.show-channel-avatars', {
default: true,
ui: {
@ -100,8 +118,8 @@ export default class Directory extends SiteModule {
component: 'setting-check-box'
},
changed: () => this.DirectoryCard.forceUpdate()
});
changed: () => this.updateCards()
});*/
this.settings.add('directory.hide-live', {
@ -125,12 +143,7 @@ export default class Directory extends SiteModule {
component: 'setting-check-box'
},
changed: () => {
//this.DirectoryCard.forceUpdate();
for(const inst of this.DirectoryCard.instances)
this.updateCard(inst);
}
changed: () => this.updateCards()
});
this.settings.add('directory.hide-recommended', {
@ -144,7 +157,7 @@ export default class Directory extends SiteModule {
changed: () => this.DirectoryShelf.forceUpdate()
});
this.settings.add('directory.hide-viewing-history', {
/*this.settings.add('directory.hide-viewing-history', {
default: false,
ui: {
path: 'Directory > Following >> Categories',
@ -164,22 +177,30 @@ export default class Directory extends SiteModule {
},
changed: () => this.DirectoryLatestVideos.forceUpdate()
});
});*/
this.routeClick = this.routeClick.bind(this);
}
async onEnable() {
onEnable() {
this.css_tweaks.toggleHide('profile-hover', this.settings.get('directory.show-channel-avatars') === 2);
this.css_tweaks.toggleHide('dir-live-ind', this.settings.get('directory.hide-live'));
this.css_tweaks.toggle('dir-reveal', this.settings.get('directory.hidden.reveal'));
this.on('i18n:update', () => this.DirectoryCard.forceUpdate());
const blur = this.settings.get('directory.hidden.style');
const t = this,
React = await this.web_munch.findModule('react');
this.css_tweaks.toggle('dir-no-blur', blur < 2);
this.css_tweaks.toggle('dir-blur-title', blur === 1 || blur === 3);
const createElement = React && React.createElement;
this.on('i18n:update', () => this.updateCards());
this.DirectoryCard.on('mount', this.updateCard, this);
this.DirectoryCard.on('mutate', this.updateCard, this);
this.DirectoryCard.on('unmount', this.clearCard, this);
this.DirectoryCard.each(el => this.updateCard(el));
const t = this;
this.DirectoryShelf.ready(cls => {
const old_render = cls.prototype.render;
@ -201,44 +222,7 @@ export default class Directory extends SiteModule {
this.DirectoryShelf.forceUpdate();
});
this.DirectorySuggestedVideos.ready(cls => {
const old_render = cls.prototype.render;
cls.prototype.render = function() {
try {
if ( t.settings.get('directory.hide-viewing-history') )
return null;
} catch(err) {
t.log.capture(err);
}
return old_render.call(this);
}
this.DirectorySuggestedVideos.forceUpdate();
});
this.DirectoryLatestVideos.ready(cls => {
const old_render = cls.prototype.render;
cls.prototype.render = function() {
if ( ! this.props || this.props.component !== 'LatestVideosFromFollowedCarousel' )
return old_render.call(this);
try {
if ( t.settings.get('directory.hide-latest-videos') )
return null;
} catch(err) {
t.log.capture(err);
}
return old_render.call(this);
}
this.DirectoryLatestVideos.forceUpdate();
});
this.DirectoryCard.ready((cls, instances) => {
/*this.DirectoryCard.ready((cls, instances) => {
//const old_render = cls.prototype.render,
const old_render_iconic = cls.prototype.renderIconicImage,
old_render_titles = cls.prototype.renderTitles;
@ -248,7 +232,7 @@ export default class Directory extends SiteModule {
return null;
return old_render.call(this);
}*/
}*
cls.prototype.renderIconicImage = function() {
if ( this.props.context !== CARD_CONTEXTS.SingleChannelList &&
@ -308,89 +292,127 @@ export default class Directory extends SiteModule {
this.DirectoryCard.forceUpdate();
// Game Directory Channel Cards
// TODO: Better query handling.
/*this.apollo.ensureQuery(
'DirectoryPage_Game',
'data.game.streams.edges.0.node.createdAt'
);*/
for(const inst of instances)
this.updateCard(inst);
});
this.DirectoryCard.on('update', this.updateCard, this);
this.DirectoryCard.on('mount', this.updateCard, this);
this.DirectoryCard.on('unmount', this.clearCard, this);
// TODO: Queries
this.DirectoryCard.on('unmount', this.clearCard, this);*/
}
updateCard(inst) {
const container = this.fine.getChildNode(inst);
if ( ! container )
updateCard(el) {
const react = this.fine.getReactInstance(el);
if ( ! react )
return;
const props = inst.props,
game = props.gameTitle || props.playerMetadataGame || (props.trackingProps && props.trackingProps.categoryName);
let props = react.child?.memoizedProps;
if ( ! props?.channelLogin )
props = react.return?.stateNode?.props;
container.classList.toggle('ffz-hide-thumbnail', this.settings.provider.get('directory.game.hidden-thumbnails', []).includes(game));
container.dataset.ffzType = props.streamType;
if ( ! props?.channelLogin )
props = react.return?.return?.return?.memoizedProps;
if ( ! props?.channelLogin )
return;
const game = props.gameTitle || props.trackingProps?.categoryName;
el.classList.toggle('ffz-hide-thumbnail', this.settings.provider.get('directory.game.hidden-thumbnails', []).includes(game));
el.dataset.ffzType = props.streamType;
const should_hide = (props.streamType === 'rerun' && this.settings.get('directory.hide-vodcasts')) ||
(props.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game));
let hide_container = container.closest('.stream-thumbnail,[style*="order:"]');
(props.context != null && props.context !== CARD_CONTEXTS.SingleGameList && this.settings.provider.get('directory.game.blocked-games', []).includes(game));
let hide_container = el.closest('.tw-tower > div');
if ( ! hide_container )
hide_container = container.closest('.tw-mg-b-2');
hide_container = el;
if ( ! hide_container )
hide_container = container;
if ( hide_container.querySelectorAll('.preview-card').length < 2 )
if ( hide_container.querySelectorAll('a[data-a-target="preview-card-image-link"]').length < 2 )
hide_container.classList.toggle('tw-hide', should_hide);
//this.log.info('Card Update', inst.props.channelDisplayName, is_video ? 'Video' : 'Live', is_host ? 'Host' : 'Not-Host', inst);
this.updateUptime(inst, 'props.currentViewerCount.createdAt');
this.updateAvatar(inst);
this.following.updateChannelCard(inst);
this.updateUptime(el, props);
}
clearCard(inst) {
this.clearUptime(inst);
updateCards() {
this.DirectoryCard.each(el => this.updateCard(el));
this.emit(':update-cards');
}
clearCard(el) {
this.clearUptime(el);
}
processNodes(edges, is_game_query = false, blocked_games) {
const out = [];
updateUptime(el, props) {
if ( ! document.contains(el) )
return this.clearUptime(el);
if ( ! Array.isArray(blocked_games) )
blocked_games = this.settings.provider.get('directory.game.blocked-games', []);
const container = el.querySelector('.tw-media-card-image__corners'),
setting = this.settings.get('directory.uptime');
for(const edge of edges) {
if ( ! edge )
continue;
if ( ! container || setting === 0 || props.viewCount || props.animatedImageProps )
return this.clearUptime(el);
const node = edge.node || edge,
stream = node.stream || node;
let created_at = props.createdAt;
if ( stream.viewersCount ) {
const store = stream.viewersCount = new Number(stream.viewersCount || 0);
store.createdAt = stream.createdAt;
store.title = stream.title;
//store.game = stream.game;
if ( ! created_at ) {
if ( el.ffz_stream_meta === undefined ) {
el.ffz_stream_meta = null;
this.twitch_data.getStreamMeta(null, props.channelLogin).then(data => {
el.ffz_stream_meta = data;
this.updateUptime(el, props);
});
}
if ( is_game_query || (! stream.game || stream.game && ! blocked_games.includes(stream.game.name)) )
out.push(edge);
created_at = el.ffz_stream_meta?.createdAt;
}
return out;
const up_since = created_at && new Date(created_at),
uptime = up_since && Math.floor((Date.now() - up_since) / 1000) || 0;
if ( uptime < 1 )
return this.clearUptime(el);
const up_text = duration_to_string(uptime, false, false, false, setting === 1);
if ( ! el.ffz_uptime_el ) {
el.ffz_uptime_el = container.querySelector('.ffz-uptime-element');
if ( ! el.ffz_uptime_el )
container.appendChild(el.ffz_uptime_el = (<div class="ffz-uptime-element tw-absolute tw-right-0 tw-top-0 tw-mg-1">
<div class="tw-relative tw-tooltip-wrapper">
<div class="tw-border-radius-small tw-c-background-overlay tw-c-text-overlay tw-flex tw-pd-x-05">
<div class="tw-flex tw-c-text-live">
<figure class="ffz-i-clock" />
</div>
{el.ffz_uptime_span = <p />}
</div>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{this.i18n.t('metadata.uptime.tooltip', 'Stream Uptime')}
{el.ffz_uptime_tt = <div class="tw-pd-t-05" />}
</div>
</div>
</div>));
}
if ( ! el.ffz_update_timer )
el.ffz_update_timer = setInterval(this.updateUptime.bind(this, el, props), 1000);
el.ffz_uptime_span.textContent = up_text;
if ( el.ffz_last_created_at !== created_at ) {
el.ffz_uptime_tt.textContent = this.i18n.t(
'metadata.uptime.since',
'(since {since,datetime})',
{since: up_since}
);
el.ffz_last_created_at = created_at;
}
}
@ -410,77 +432,7 @@ export default class Directory extends SiteModule {
}
updateUptime(inst, created_path) {
const container = this.fine.getChildNode(inst),
card = container && container.querySelector && container.querySelector('.preview-card-overlay'),
setting = this.settings.get('directory.uptime');
if ( ! card || setting === 0 || ! inst.props || inst.props.viewCount || inst.props.animatedImageProps )
return this.clearUptime(inst);
let created_at = inst.props.createdAt || get(created_path, inst);
if ( ! created_at ) {
if ( inst.ffz_stream_meta === undefined ) {
inst.ffz_stream_meta = null;
this.twitch_data.getStreamMeta(inst.props.channelId, inst.props.channelLogin).then(data => {
inst.ffz_stream_meta = data;
this.updateUptime(inst, created_path);
});
}
if ( inst.ffz_stream_meta )
created_at = inst.ffz_stream_meta.createdAt;
}
if ( ! created_at )
return this.clearUptime(inst);
const up_since = created_at && new Date(created_at),
uptime = up_since && Math.floor((Date.now() - up_since) / 1000) || 0;
if ( uptime < 1 )
return this.clearUptime(inst);
const up_text = duration_to_string(uptime, false, false, false, setting === 1);
if ( ! inst.ffz_uptime_el ) {
inst.ffz_uptime_el = card.querySelector('.ffz-uptime-element');
if ( ! inst.ffz_uptime_el )
card.appendChild(inst.ffz_uptime_el = (<div class="ffz-uptime-element tw-absolute tw-right-0 tw-top-0 tw-mg-1">
<div class="tw-relative tw-tooltip-wrapper">
<div class="preview-card-stat tw-align-items-center tw-border-radi-us-small tw-c-background-overlay tw-c-text-overlay tw-flex tw-font-size-6 tw-justify-content-center">
<div class="tw-flex tw-c-text-live">
<figure class="ffz-i-clock" />
</div>
{inst.ffz_uptime_span = <p />}
</div>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{this.i18n.t('metadata.uptime.tooltip', 'Stream Uptime')}
{inst.ffz_uptime_tt = <div class="tw-pd-t-05" />}
</div>
</div>
</div>));
}
if ( ! inst.ffz_update_timer )
inst.ffz_update_timer = setInterval(this.updateUptime.bind(this, inst, created_path), 1000);
inst.ffz_uptime_span.textContent = up_text;
if ( inst.ffz_last_created_at !== created_at ) {
inst.ffz_uptime_tt.textContent = this.i18n.t(
'metadata.uptime.since',
'(since {since,datetime})',
{since: up_since}
);
inst.ffz_last_created_at = created_at;
}
}
updateAvatar(inst) {
/*updateAvatar(inst) {
const container = this.fine.getChildNode(inst),
card = container && container.querySelector && container.querySelector('.preview-card-overlay'),
setting = this.settings.get('directory.show-channel-avatars');
@ -524,7 +476,7 @@ export default class Directory extends SiteModule {
</figure>
</div>
</a>);
}
}*/
routeClick(event, url) {

View file

@ -1,10 +0,0 @@
query RecommendedChannels {
recommendedStreams {
edges {
node {
createdAt
type
}
}
}
}

View file

@ -1,14 +0,0 @@
query {
currentUser {
subscribedChannels {
edges {
node {
stream {
type
createdAt
}
}
}
}
}
}

View file

@ -189,6 +189,11 @@ export default class Layout extends Module {
this.css_tweaks.setVariable('portrait-extra-width', `${this.settings.get('layout.portrait-extra-width')}rem`);
this.css_tweaks.setVariable('portrait-extra-height', `${this.settings.get('layout.portrait-extra-height')}rem`);
this.on('site.directory:update-cards', () => {
for(const inst of this.SideBarChannels.instances)
this.updateCardClass(inst);
});
this.SideBarChannels.ready((cls, instances) => {
for(const inst of instances)
this.updateCardClass(inst);
@ -250,10 +255,17 @@ export default class Layout extends Module {
updateCardClass(inst) {
const node = this.fine.getChildNode(inst);
if ( node )
if ( node ) {
node.classList.toggle('ffz--side-nav-card-rerun',
inst.props?.tooltipContent?.props?.streamType === 'rerun'
);
node.classList.toggle('ffz--side-nav-card-offline',
inst.props?.offline === true
);
const game = inst.props?.tooltipContent?.props?.gameName || inst.props?.metadataLeft?.props?.activity?.stream?.game?.name || inst.props?.metadataLeft;
node.classList.toggle('tw-hide', this.settings.provider.get('directory.game.blocked-games', []).includes(game));
}
}
updateNavLinks() {

View file

@ -27,20 +27,10 @@
}
.ffz-hide-thumbnail {
.preview-card-thumbnail__image {
&:before {
content: '';
position: absolute;
top: 0; left: 0;
height: 100%;
width: 100%;
background: url("https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg") no-repeat;
background-size: cover;
}
.ffz-hide-thumbnail a[data-a-target="preview-card-image-link"] {
.tw-aspect {
img {
display: none;
filter: blur(2.5rem);
}
}
}

View file

@ -27,14 +27,14 @@ export default class Elemental extends Module {
}
define(key, selector, routes, opts = null, limit = 0, timeout = 5000) {
define(key, selector, routes, opts = null, limit = 0, timeout = 5000, remove = true) {
if ( this._wrappers.has(key) )
return this._wrappers.get(key);
if ( ! selector || typeof selector !== 'string' || ! selector.length )
throw new Error('cannot find definition and no selector provided');
const wrapper = new ElementalWrapper(key, selector, routes, opts, limit, timeout, this);
const wrapper = new ElementalWrapper(key, selector, routes, opts, limit, timeout, remove, this);
this._wrappers.set(key, wrapper);
return wrapper;
@ -46,6 +46,19 @@ export default class Elemental extends Module {
this._timer = Date.now();
this._updateLiveWatching();
this.checkAll();
this.cleanAll();
}
cleanAll() {
if ( this._clean_all )
cancelAnimationFrame(this._clean_all);
this._clean_all = requestAnimationFrame(() => {
this._clean_all = null;
for(const wrapper of this._wrappers.values())
wrapper.clean();
});
}
@ -160,7 +173,7 @@ export default class Elemental extends Module {
let elemental_id = 0;
export class ElementalWrapper extends EventEmitter {
constructor(name, selector, routes, opts, limit, timeout, elemental) {
constructor(name, selector, routes, opts, limit, timeout, remove, elemental) {
super();
this.id = elemental_id++;
@ -176,6 +189,7 @@ export class ElementalWrapper extends EventEmitter {
this.opts = opts;
this.limit = limit;
this.timeout = timeout;
this.check_removal = remove;
if ( this.opts && ! this.opts.childList && ! this.opts.attributes && ! this.opts.characterData )
this.opts.attributes = true;
@ -193,6 +207,14 @@ export class ElementalWrapper extends EventEmitter {
return this.limit > 0 && this.count >= this.limit;
}
clean() {
const instances = Array.from(this.instances);
for(const el of instances) {
if ( ! document.contains(el) )
this.remove(el);
}
}
schedule() {
if ( ! this._stimer )
this._stimer = setTimeout(this._schedule, 0);
@ -251,6 +273,7 @@ export class ElementalWrapper extends EventEmitter {
this.instances.add(el);
this.count++;
if ( this.check_removal ) {
const remove_check = new MutationObserver(() => {
requestAnimationFrame(() => {
if ( ! document.contains(el) )
@ -260,6 +283,7 @@ export class ElementalWrapper extends EventEmitter {
remove_check.observe(el.parentNode, {childList: true});
el[this.remove_param] = remove_check;
}
if ( this.opts ) {
const observer = new MutationObserver(muts => {