diff --git a/package.json b/package.json index 38ee60e2..df82b687 100755 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/sites/twitch-twilight/modules/channel.js b/src/sites/twitch-twilight/modules/channel.js index 2ae5c930..eb65b73d 100644 --- a/src/sites/twitch-twilight/modules/channel.js +++ b/src/sites/twitch-twilight/modules/channel.js @@ -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 ) diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index f4186ec9..56fd18d7 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -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) }); diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-blur-title.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-blur-title.scss new file mode 100644 index 00000000..0299772c --- /dev/null +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-blur-title.scss @@ -0,0 +1,3 @@ +.ffz-hide-thumbnail h3 { + filter: blur(0.5rem); +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-no-blur.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-no-blur.scss new file mode 100644 index 00000000..0db661c0 --- /dev/null +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-no-blur.scss @@ -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; + } + } +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-reveal.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-reveal.scss new file mode 100644 index 00000000..72375ce9 --- /dev/null +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/dir-reveal.scss @@ -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; + } +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss index 1e2dea84..eda91ad8 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/portrait-metadata-top.scss @@ -1,3 +1,6 @@ -.channel-root__scroll-area--theatre-mode .channel-info-bar { - bottom: calc(10rem + var(--ffz-chat-height)) !important; +.channel-root__scroll-area--theatre-mode { + .channel-info-content > div:first-child { + right: 40rem !important; + bottom: calc(10rem + var(--ffz-chat-height)) !important; + } } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/browse_popular.gql b/src/sites/twitch-twilight/modules/directory/browse_popular.gql deleted file mode 100644 index 419b25a3..00000000 --- a/src/sites/twitch-twilight/modules/directory/browse_popular.gql +++ /dev/null @@ -1,5 +0,0 @@ -fragment browsePagePopularStreamsWithTagsEdge on StreamEdge { - node { - createdAt - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/browse_popular.js b/src/sites/twitch-twilight/modules/directory/browse_popular.js deleted file mode 100644 index 99310091..00000000 --- a/src/sites/twitch-twilight/modules/directory/browse_popular.js +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/followed_channels.gql b/src/sites/twitch-twilight/modules/directory/followed_channels.gql deleted file mode 100644 index 730d5c2a..00000000 --- a/src/sites/twitch-twilight/modules/directory/followed_channels.gql +++ /dev/null @@ -1,21 +0,0 @@ -query FollowedChannels_RENAME2 { - currentUser { - follows { - edges { - node { - stream { - createdAt - type - } - } - } - } - followedLiveUsers { - nodes { - stream { - createdAt - } - } - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/followed_hosts.gql b/src/sites/twitch-twilight/modules/directory/followed_hosts.gql deleted file mode 100644 index c5ddb257..00000000 --- a/src/sites/twitch-twilight/modules/directory/followed_hosts.gql +++ /dev/null @@ -1,14 +0,0 @@ -query FollowingHosts_CurrentUser { - currentUser { - followedHosts { - nodes { - profileImageURL(width: 50) - hosting { - stream { - createdAt - } - } - } - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/followed_index.gql b/src/sites/twitch-twilight/modules/directory/followed_index.gql deleted file mode 100644 index c926bbad..00000000 --- a/src/sites/twitch-twilight/modules/directory/followed_index.gql +++ /dev/null @@ -1,21 +0,0 @@ -query FollowedIndex_CurrentUser { - currentUser { - followedLiveUsers { - nodes { - stream { - createdAt - } - } - } - followedHosts { - nodes { - profileImageURL(width: 50) - hosting { - stream { - createdAt - } - } - } - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/followed_live.gql b/src/sites/twitch-twilight/modules/directory/followed_live.gql deleted file mode 100644 index 515bd990..00000000 --- a/src/sites/twitch-twilight/modules/directory/followed_live.gql +++ /dev/null @@ -1,13 +0,0 @@ -query FollowingLive_CurrentUser { - currentUser { - followedLiveUsers { - edges { - node { - stream { - createdAt - } - } - } - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/following.jsx b/src/sites/twitch-twilight/modules/directory/following.jsx index 9cc8be77..66446ced 100644 --- a/src/sites/twitch-twilight/modules/directory/following.jsx +++ b/src/sites/twitch-twilight/modules/directory/following.jsx @@ -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)); } diff --git a/src/sites/twitch-twilight/modules/directory/game.gql b/src/sites/twitch-twilight/modules/directory/game.gql deleted file mode 100644 index 392acc1e..00000000 --- a/src/sites/twitch-twilight/modules/directory/game.gql +++ /dev/null @@ -1,5 +0,0 @@ -fragment directoryPageGameStreamWithTagsEdge on StreamEdge { - node { - createdAt - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/game.jsx b/src/sites/twitch-twilight/modules/directory/game.jsx index 9c4f5718..b9dbfd42 100644 --- a/src/sites/twitch-twilight/modules/directory/game.jsx +++ b/src/sites/twitch-twilight/modules/directory/game.jsx @@ -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(); - } - }*/ } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 585a123e..a8b46541 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -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 = (
+
+
+
+
+
+ {el.ffz_uptime_span =

} +

+
+ {this.i18n.t('metadata.uptime.tooltip', 'Stream Uptime')} + {el.ffz_uptime_tt =
} +
+
+
)); + } + + 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 = (
-
-
-
-
-
- {inst.ffz_uptime_span =

} -

-
- {this.i18n.t('metadata.uptime.tooltip', 'Stream Uptime')} - {inst.ffz_uptime_tt =
} -
-
-
)); - } - - 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 {
); - } + }*/ routeClick(event, url) { diff --git a/src/sites/twitch-twilight/modules/directory/recommended_channels.gql b/src/sites/twitch-twilight/modules/directory/recommended_channels.gql deleted file mode 100644 index f8eab56f..00000000 --- a/src/sites/twitch-twilight/modules/directory/recommended_channels.gql +++ /dev/null @@ -1,10 +0,0 @@ -query RecommendedChannels { - recommendedStreams { - edges { - node { - createdAt - type - } - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/sidenav_subscribed.gql b/src/sites/twitch-twilight/modules/directory/sidenav_subscribed.gql deleted file mode 100644 index 417e545e..00000000 --- a/src/sites/twitch-twilight/modules/directory/sidenav_subscribed.gql +++ /dev/null @@ -1,14 +0,0 @@ -query { - currentUser { - subscribedChannels { - edges { - node { - stream { - type - createdAt - } - } - } - } - } -} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/layout.js b/src/sites/twitch-twilight/modules/layout.js index e431938d..36cd26d7 100644 --- a/src/sites/twitch-twilight/modules/layout.js +++ b/src/sites/twitch-twilight/modules/layout.js @@ -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() { diff --git a/src/sites/twitch-twilight/styles/directory.scss b/src/sites/twitch-twilight/styles/directory.scss index 9b92b76f..cbd246f0 100644 --- a/src/sites/twitch-twilight/styles/directory.scss +++ b/src/sites/twitch-twilight/styles/directory.scss @@ -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); } } } diff --git a/src/utilities/compat/elemental.js b/src/utilities/compat/elemental.js index f21f2ab1..0a2b99e6 100644 --- a/src/utilities/compat/elemental.js +++ b/src/utilities/compat/elemental.js @@ -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,15 +273,17 @@ export class ElementalWrapper extends EventEmitter { this.instances.add(el); this.count++; - const remove_check = new MutationObserver(() => { - requestAnimationFrame(() => { - if ( ! document.contains(el) ) - this.remove(el); + if ( this.check_removal ) { + const remove_check = new MutationObserver(() => { + requestAnimationFrame(() => { + if ( ! document.contains(el) ) + this.remove(el); + }); }); - }); - remove_check.observe(el.parentNode, {childList: true}); - el[this.remove_param] = remove_check; + remove_check.observe(el.parentNode, {childList: true}); + el[this.remove_param] = remove_check; + } if ( this.opts ) { const observer = new MutationObserver(muts => {