diff --git a/src/sites/twitch-twilight/modules/directory/community.js b/src/sites/twitch-twilight/modules/directory/community.js index 33d7f816..145fc5a3 100644 --- a/src/sites/twitch-twilight/modules/directory/community.js +++ b/src/sites/twitch-twilight/modules/directory/community.js @@ -5,24 +5,12 @@ // ============================================================================ import {SiteModule} from 'utilities/module'; -import {createElement as e} from 'utilities/dom'; -import {duration_to_string} from 'utilities/time'; export default class Community extends SiteModule { constructor(...args) { super(...args); - this.inject('site.fine'); - this.inject('site.router'); this.inject('site.apollo'); - this.inject('site.css_tweaks'); - - this.inject('settings'); - - this.GameHeader = this.fine.define( - 'game-header', - n => n.renderFollowButton && n.renderGameDetailsTab - ); this.apollo.registerModifier('GamePage_Game', `query { directory { @@ -41,265 +29,5 @@ export default class Community extends SiteModule { } } }`); - - this.ChannelCard = this.fine.define( - 'community-channel-card', - n => n.props && n.props.streamNode - ); - - this.apollo.registerModifier('GamePage_Game', res => this.router.current.name === 'dir-community' && this.modifyStreams(res), false); - - this.on('settings:changed:show-channel-avatar', value => { - this.css_tweaks.toggleHide('profile-hover-game', value === 2); - this.router.current.name === 'dir-community' && this.ChannelCard.forceUpdate(); - }); - - this.on('settings:changed:directory.following.uptime', () => this.router.current.name === 'dir-community' && this.ChannelCard.forceUpdate()); - } - - modifyStreams(res) { // eslint-disable-line class-methods-use-this - const newStreams = []; - - const edges = res.data.directory.streams.edges; - for (let i = 0; i < edges.length; i++) { - const edge = edges[i]; - const node = edge.node; - - const s = node.viewersCount = new Number(node.viewersCount || 0); - s.profileImageURL = node.broadcaster.profileImageURL; - s.createdAt = node.createdAt; - - newStreams.push(edge); - } - res.data.directory.streams.edges = newStreams; - return res; - } - - onEnable() { - this.GameHeader.ready((cls, instances) => { - if (this.router.current.name === 'dir-community') { - for(const inst of instances) this.updateButtons(inst); - } - }); - - this.ChannelCard.ready((cls, instances) => { - if (this.router.current.name === 'dir-community') { - this.apollo.ensureQuery( - 'GamePage_Game', - 'data.directory.streams.edges.0.node.createdAt' - ); - - for(const inst of instances) this.updateChannelCard(inst); - } - }); - - this.ChannelCard.on('update', inst => this.router.current.name === 'dir-community' && this.updateChannelCard(inst), this); - - this.ChannelCard.on('mount', inst => this.router.current.name === 'dir-community' && this.updateChannelCard(inst), this); - - this.ChannelCard.on('unmount', inst => this.router.current.name === 'dir-community' && this.updateUptime(inst), this); - - this.css_tweaks.toggleHide('profile-hover-game', this.settings.get('directory.following.show-channel-avatar') === 2); - } - - updateUptime(inst) { - const container = this.fine.getHostNode(inst); - const card = container && container.querySelector && container.querySelector('.tw-card'); - - if (container === null || card === null) { - if (inst.updateTimer !== undefined) { - clearInterval(inst.updateTimer); - inst.updateTimer = undefined; - return; - } - } - - if (this.settings.get('directory.following.uptime') === 0) { - if (inst.updateTimer !== undefined) { - clearInterval(inst.updateTimer); - inst.updateTimer = undefined; - } - - if (inst.uptimeElement !== undefined) { - inst.uptimeElement.remove(); - inst.uptimeElementSpan = inst.uptimeElement = undefined; - } - } else { - if (inst.updateTimer === undefined) { - inst.updateTimer = setInterval( - this.updateUptime.bind(this, inst), - 1000 - ); - } - - const up_since = new Date(inst.props.streamNode.viewersCount.createdAt); - const uptime = up_since && Math.floor((Date.now() - up_since) / 1000) || 0; - const uptimeText = duration_to_string(uptime, false, false, false, this.settings.get('directory.following.uptime') === 1); - - if (uptime > 0) { - if (inst.uptimeElement === undefined) { - inst.uptimeElementSpan = e('span', 'tw-stat__value ffz-uptime', `${uptimeText}`); - inst.uptimeElement = e('div', { - className: 'c-background-overlay c-text-overlay font-size-6 top-0 right-0 z-default inline-flex absolute mg-05', - style: 'padding-left: 4px; padding-right: 4px;' - }, [ - e('span', 'tw-stat__icon', - e('figure', 'ffz-i-clock') - ), - inst.uptimeElementSpan - ]); - - if (card.querySelector('.ffz-uptime') === null) card.appendChild(inst.uptimeElement); - } else { - inst.uptimeElementSpan.textContent = `${uptimeText}`; - } - } - } - } - - updateChannelCard(inst) { - this.updateUptime(inst); - - const container = this.fine.getHostNode(inst); - const card = container && container.querySelector && container.querySelector('.tw-card'); - - if (container === null || card === null) return; - - if (!inst.props.streamNode.viewersCount.createdAt) return; - - // Remove old elements - const hiddenBodyCard = card.querySelector('.tw-card-body.hide'); - if (hiddenBodyCard !== null) hiddenBodyCard.classList.remove('hide'); - - const ffzChannelData = card.querySelector('.ffz-channel-data'); - if (ffzChannelData !== null) ffzChannelData.remove(); - - const channelAvatar = card.querySelector('.channel-avatar'); - if (channelAvatar !== null) channelAvatar.remove(); - - if (inst.props.streamNode.viewersCount.profileImageURL) { - const avatarSetting = this.settings.get('directory.following.show-channel-avatar'); - if (avatarSetting === 1) { - const cardDiv = card.querySelector('.tw-card-body'); - const modifiedDiv = e('div', { - innerHTML: cardDiv.innerHTML - }); - - const avatarDiv = e('a', { - className: 'channel-avatar', - href: `/${inst.props.streamNode.broadcaster.login}`, - style: 'margin-right: 8px; min-width: 4rem; margin-top: 0.5rem;', - onclick: event => { - event.preventDefault(); - event.stopPropagation(); - - this.router.navigate('user', { userName: inst.props.streamNode.broadcaster.login}); - } - }, e('img', { - title: inst.props.streamNode.broadcaster.displayName, - src: inst.props.streamNode.viewersCount.profileImageURL, - style: 'height: 4rem;' - })); - - const cardDivParent = cardDiv.parentElement; - - if (cardDivParent.querySelector('.ffz-channel-data') === null) { - cardDiv.classList.add('hide'); - - const newCardDiv = e('div', 'ffz-channel-data flex flex-nowrap', [ - avatarDiv, modifiedDiv - ]); - cardDivParent.appendChild(newCardDiv); - } - } else if (avatarSetting === 2 || avatarSetting === 3) { - const avatarElement = e('a', { - className: 'channel-avatar', - href: `/${inst.props.streamNode.broadcaster.login}`, - onclick: event => { - event.preventDefault(); - event.stopPropagation(); - - this.router.navigate('user', { userName: inst.props.streamNode.broadcaster.login}); - } - }, e('div', 'live-channel-card__boxart bottom-0 absolute', - e('figure', 'tw-aspect tw-aspect--align-top', - e('img', { - title: inst.props.streamNode.broadcaster.displayName, - src: inst.props.streamNode.viewersCount.profileImageURL - }) - ) - ) - ); - - const divToAppend = card.querySelector('figure.tw-aspect'); - if (divToAppend.querySelector('.channel-avatar') === null) divToAppend.appendChild(avatarElement); - } - } - } - - updateButtons(inst) { - const container = this.fine.getHostNode(inst); - // We can't get the buttons through querySelector('button ...') so this has to do for now... - const buttons = container && container.querySelector && container.querySelector('div > div.align-items-center'); - - const ffzButtons = buttons.querySelector('.ffz-buttons'); - if (ffzButtons !== null) ffzButtons.remove(); - - if (buttons.querySelector('.ffz-buttons') === null) { - // Block / Unblock Games - const blockedGames = this.settings.provider.get('directory.game.blocked-games') || []; - const gameBlocked = blockedGames.includes(inst.props.directoryName); - - const blockButton = e('button', { - className: 'mg-l-1 tw-button ffz-toggle-game-block', - style: `background-color: ${gameBlocked ? '#228B22' : '#B22222'};` - }, e('span', { - className: 'tw-button__text', - textContent: `${gameBlocked ? 'Unblock' : 'Block'}` - }) - ); - - blockButton.addEventListener('click', () => { - const gameName = inst.props.directoryName; - const blockedGames = this.settings.provider.get('directory.game.blocked-games') || []; - if (blockedGames.includes(gameName)) blockedGames.splice(blockedGames.indexOf(gameName), 1); - else blockedGames.push(gameName); - - this.settings.provider.set('directory.game.blocked-games', blockedGames); - - this.updateButtons(inst); - }); - - // Hide / Unhide Thumbnails - const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || []; - const thumbnailBlocked = hiddenThumbnails.includes(inst.props.directoryName); - - const hideThumbnailButton = e('button', { - className: 'mg-l-1 tw-button ffz-toggle-thumbnail', - style: `background-color: ${thumbnailBlocked ? '#228B22' : '#B22222'};` - }, e('span', { - className: 'tw-button__text', - textContent: `${thumbnailBlocked ? 'Unhide Thumbnails' : 'Hide Thumbnails'}` - }) - ); - - hideThumbnailButton.addEventListener('click', () => { - const gameName = inst.props.directoryName; - const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || []; - if (hiddenThumbnails.includes(gameName)) hiddenThumbnails.splice(hiddenThumbnails.indexOf(gameName), 1); - else hiddenThumbnails.push(gameName); - - this.settings.provider.set('directory.game.hidden-thumbnails', hiddenThumbnails); - - this.updateButtons(inst); - }); - - const ffzButtons = e('div', 'ffz-buttons', [ - blockButton, - hideThumbnailButton - ]); - - buttons.appendChild(ffzButtons); - } } } \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/following.js b/src/sites/twitch-twilight/modules/directory/following.js index 536573c1..aeabd5a2 100644 --- a/src/sites/twitch-twilight/modules/directory/following.js +++ b/src/sites/twitch-twilight/modules/directory/following.js @@ -7,7 +7,6 @@ import {SiteModule} from 'utilities/module'; import {createElement as e} from 'utilities/dom'; import {get} from 'utilities/object'; -import {duration_to_string} from 'utilities/time'; import Popper from 'popper.js'; @@ -178,29 +177,25 @@ export default class Following extends SiteModule { }`); this.ChannelCard = this.fine.define( - 'channel-card', + 'following-channel-card', n => n.renderGameBoxArt && n.renderContentType ); this.apollo.registerModifier('FollowedIndex_CurrentUser', res => { - res = this.modifyLiveUsers(res); - res = this.modifyLiveHosts(res); - return res; + this.modifyLiveUsers(res); + this.modifyLiveHosts(res); }, false); this.apollo.registerModifier('FollowingLive_CurrentUser', res => this.modifyLiveUsers(res), false); - this.apollo.registerModifier('FollowingHosts_CurrentUser', res => this.modifyLiveHosts(res), false); } isRouteAcceptable() { - return this.router.current.name === 'dir-following-index' + return this.router.current.name === 'dir-following' || this.router.current.name === 'dir-category' && this.router.match[1] === 'following'; } - modifyLiveUsers(res) { // eslint-disable-line class-methods-use-this - if (!this.isRouteAcceptable()) return res; - + modifyLiveUsers(res) { const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || []; const blockedGames = this.settings.provider.get('directory.game.blocked-games') || []; @@ -221,9 +216,7 @@ export default class Following extends SiteModule { return res; } - modifyLiveHosts(res) { // eslint-disable-line class-methods-use-this - if (!this.isRouteAcceptable()) return res; - + modifyLiveHosts(res) { const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || []; const blockedGames = this.settings.provider.get('directory.game.blocked-games') || []; @@ -266,8 +259,6 @@ export default class Following extends SiteModule { this.css_tweaks.toggleHide('boxart-hide', this.settings.get('directory.following.hide-boxart') === 2); this.css_tweaks.toggleHide('profile-hover-following', this.settings.get('directory.following.show-channel-avatar') === 2); - this.ChannelCard.on('update', inst => this.updateChannelCard(inst), this); - this.ChannelCard.ready((cls, instances) => { if (this.router && this.router.match) { if (this.router.match[1] === 'following') { @@ -294,6 +285,7 @@ export default class Following extends SiteModule { for(const inst of instances) this.updateChannelCard(inst); }); + this.ChannelCard.on('update', inst => this.updateChannelCard(inst), this); this.ChannelCard.on('mount', inst => this.updateChannelCard(inst), this); this.ChannelCard.on('unmount', inst => this.parent.clearUptime(inst), this); @@ -301,7 +293,7 @@ export default class Following extends SiteModule { } destroyHostMenu(event) { - if (event.target.closest('.ffz-channel-selector-outer') === null && Date.now() > this.hostMenuBuffer) { + if (!event || event && event.target && event.target.closest('.ffz-channel-selector-outer') === null && Date.now() > this.hostMenuBuffer) { this.hostMenuPopper && this.hostMenuPopper.destroy(); this.hostMenu && this.hostMenu.remove(); this.hostMenuPopper = this.hostMenu = undefined; @@ -332,14 +324,9 @@ export default class Following extends SiteModule { simplebarContentChildren.push( e('a', { className: 'tw-interactable', - href: inst.props.linkTo.pathname, + href: `/${inst.props.viewerCount.hostData.channel}`, style: 'padding-top: 0.1rem; padding-bottom: 0.1rem;', - onclick: event => { - event.preventDefault(); - event.stopPropagation(); - - this.router.navigate('user', { userName: inst.props.linkTo.pathname.substring(1)}); - } + onclick: event => this.parent.hijackUserClick(event, inst.props.viewerCount.hostData.channel, this.destroyHostMenu.bind(this)) }, e('div', 'align-items-center flex flex-row flex-nowrap mg-x-1 mg-y-05', [ e('div', { @@ -374,6 +361,7 @@ export default class Following extends SiteModule { className: 'tw-interactable', href: `/${node.login}`, style: 'padding-top: 0.1rem; padding-bottom: 0.1rem;', + onclick: event => this.parent.hijackUserClick(event, node.login, this.destroyHostMenu.bind(this)) }, e('div', 'align-items-center flex flex-row flex-nowrap mg-x-1 mg-y-05', [ e('div', { @@ -427,6 +415,8 @@ export default class Following extends SiteModule { } updateChannelCard(inst) { + if (!this.isRouteAcceptable()) return; + this.parent.updateUptime(inst, 'props.viewerCount.createdAt', '.tw-card .tw-aspect > div'); const container = this.fine.getHostNode(inst), @@ -435,11 +425,6 @@ export default class Following extends SiteModule { if ( container === null || card === null ) return; - const channelCardTitle = card.querySelector('.live-channel-card__title'); - - if ( channelCardTitle === null ) - return; - // Remove old elements const hiddenBodyCard = card.querySelector('.tw-card-body.hide'); if (hiddenBodyCard !== null) hiddenBodyCard.classList.remove('hide'); @@ -451,7 +436,7 @@ export default class Following extends SiteModule { if (channelAvatar !== null) channelAvatar.remove(); if (inst.props.viewerCount.profileImageURL) { - const hosting = inst.props.viewerCount.hostData; + const hosting = inst.props.channelNameLinkTo.state.content === 'live_host' && inst.props.viewerCount.hostData; let channel, displayName; if (hosting) { channel = inst.props.viewerCount.hostData.channel; @@ -479,12 +464,7 @@ export default class Following extends SiteModule { const avatarElement = e('a', { className: 'channel-avatar', href: hosting ? `/${channel}` : inst.props.linkTo.pathname, - onclick: event => { - event.preventDefault(); - event.stopPropagation(); - - this.router.navigate('user', { userName: inst.props.streamNode.broadcaster.login}); - } + onclick: event => this.parent.hijackUserClick(event, inst.props.streamNode.broadcaster.login) }, e('div', 'live-channel-card__boxart bottom-0 absolute', e('figure', 'tw-aspect tw-aspect--align-top', e('img', { @@ -515,16 +495,17 @@ export default class Following extends SiteModule { if (this.settings.get('directory.following.group-hosts')) { const titleLink = card.querySelector('.ffz-channel-data a[data-a-target="live-channel-card-title-link"]'); const thumbnailLink = card.querySelector('a[data-a-target="live-channel-card-thumbnail-link"]'); + const channelCardTitle = card.querySelector('.ffz-channel-data .live-channel-card__title'); - if (hostObj.channels.length > 1) { - const textContent = `${hostObj.channels.length} hosting ${displayName}`; + const textContent = hostObj.channels.length > 1 ? `${hostObj.channels.length} hosting ${displayName}` : inst.props.title; + if (channelCardTitle !== null) { channelCardTitle.textContent = channelCardTitle.title = textContent; - - if (thumbnailLink !== null) thumbnailLink.title = textContent; } + if (thumbnailLink !== null) thumbnailLink.title = textContent; + if (titleLink !== null) titleLink.onclick = this.showHostMenu.bind(this, inst, hostObj); if (thumbnailLink !== null) thumbnailLink.onclick = this.showHostMenu.bind(this, inst, hostObj); } diff --git a/src/sites/twitch-twilight/modules/directory/game.js b/src/sites/twitch-twilight/modules/directory/game.js index d9f3211f..86b556e9 100644 --- a/src/sites/twitch-twilight/modules/directory/game.js +++ b/src/sites/twitch-twilight/modules/directory/game.js @@ -6,16 +6,13 @@ import {SiteModule} from 'utilities/module'; import {createElement as e} from 'utilities/dom'; -import {duration_to_string} from 'utilities/time'; export default class Game extends SiteModule { constructor(...args) { super(...args); this.inject('site.fine'); - this.inject('site.router'); this.inject('site.apollo'); - this.inject('site.css_tweaks'); this.inject('settings'); @@ -41,223 +38,26 @@ export default class Game extends SiteModule { } } }`); - - this.ChannelCard = this.fine.define( - 'game-channel-card', - n => n.props && n.props.streamNode - ); - - this.apollo.registerModifier('GamePage_Game', res => this.router.current.name === 'dir-game-index' && this.modifyStreams(res), false); - - this.on('site.directory.following:update-show-channel-avatar', value => { - this.css_tweaks.toggleHide('profile-hover-game', value === 2); - this.router.current.name === 'dir-game-index' && this.ChannelCard.forceUpdate(); - }); - - this.on('site.directory.following:update-uptime', () => this.router.current.name === 'dir-game-index' && this.ChannelCard.forceUpdate()); - } - - modifyStreams(res) { // eslint-disable-line class-methods-use-this - const newStreams = []; - - // const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || []; - // const thumbnailBlocked = hiddenThumbnails.includes(res.data.directory.displayName); - - const edges = res.data.directory.streams.edges; - for (let i = 0; i < edges.length; i++) { - const edge = edges[i]; - const node = edge.node; - - const s = node.viewersCount = new Number(node.viewersCount || 0); - s.profileImageURL = node.broadcaster.profileImageURL; - s.createdAt = node.createdAt; - - // if (thumbnailBlocked) edge.node.previewImageURL = 'https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg'; - - newStreams.push(edge); - } - res.data.directory.streams.edges = newStreams; - return res; } onEnable() { this.GameHeader.ready((cls, instances) => { - if (this.router.current.name === 'dir-game-index') { - for(const inst of instances) this.updateButtons(inst); - } + for(const inst of instances) this.updateButtons(inst); }); - this.ChannelCard.ready((cls, instances) => { - if (this.router.current.name === 'dir-game-index') { - this.apollo.ensureQuery( - 'GamePage_Game', - 'data.directory.streams.edges.0.node.createdAt' - ); - - for(const inst of instances) this.updateChannelCard(inst); - } - }); - - this.ChannelCard.on('update', inst => this.router.current.name === 'dir-game-index' && this.updateChannelCard(inst), this); - - this.ChannelCard.on('mount', inst => this.router.current.name === 'dir-game-index' && this.updateChannelCard(inst), this); - - this.ChannelCard.on('unmount', inst => this.router.current.name === 'dir-game-index' && this.updateUptime(inst), this); - - this.css_tweaks.toggleHide('profile-hover-game', this.settings.get('directory.following.show-channel-avatar') === 2); + this.GameHeader.on('update', inst => this.updateButtons(inst)); } - updateUptime(inst) { - const container = this.fine.getHostNode(inst); - const card = container && container.querySelector && container.querySelector('.tw-thumbnail-card'); + updateButtons(inst, update = false) { + if (inst.props.directoryType !== 'GAMES') return; - if (container === null || card === null) { - if (inst.updateTimer !== undefined) { - clearInterval(inst.updateTimer); - inst.updateTimer = undefined; - return; - } - } - - if (this.settings.get('directory.following.uptime') === 0) { - if (inst.updateTimer !== undefined) { - clearInterval(inst.updateTimer); - inst.updateTimer = undefined; - } - - if (inst.uptimeElement !== undefined) { - inst.uptimeElement.remove(); - inst.uptimeElementSpan = inst.uptimeElement = undefined; - } - } else { - if (inst.updateTimer === undefined) { - inst.updateTimer = setInterval( - this.updateUptime.bind(this, inst), - 1000 - ); - } - - const up_since = new Date(inst.props.streamNode.viewersCount.createdAt); - const uptime = up_since && Math.floor((Date.now() - up_since) / 1000) || 0; - const uptimeText = duration_to_string(uptime, false, false, false, this.settings.get('directory.following.uptime') === 1); - - if (uptime > 0) { - if (inst.uptimeElement === undefined) { - inst.uptimeElementSpan = e('span', 'tw-stat__value ffz-uptime', `${uptimeText}`); - inst.uptimeElement = e('div', { - className: 'c-background-overlay c-text-overlay font-size-6 top-0 right-0 z-default inline-flex absolute mg-05', - style: 'padding-left: 4px; padding-right: 4px;' - }, [ - e('span', 'tw-stat__icon', - e('figure', 'ffz-i-clock') - ), - inst.uptimeElementSpan - ]); - - if (card.querySelector('.ffz-uptime') === null) card.appendChild(inst.uptimeElement); - } else { - inst.uptimeElementSpan.textContent = `${uptimeText}`; - } - } - } - } - - updateChannelCard(inst) { - this.updateUptime(inst); - - const container = this.fine.getHostNode(inst); - const card = container && container.querySelector && container.querySelector('.tw-thumbnail-card'); - - if (container === null || card === null) return; - - if (!inst.props.streamNode.viewersCount.createdAt || container === null || card === null) return; - - // Remove old elements - const hiddenBodyCard = card.querySelector('.tw-card-body.hide'); - if (hiddenBodyCard !== null) hiddenBodyCard.classList.remove('hide'); - - const ffzChannelData = card.querySelector('.ffz-channel-data'); - if (ffzChannelData !== null) ffzChannelData.remove(); - - const channelAvatar = card.querySelector('.channel-avatar'); - if (channelAvatar !== null) channelAvatar.remove(); - - const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || []; - const thumbnailBlocked = hiddenThumbnails.includes(inst.props.directoryName); - - if (thumbnailBlocked) { - card.classList.add('ffz-thumbnail-hidden'); - } else { - card.classList.remove('ffz-thumbnail-hidden'); - } - - if (inst.props.streamNode.viewersCount.profileImageURL) { - const avatarSetting = this.settings.get('directory.following.show-channel-avatar'); - if (avatarSetting === 1) { - const cardDiv = card.querySelector('.tw-card-body'); - const modifiedDiv = e('div', { - innerHTML: cardDiv.innerHTML - }); - - const avatarDiv = e('a', { - className: 'channel-avatar', - href: `/${inst.props.streamNode.broadcaster.login}`, - style: 'margin-right: 8px; min-width: 4rem; margin-top: 0.5rem;', - onclick: event => { - event.preventDefault(); - event.stopPropagation(); - - this.router.navigate('user', { userName: inst.props.streamNode.broadcaster.login}); - } - }, e('img', { - title: inst.props.streamNode.broadcaster.displayName, - src: inst.props.streamNode.viewersCount.profileImageURL, - style: 'height: 4rem;' - })); - - const cardDivParent = cardDiv.parentElement; - - if (cardDivParent.querySelector('.ffz-channel-data') === null) { - cardDiv.classList.add('hide'); - - const newCardDiv = e('div', 'ffz-channel-data flex flex-nowrap', [ - avatarDiv, modifiedDiv - ]); - cardDivParent.appendChild(newCardDiv); - } - } else if (avatarSetting === 2 || avatarSetting === 3) { - const avatarElement = e('a', { - className: 'channel-avatar', - href: `/${inst.props.streamNode.broadcaster.login}`, - onclick: event => { - event.preventDefault(); - event.stopPropagation(); - - this.router.navigate('user', { userName: inst.props.streamNode.broadcaster.login}); - } - }, e('div', 'live-channel-card__boxart bottom-0 absolute', - e('figure', 'tw-aspect tw-aspect--align-top', - e('img', { - title: inst.props.streamNode.broadcaster.displayName, - src: inst.props.streamNode.viewersCount.profileImageURL - }) - ) - ) - ); - - const divToAppend = card.querySelector('figure.tw-aspect'); - if (divToAppend.querySelector('.channel-avatar') === null) divToAppend.appendChild(avatarElement); - } - } - } - - updateButtons(inst) { const container = this.fine.getHostNode(inst); // We can't get the buttons through querySelector('button ...') so this has to do for now... const buttons = container && container.querySelector && container.querySelector('div > div.align-items-center'); const ffzButtons = buttons.querySelector('.ffz-buttons'); - if (ffzButtons !== null) ffzButtons.remove(); + if (ffzButtons !== null && !update) return; + else if (ffzButtons) ffzButtons.remove(); if (buttons.querySelector('.ffz-buttons') === null) { // Block / Unblock Games @@ -281,7 +81,7 @@ export default class Game extends SiteModule { this.settings.provider.set('directory.game.blocked-games', blockedGames); - this.updateButtons(inst); + this.updateButtons(inst, true); }); // Hide / Unhide Thumbnails @@ -305,8 +105,8 @@ export default class Game extends SiteModule { this.settings.provider.set('directory.game.hidden-thumbnails', hiddenThumbnails); - this.updateButtons(inst); - this.ChannelCard.forceUpdate(); + this.parent.ChannelCard.forceUpdate(); + this.updateButtons(inst, true); }); const ffzButtons = e('div', 'ffz-buttons', [ diff --git a/src/sites/twitch-twilight/modules/directory/index.js b/src/sites/twitch-twilight/modules/directory/index.js new file mode 100644 index 00000000..d4343028 --- /dev/null +++ b/src/sites/twitch-twilight/modules/directory/index.js @@ -0,0 +1,234 @@ +'use strict'; + +// ============================================================================ +// Directory +// ============================================================================ + +import {SiteModule} from 'utilities/module'; +import {duration_to_string} from 'utilities/time'; +import {createElement as e} from 'utilities/dom'; +import {get} from 'utilities/object'; + +import Following from './following'; +import Game from './game'; +import Community from './community'; + +export default class Directory extends SiteModule { + constructor(...args) { + super(...args); + + this.should_enable = true; + + this.inject('site.fine'); + this.inject('site.router'); + this.inject('site.apollo'); + this.inject('site.css_tweaks'); + + this.inject('settings'); + + this.inject(Following); + this.inject(Game); + this.inject(Community); + + this.apollo.registerModifier('GamePage_Game', res => this.modifyStreams(res), false); + + this.ChannelCard = this.fine.define( + 'channel-card', + n => n.props && n.props.streamNode + ); + + this.on('settings:changed:directory.following.show-channel-avatar', value => { + this.css_tweaks.toggleHide('profile-hover-game', value === 2); + this.ChannelCard.forceUpdate(); + }); + + this.on('settings:changed:directory.following.uptime', () => this.ChannelCard.forceUpdate()); + } + + + onEnable() { + this.css_tweaks.toggleHide('profile-hover-game', this.settings.get('directory.following.show-channel-avatar') === 2); + + this.ChannelCard.ready((cls, instances) => { + this.apollo.ensureQuery( + 'GamePage_Game', + 'data.directory.streams.edges.0.node.createdAt' + ); + + for(const inst of instances) this.updateChannelCard(inst); + }); + + this.ChannelCard.on('update', inst => this.updateChannelCard(inst), this); + this.ChannelCard.on('mount', inst => this.updateChannelCard(inst), this); + this.ChannelCard.on('unmount', inst => this.clearUptime(inst), this); + } + + + updateChannelCard(inst) { + const uptimeSel = inst.props.directoryType === 'GAMES' ? '.tw-thumbnail-card .tw-card-img' : '.tw-card .tw-aspect > div'; + const avatarSel = inst.props.directoryType === 'GAMES' ? '.tw-thumbnail-card' : '.tw-card'; + + this.updateUptime(inst, 'props.streamNode.viewersCount.createdAt', uptimeSel); + this.addCardAvatar(inst, avatarSel); + + const type = inst.props.directoryType; + const hiddenThumbnails = this.settings.provider.get('directory.game.hidden-thumbnails') || []; + const hiddenPreview = 'https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg'; + + const container = this.fine.getHostNode(inst); + const img = container && container.querySelector && container.querySelector(`${uptimeSel} img`); + if (img === null) return; + + if (type === 'GAMES' && hiddenThumbnails.includes(inst.props.directoryName) || + type === 'COMMUNITIES' && hiddenThumbnails.includes(inst.props.streamNode.game.name)) { + img.src = hiddenPreview; + } else { + img.src = inst.props.streamNode.previewImageURL; + } + } + + + modifyStreams(res) { // eslint-disable-line class-methods-use-this + const newStreams = []; + + const edges = res.data.directory.streams.edges; + for (let i = 0; i < edges.length; i++) { + const edge = edges[i]; + const node = edge.node; + + const s = node.viewersCount = new Number(node.viewersCount || 0); + s.profileImageURL = node.broadcaster.profileImageURL; + s.createdAt = node.createdAt; + + newStreams.push(edge); + } + res.data.directory.streams.edges = newStreams; + return res; + } + + + clearUptime(inst) { // eslint-disable-line class-methods-use-this + if ( inst.ffz_update_timer ) { + clearInterval(inst.ffz_update_timer); + inst.ffz_update_timer = null; + } + + if ( inst.ffz_uptime_el ) { + inst.ffz_uptime_el.parentElement.removeChild(inst.ffz_uptime_el); + inst.ffz_uptime_el = null; + inst.ffz_uptime_span = null; + inst.ffz_uptime_tt = null; + } + } + + + updateUptime(inst, created_path, selector) { + const container = this.fine.getHostNode(inst), + card = container && container.querySelector && container.querySelector(selector), + setting = this.settings.get('directory.following.uptime'), + created_at = get(created_path, inst), + up_since = created_at && new Date(created_at), + uptime = up_since && Math.floor((Date.now() - up_since) / 1000) || 0; + + if ( ! card || setting === 0 || uptime < 1 ) + return this.clearUptime(inst); + + const up_text = duration_to_string(uptime, false, false, false, setting === 1); + + if ( ! inst.ffz_uptime_el ) + card.appendChild(inst.ffz_uptime_el = e('div', + 'video-preview-card__preview-overlay-stat c-background-overlay c-text-overlay font-size-6 top-0 right-0 z-default inline-flex absolute mg-05', + e('div', 'tw-tooltip-wrapper inline-flex', [ + e('div', 'tw-stat', [ + e('span', 'c-text-live tw-stat__icon', e('figure', 'ffz-i-clock')), + inst.ffz_uptime_span = e('span', 'tw-stat__value') + ]), + inst.ffz_uptime_tt = e('div', 'tw-tooltip tw-tooltip--down tw-tooltip--align-center') + ]))); + + if ( ! inst.ffz_update_timer ) + inst.ffz_update_timer = setInterval(this.updateUptime.bind(this, inst, created_path, selector), 1000); + + inst.ffz_uptime_span.textContent = up_text; + inst.ffz_uptime_tt.textContent = up_since.toLocaleString(); + + } + + + addCardAvatar(inst, selector) { + const container = this.fine.getHostNode(inst), + card = container && container.querySelector && container.querySelector(selector), + setting = this.settings.get('directory.following.show-channel-avatar'); + + // Remove old elements + const hiddenBodyCard = card.querySelector('.tw-card-body.hide'); + if (hiddenBodyCard !== null) hiddenBodyCard.classList.remove('hide'); + + const ffzChannelData = card.querySelector('.ffz-channel-data'); + if (ffzChannelData !== null) ffzChannelData.remove(); + + const channelAvatar = card.querySelector('.channel-avatar'); + if (channelAvatar !== null) channelAvatar.remove(); + + if ( ! card || setting === 0 ) + return; + + if (inst.props.streamNode.viewersCount.profileImageURL) { + if (setting === 1) { + const cardDiv = card.querySelector('.tw-card-body'); + const modifiedDiv = e('div', { + innerHTML: cardDiv.innerHTML + }); + + const avatarDiv = e('a', { + className: 'channel-avatar', + href: `/${inst.props.streamNode.broadcaster.login}`, + style: 'margin-right: 8px; min-width: 4rem; margin-top: 0.5rem;', + onclick: event => this.hijackUserClick(event, inst.props.streamNode.broadcaster.login) + }, e('img', { + title: inst.props.streamNode.broadcaster.displayName, + src: inst.props.streamNode.viewersCount.profileImageURL, + style: 'height: 4rem;' + })); + + const cardDivParent = cardDiv.parentElement; + + if (cardDivParent.querySelector('.ffz-channel-data') === null) { + cardDiv.classList.add('hide'); + + const newCardDiv = e('div', 'ffz-channel-data flex flex-nowrap', [ + avatarDiv, modifiedDiv + ]); + cardDivParent.appendChild(newCardDiv); + } + } else if (setting === 2 || setting === 3) { + const avatarElement = e('a', { + className: 'channel-avatar', + href: `/${inst.props.streamNode.broadcaster.login}`, + onclick: event => this.hijackUserClick(event, inst.props.streamNode.broadcaster.login) + }, e('div', 'live-channel-card__boxart bottom-0 absolute', + e('figure', 'tw-aspect tw-aspect--align-top', + e('img', { + title: inst.props.streamNode.broadcaster.displayName, + src: inst.props.streamNode.viewersCount.profileImageURL + }) + ) + ) + ); + + const divToAppend = card.querySelector('figure.tw-aspect'); + if (divToAppend.querySelector('.channel-avatar') === null) divToAppend.appendChild(avatarElement); + } + } + } + + + hijackUserClick(event, user, optionalFn = null) { + event.preventDefault(); + event.stopPropagation(); + + if (optionalFn) optionalFn(); + + this.router.navigate('user', { userName: user }); + } +} \ No newline at end of file diff --git a/src/sites/twitch-twilight/modules/directory/index.off b/src/sites/twitch-twilight/modules/directory/index.off deleted file mode 100644 index eccb6f03..00000000 --- a/src/sites/twitch-twilight/modules/directory/index.off +++ /dev/null @@ -1,77 +0,0 @@ -'use strict'; - -// ============================================================================ -// Directory -// ============================================================================ - -import {SiteModule} from 'utilities/module'; -import {duration_to_string} from 'utilities/time'; -import {createElement as e} from 'utilities/dom'; -import {get} from 'utilities/object'; - -import Following from './following'; -import Game from './game'; -import Community from './community'; - -export default class Directory extends SiteModule { - constructor(...args) { - super(...args); - - this.should_enable = true; - - this.inject('i18n'); - this.inject('settings'); - this.inject('site.fine'); - - this.inject(Following); - this.inject(Game); - this.inject(Community); - } - - - clearUptime(inst) { // eslint-disable-line class-methods-use-this - if ( inst.ffz_update_timer ) { - clearInterval(inst.ffz_update_timer); - inst.ffz_update_timer = null; - } - - if ( inst.ffz_uptime_el ) { - inst.ffz_uptime_el.parentElement.removeChild(inst.ffz_uptime_el); - inst.ffz_uptime_el = null; - inst.ffz_uptime_span = null; - inst.ffz_uptime_tt = null; - } - } - - - updateUptime(inst, created_path, selector) { - const container = this.fine.getHostNode(inst), - card = container && container.querySelector && container.querySelector(selector), - setting = this.settings.get('directory.following.uptime'), - created_at = get(created_path, inst), - up_since = created_at && new Date(created_at), - uptime = up_since && Math.floor((Date.now() - up_since) / 1000) || 0; - - if ( ! card || setting === 0 || uptime < 1 ) - return this.clearUptime(inst); - - const up_text = duration_to_string(uptime, false, false, false, setting === 1); - - if ( ! inst.ffz_uptime_el ) - card.appendChild(inst.ffz_uptime_el = e('div', - 'video-preview-card__preview-overlay-stat c-background-overlay c-text-overlay font-size-6 top-0 right-0 z-default inline-flex absolute mg-05', - e('div', 'tw-tooltip-wrapper inline-flex', [ - e('div', 'tw-stat', [ - e('span', 'c-text-live tw-stat__icon', e('figure', 'ffz-i-clock')), - inst.ffz_uptime_span = e('span', 'tw-stat__value') - ]), - inst.ffz_uptime_tt = e('div', 'tw-tooltip tw-tooltip--down tw-tooltip--align-center') - ]))); - - if ( ! inst.ffz_update_timer ) - inst.ffz_update_timer = setInterval(this.updateUptime.bind(this, inst, created_path, selector), 1000); - - inst.ffz_uptime_span.textContent = up_text; - inst.ffz_uptime_tt.textContent = up_since.toLocaleString(); - } -} \ No newline at end of file