mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-24 11:38:30 +00:00
4.0.0-rc4
* Added: Smooth Scrolling for Chat. (Thanks @neuspadrin) * Changed: Disable mouse interaction with the background of the metadata bar when it's visible over the player in theater mode. * Fixed: All directory features except host menus. * API Added: tList(...) method for i18n to support localizing strings with embedded links, etc.
This commit is contained in:
parent
be1e1bfd47
commit
0f88ce216f
16 changed files with 264 additions and 111 deletions
|
@ -1,3 +1,16 @@
|
|||
<div class="list-header">4.0.0-rc4<span>@012130d37baabae67622</span> <time datetime="2018-07-03">(2018-07-03)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Added: Smooth Scrolling for Chat. (Thanks neuspadrin on GitHub!)</li>
|
||||
<li>Fixed: All directory features except the Host Menu.</li>
|
||||
<li>Changed: Disable mouse interaction with the background of the metadata bar when it's visible in theater mode.</li>
|
||||
</ul>
|
||||
|
||||
<div class="list-header">4.0.0-rc3.4<span>@104efaa42e14d30191d1</span> <time datetime="2018-07-02">(2018-07-02)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Fixed: Stream uptime when watching a channel.</li>
|
||||
<li class="tw-align-right">~ Arthur, King of the Britons</li>
|
||||
</ul>
|
||||
|
||||
<div class="list-header">4.0.0-rc3.3<span>@85ad2d458fb808c0365f</span> <time datetime="2018-06-29">(2018-06-29)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Fixed: Update class names for all input elements to use Twitch's new, long-winded methodology.</li>
|
||||
|
|
68
src/i18n.js
68
src/i18n.js
|
@ -361,6 +361,10 @@ export class TranslationManager extends Module {
|
|||
t(...args) {
|
||||
return this._.t(...args);
|
||||
}
|
||||
|
||||
tList(...args) {
|
||||
return this._.tList(...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -391,6 +395,7 @@ export default class TranslationCore {
|
|||
const allowMissing = options.allowMissing ? transformPhrase : null;
|
||||
this.onMissingKey = typeof options.onMissingKey === 'function' ? options.onMissingKey : allowMissing;
|
||||
this.transformPhrase = typeof options.transformPhrase === 'function' ? options.transformPhrase : transformPhrase;
|
||||
this.transformList = typeof options.transformList === 'function' ? options.transformList : transformList;
|
||||
this.delimiter = options.delimiter || /\s*\|\|\|\|\s*/;
|
||||
this.tokenRegex = options.tokenRegex || /%\{(.*?)(?:\|(.*?))?\}/g;
|
||||
this.formatters = Object.assign({}, DEFAULT_FORMATTERS, options.formatters || {});
|
||||
|
@ -457,7 +462,7 @@ export default class TranslationCore {
|
|||
this.extend(phrases);
|
||||
}
|
||||
|
||||
t(key, phrase, options, use_default) {
|
||||
preT(key, phrase, options, use_default) {
|
||||
const opts = options == null ? {} : options;
|
||||
let p, locale;
|
||||
|
||||
|
@ -492,8 +497,20 @@ export default class TranslationCore {
|
|||
if ( this.transformation )
|
||||
p = this.transformation(key, p, opts, locale, this.tokenRegex);
|
||||
|
||||
return [p, opts, locale];
|
||||
}
|
||||
|
||||
t(key, phrase, options, use_default) {
|
||||
const [p, opts, locale] = this.preT(key, phrase, options, use_default);
|
||||
|
||||
return this.transformPhrase(p, opts, locale, this.tokenRegex, this.formatters);
|
||||
}
|
||||
|
||||
tList(key, phrase, options, use_default) {
|
||||
const [p, opts, locale] = this.preT(key, phrase, options, use_default);
|
||||
|
||||
return this.transformList(p, opts, locale, this.tokenRegex, this.formatters);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -503,6 +520,55 @@ export default class TranslationCore {
|
|||
|
||||
const DOLLAR_REGEX = /\$/g;
|
||||
|
||||
export function transformList(phrase, substitutions, locale, token_regex, formatters) {
|
||||
const is_array = Array.isArray(phrase);
|
||||
if ( substitutions == null )
|
||||
return is_array ? phrase[0] : phrase;
|
||||
|
||||
let p = phrase;
|
||||
const options = typeof substitutions === 'number' ? {count: substitutions} : substitutions;
|
||||
|
||||
if ( is_array )
|
||||
p = p[pluralTypeIndex(
|
||||
locale || 'en',
|
||||
has(options, 'count') ? options.count : 1
|
||||
)] || p[0];
|
||||
|
||||
const result = [];
|
||||
|
||||
token_regex.lastIndex = 0;
|
||||
let idx = 0, match;
|
||||
|
||||
while((match = token_regex.exec(p))) {
|
||||
const nix = match.index,
|
||||
arg = match[1],
|
||||
fmt = match[2];
|
||||
|
||||
if ( nix !== idx )
|
||||
result.push(p.slice(idx, nix));
|
||||
|
||||
let val = get(arg, options);
|
||||
|
||||
if ( val != null ) {
|
||||
const formatter = formatters[fmt];
|
||||
if ( typeof formatter === 'function' )
|
||||
val = formatter(val, locale, options);
|
||||
else if ( typeof val === 'string' )
|
||||
val = REPLACE.call(val, DOLLAR_REGEX, '$$');
|
||||
|
||||
result.push(val);
|
||||
}
|
||||
|
||||
idx = nix + match[0].length;
|
||||
}
|
||||
|
||||
if ( idx < p.length )
|
||||
result.push(p.slice(idx));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
export function transformPhrase(phrase, substitutions, locale, token_regex, formatters) {
|
||||
const is_array = Array.isArray(phrase);
|
||||
if ( substitutions == null )
|
||||
|
|
|
@ -100,7 +100,7 @@ class FrankerFaceZ extends Module {
|
|||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc3.3',
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc4',
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`
|
||||
|
|
|
@ -61,7 +61,7 @@ export default class Metadata extends Module {
|
|||
setup() {
|
||||
const socket = this.resolve('socket'),
|
||||
apollo = this.resolve('site.apollo'),
|
||||
created_at = apollo.getFromQuery('ChannelPage_ChannelInfoBar_User', 'data.user.stream.createdAt');
|
||||
created_at = apollo.getFromQuery('ChannelPage_ChannelHeader', 'data.user.stream.createdAt');
|
||||
|
||||
if ( ! created_at )
|
||||
return {};
|
||||
|
|
|
@ -21,8 +21,8 @@ export default class ChannelBar extends Module {
|
|||
this.inject('metadata');
|
||||
this.inject('socket');
|
||||
|
||||
this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', CHANNEL_QUERY);
|
||||
this.apollo.registerModifier('ChannelPage_ChannelInfoBar_User', data => {
|
||||
this.apollo.registerModifier('ChannelPage_ChannelHeader', CHANNEL_QUERY);
|
||||
this.apollo.registerModifier('ChannelPage_ChannelHeader', data => {
|
||||
const u = data && data.data && data.data.user;
|
||||
if ( u ) {
|
||||
const o = u.profileViewCount = new Number(u.profileViewCount || 0);
|
||||
|
|
|
@ -51,7 +51,7 @@ export default class Scroller extends Module {
|
|||
ui: {
|
||||
path: 'Chat > Behavior >> General',
|
||||
title: 'Smooth Scrolling',
|
||||
description: 'Animates new chat messages into view. Will speed up if necessary to keep up with chat.',
|
||||
description: 'Smoothly slide new chat messages into view. Speed will increase as necessary to keep up with chat.',
|
||||
component: 'setting-select-box',
|
||||
data: [
|
||||
{value: 0, title: 'Disabled'},
|
||||
|
@ -85,9 +85,8 @@ export default class Scroller extends Module {
|
|||
this.chat.context.on('changed:chat.scroller.smooth-scroll', val => {
|
||||
this.smoothScroll = val;
|
||||
|
||||
for(const inst of this.ChatScroller.instances) {
|
||||
for(const inst of this.ChatScroller.instances)
|
||||
inst.ffzSetSmoothScroll(val);
|
||||
}
|
||||
});
|
||||
|
||||
this.ChatScroller.ready((cls, instances) => {
|
||||
|
@ -250,52 +249,62 @@ export default class Scroller extends Module {
|
|||
}
|
||||
|
||||
cls.prototype.smoothScrollBottom = function() {
|
||||
if(this.state.ffzSmoothAnimation){
|
||||
cancelAnimationFrame(this.state.ffzSmoothAnimation);
|
||||
}
|
||||
this.isScrollingToBottom = true;
|
||||
if ( this._ffz_smooth_animation )
|
||||
cancelAnimationFrame(this._ffz_smooth_animation);
|
||||
|
||||
this.ffz_is_smooth_scrolling = true;
|
||||
|
||||
// Step setting value is # pixels to scroll per 10ms.
|
||||
// 1 is pretty slow, 2 medium, 3 fast, 4 very fast.
|
||||
let step = this.ffz_smooth_scroll;
|
||||
const scrollContent = this.scroll.scrollContent;
|
||||
const targetTop = scrollContent.scrollHeight - scrollContent.clientHeight;
|
||||
const difference = targetTop - scrollContent.scrollTop;
|
||||
let step = this.ffz_smooth_scroll,
|
||||
old_time = Date.now();
|
||||
|
||||
const scroll_content = this.scroll.scrollContent,
|
||||
target_top = scroll_content.scrollHeight - scroll_content.clientHeight,
|
||||
difference = target_top - scroll_content.scrollTop;
|
||||
|
||||
// If we are falling behind speed us up
|
||||
if (difference > scrollContent.clientHeight) {
|
||||
if ( difference > scroll_content.clientHeight ) {
|
||||
// we are a full scroll away, just jump there
|
||||
step = difference;
|
||||
} else if (difference > 200) {
|
||||
|
||||
} else if ( difference > 200 ) {
|
||||
// we are starting to fall behind, speed it up a bit
|
||||
step += step * parseInt(difference / 200, 10);
|
||||
}
|
||||
let prevTime = Date.now();
|
||||
const smoothAnimation = () => {
|
||||
if(this.state.ffzFrozen) {
|
||||
this.isScrollingToBottom = false;
|
||||
return;
|
||||
}
|
||||
// See how much time has passed to get a step based off the delta
|
||||
const currentTime = Date.now();
|
||||
const delta = currentTime - prevTime;
|
||||
const currentStep = step * (delta / 10);
|
||||
// we need to move at least one full pixel for scrollTop to do anything in this delta.
|
||||
if (currentStep >= 1) {
|
||||
prevTime = currentTime;
|
||||
if (scrollContent.scrollTop < (scrollContent.scrollHeight - scrollContent.clientHeight)) {
|
||||
scrollContent.scrollTop += currentStep;
|
||||
this.state.ffzSmoothAnimation = requestAnimationFrame(smoothAnimation);
|
||||
} else {
|
||||
scrollContent.scrollTop = scrollContent.scrollHeight - scrollContent.clientHeight;
|
||||
this.isScrollingToBottom = false;
|
||||
}
|
||||
} else {
|
||||
// the frame happened so quick since last update we didn't move a full pixel yet.
|
||||
// should only be possible if the FPS of a browser went over 60fps.
|
||||
this.state.ffzSmoothAnimation = requestAnimationFrame(smoothAnimation);
|
||||
step += step * Math.floor(difference / 200);
|
||||
}
|
||||
|
||||
const smoothAnimation = () => {
|
||||
if ( this.state.ffzFrozen )
|
||||
return this.ffz_is_smooth_scrolling = false;
|
||||
|
||||
// See how much time has passed to get a step based off the delta
|
||||
const current_time = Date.now(),
|
||||
delta = current_time - old_time,
|
||||
current_step = step * (delta / 10);
|
||||
|
||||
// we need to move at least one full pixel for scrollTop to do anything in this delta.
|
||||
if ( current_step >= 1 ) {
|
||||
const scroll_top = scroll_content.scrollTop,
|
||||
target_top = scroll_content.scrollHeight - scroll_content.clientHeight;
|
||||
|
||||
old_time = current_time;
|
||||
if ( scroll_top < target_top ) {
|
||||
scroll_content.scrollTop = scroll_top + current_step;
|
||||
this._ffz_smooth_animation = requestAnimationFrame(smoothAnimation);
|
||||
|
||||
} else {
|
||||
// We've reached the bottom.
|
||||
scroll_content.scrollTop = target_top;
|
||||
this.ffz_is_smooth_scrolling = false;
|
||||
}
|
||||
|
||||
} else {
|
||||
// The frame happened so quick since last update that we haven't moved a full pixel.
|
||||
// Just wait.
|
||||
this._ffz_smooth_animation = requestAnimationFrame(smoothAnimation);
|
||||
}
|
||||
}
|
||||
|
||||
smoothAnimation();
|
||||
}
|
||||
|
||||
|
@ -308,13 +317,12 @@ export default class Scroller extends Module {
|
|||
this._old_scroll = this.scrollToBottom;
|
||||
this.scrollToBottom = function() {
|
||||
if ( ! this.ffz_freeze_enabled || ! this.state.ffzFrozen ) {
|
||||
if (this.ffz_smooth_scroll !== 0) {
|
||||
if ( this.ffz_smooth_scroll )
|
||||
this.smoothScrollBottom();
|
||||
} else {
|
||||
else
|
||||
this._old_scroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._ffz_handleScroll = this.handleScrollEvent;
|
||||
this.handleScrollEvent = function(e) {
|
||||
|
|
|
@ -5,6 +5,12 @@
|
|||
right: calc(var(--ffz-chat-width) + 25rem);
|
||||
z-index: 3500;
|
||||
opacity: 0;
|
||||
|
||||
pointer-events: none;
|
||||
|
||||
.tw-tooltip-wrapper, .tw-stat, .ffz-stat, button, a {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-page-layout__scroll-area--theatre-mode:hover .channel-info-bar {
|
||||
|
|
|
@ -3,9 +3,6 @@ query {
|
|||
edges {
|
||||
node {
|
||||
createdAt
|
||||
broadcaster {
|
||||
profileImageURL(width: 50)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ export default class BrowsePopular extends SiteModule {
|
|||
}
|
||||
|
||||
modifyStreams(res) { // eslint-disable-line class-methods-use-this
|
||||
this.log.info('Streams', res);
|
||||
|
||||
const edges = get('data.streams.edges', res);
|
||||
if ( ! edges || ! edges.length )
|
||||
return res;
|
||||
|
|
|
@ -2,7 +2,6 @@ query {
|
|||
currentUser {
|
||||
followedLiveUsers {
|
||||
nodes {
|
||||
profileImageURL(width: 50)
|
||||
stream {
|
||||
type
|
||||
createdAt
|
||||
|
|
|
@ -2,7 +2,6 @@ query {
|
|||
currentUser {
|
||||
followedLiveUsers {
|
||||
nodes {
|
||||
profileImageURL(width: 50)
|
||||
stream {
|
||||
createdAt
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ query {
|
|||
followedLiveUsers {
|
||||
edges {
|
||||
node {
|
||||
profileImageURL(width: 50)
|
||||
stream {
|
||||
createdAt
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ 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';
|
||||
|
||||
export default class Following extends SiteModule {
|
||||
constructor(...args) {
|
||||
|
@ -63,27 +64,37 @@ export default class Following extends SiteModule {
|
|||
changed: () => this.ChannelCard.forceUpdate()
|
||||
});
|
||||
|
||||
this.apollo.registerModifier('FollowedChannels_RENAME2', FOLLOWED_CHANNELS);
|
||||
this.apollo.registerModifier('SideNav_SubscribedChannels', SUBSCRIBED_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', FOLLOWED_CHANNELS);
|
||||
|
||||
this.apollo.registerModifier('FollowedChannels_RENAME2', res => this.modifyLiveUsers(res), false);
|
||||
this.apollo.registerModifier('SideNav_SubscribedChannels', res => this.modifyLiveUsers(res, 'subscribedChannels'), 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('FollowedChannels', res => this.modifyLiveUsers(res), false);
|
||||
this.apollo.registerModifier('FollowingLive_CurrentUser', res => this.modifyLiveUsers(res), false);
|
||||
this.apollo.registerModifier('FollowingHosts_CurrentUser', res => this.modifyLiveHosts(res), false);
|
||||
this.hosts = new WeakMap;
|
||||
}
|
||||
|
||||
modifyLiveUsers(res) {
|
||||
const edges = get('data.currentUser.followedLiveUsers.nodes', res);
|
||||
if ( ! edges || ! edges.length )
|
||||
return res;
|
||||
modifyLiveUsers(res, path = 'followedLiveUsers') {
|
||||
const followed_live = get(`data.currentUser.${path}`, res);
|
||||
if ( ! followed_live )
|
||||
return;
|
||||
|
||||
if ( followed_live.nodes )
|
||||
followed_live.nodes = this.parent.processNodes(followed_live.nodes);
|
||||
|
||||
else if ( followed_live.edges )
|
||||
followed_live.edges = this.parent.processNodes(followed_live.edges);
|
||||
|
||||
res.data.currentUser.followedLiveUsers.nodes = this.parent.processNodes(edges);
|
||||
return res;
|
||||
}
|
||||
|
||||
|
@ -103,23 +114,29 @@ export default class Following extends SiteModule {
|
|||
hosted = node.hosting,
|
||||
stream = hosted && hosted.stream;
|
||||
|
||||
if ( ! stream || stream.game && blocked_games.includes(stream.game.game) )
|
||||
if ( ! stream || stream.game && blocked_games.includes(stream.game.name) )
|
||||
continue;
|
||||
|
||||
const store = {}; // stream.viewersCount = new Number(stream.viewersCount || 0);
|
||||
if ( ! stream.viewersCount ) {
|
||||
if ( ! do_grouping || ! hosts[hosted.login] )
|
||||
out.push(edge);
|
||||
continue;
|
||||
}
|
||||
|
||||
const store = stream.viewersCount = new Number(stream.viewersCount || 0);
|
||||
|
||||
store.createdAt = stream.createdAt;
|
||||
store.title = stream.title;
|
||||
store.game = stream.game;
|
||||
//store.game = stream.game;
|
||||
|
||||
if ( do_grouping ) {
|
||||
const host_nodes = hosts[hosted.login];
|
||||
if ( host_nodes ) {
|
||||
host_nodes.push(node);
|
||||
store.host_nodes = node._ffz_host_nodes = host_nodes;
|
||||
this.hosts.set(store, host_nodes);
|
||||
|
||||
} else {
|
||||
store.host_nodes = node._ffz_host_nodes = hosts[hosted.login] = [node];
|
||||
this.hosts.set(store, hosts[hosted.login] = [node]);
|
||||
out.push(edge);
|
||||
}
|
||||
|
||||
|
@ -133,8 +150,13 @@ export default class Following extends SiteModule {
|
|||
|
||||
ensureQueries () {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowedChannels',
|
||||
'data.currentUser.followedLiveUsers.nodes.0.profileImageURL'
|
||||
'FollowedChannels_RENAME2',
|
||||
'data.currentUser.followedLiveUsers.nodes.0.stream.createdAt'
|
||||
);
|
||||
|
||||
this.apollo.ensureQuery(
|
||||
'SideNav_SubscribedChannels',
|
||||
'data.currentUser.subscribedChannels.edges.0.node.stream.createdAt'
|
||||
);
|
||||
|
||||
if ( this.router.current_name !== 'dir-following' )
|
||||
|
|
|
@ -25,7 +25,7 @@ export default class Game extends SiteModule {
|
|||
['dir-game-index', 'dir-community']
|
||||
);
|
||||
|
||||
this.apollo.registerModifier('GamePage_Game', GAME_QUERY);
|
||||
this.apollo.registerModifier('GamePage_Game_RENAME2', GAME_QUERY);
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
|
|
|
@ -22,6 +22,8 @@ export const CARD_CONTEXTS = ((e ={}) => {
|
|||
})();
|
||||
|
||||
|
||||
const CREATIVE_ID = 488191;
|
||||
|
||||
const DIR_ROUTES = ['dir', 'dir-community', 'dir-community-index', 'dir-creative', 'dir-following', 'dir-game-index', 'dir-game-clips', 'dir-game-videos', 'dir-all', 'dir-category', 'user-videos', 'user-clips'];
|
||||
|
||||
|
||||
|
@ -44,7 +46,7 @@ export default class Directory extends SiteModule {
|
|||
this.inject(Game);
|
||||
this.inject(BrowsePopular);
|
||||
|
||||
this.apollo.registerModifier('GamePage_Game', res => this.modifyStreams(res), false);
|
||||
this.apollo.registerModifier('GamePage_Game_RENAME2', res => this.modifyStreams(res), false);
|
||||
|
||||
this.DirectoryCard = this.fine.define(
|
||||
'directory-card',
|
||||
|
@ -52,12 +54,6 @@ export default class Directory extends SiteModule {
|
|||
DIR_ROUTES
|
||||
);
|
||||
|
||||
this.CardWrapper = this.fine.define(
|
||||
'directory-card-wrapper',
|
||||
n => n.renderFallback && n.renderStreamFlag,
|
||||
DIR_ROUTES
|
||||
);
|
||||
|
||||
this.settings.add('directory.uptime', {
|
||||
default: 1,
|
||||
|
||||
|
@ -123,8 +119,11 @@ export default class Directory extends SiteModule {
|
|||
component: 'setting-check-box'
|
||||
},
|
||||
|
||||
changed: () => this.CardWrapper.forceUpdate()
|
||||
changed: () => this.DirectoryCard.forceUpdate()
|
||||
});
|
||||
|
||||
|
||||
this.routeClick = this.routeClick.bind(this);
|
||||
}
|
||||
|
||||
|
||||
|
@ -132,28 +131,25 @@ export default class Directory extends SiteModule {
|
|||
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.on('i18n:update', () => this.DirectoryCard.forceUpdate());
|
||||
|
||||
const t = this,
|
||||
React = await this.web_munch.findModule('react');
|
||||
|
||||
const createElement = React && React.createElement;
|
||||
|
||||
this.CardWrapper.ready(cls => {
|
||||
const old_render = cls.prototype.render;
|
||||
this.DirectoryCard.ready(cls => {
|
||||
const old_render = cls.prototype.render,
|
||||
old_render_iconic = cls.prototype.renderIconicImage,
|
||||
old_render_titles = cls.prototype.renderTitles;
|
||||
|
||||
cls.prototype.render = function() {
|
||||
if ( get('props.streamNode.type', this) === 'rerun' && t.settings.get('directory.hide-vodcasts') )
|
||||
if ( get('props.streamType', this) === 'rerun' && t.settings.get('directory.hide-vodcasts') )
|
||||
return null;
|
||||
|
||||
return old_render.call(this);
|
||||
}
|
||||
|
||||
this.CardWrapper.forceUpdate();
|
||||
});
|
||||
|
||||
this.DirectoryCard.ready(cls => {
|
||||
const old_render_iconic = cls.prototype.renderIconicImage,
|
||||
old_render_titles = cls.prototype.renderTitles;
|
||||
|
||||
cls.prototype.renderIconicImage = function() {
|
||||
if ( this.props.context !== CARD_CONTEXTS.SingleChannelList &&
|
||||
t.settings.get('directory.show-channel-avatars') !== 1 )
|
||||
|
@ -163,27 +159,48 @@ export default class Directory extends SiteModule {
|
|||
}
|
||||
|
||||
cls.prototype.renderTitles = function() {
|
||||
const nodes = get('props.currentViewerCount.host_nodes', this);
|
||||
const nodes = t.following.hosts.get(get('props.currentViewerCount', this));
|
||||
if ( this.props.hostedByChannelLogin == null || ! nodes || ! nodes.length )
|
||||
return old_render_titles.call(this);
|
||||
|
||||
const channel = nodes[0].hosting,
|
||||
stream = channel.stream;
|
||||
stream = channel.stream,
|
||||
game = stream && stream.game,
|
||||
|
||||
channel_url = `/${channel.login}`,
|
||||
game_url = game && `/directory/game/${stream.game.name}`,
|
||||
|
||||
user_link = <a href={channel_url} data-href={channel_url} onClick={t.routeClick} class="tw-link tw-link--inherit">{channel.displayName}</a>,
|
||||
game_link = game && <a href={game_url} data-href={game_url} onClick={t.routeClick} class="tw-link tw-link--inherit">{game.name}</a>;
|
||||
|
||||
return (<div>
|
||||
<a class="tw-link tw-link--inherit" data-test-selector="preview-card-titles__primary-link">
|
||||
<a href={channel_url} data-href={channel_url} onClick={t.routeClick} class="tw-link tw-link--inherit" data-test-selector="preview-card-titles__primary-link">
|
||||
<h3 class="tw-ellipsis tw-font-size-5 tw-strong" title={stream.title}>{stream.title}</h3>
|
||||
</a>
|
||||
<div class="preview-card-titles__subtitle-wrapper">
|
||||
<div data-test-selector="preview-card-titles__subtitle">
|
||||
<p class="tw-c-text-alt tw-ellipsis">
|
||||
<a class="tw-link tw-link--inherit">{channel.displayName}</a> playing <a class="tw-link tw-link--inherit">{stream.game.name}</a>
|
||||
</p>
|
||||
<p class="tw-c-text-alt tw-ellipsis">{
|
||||
game ?
|
||||
game.id == CREATIVE_ID ?
|
||||
t.i18n.tList('directory.user-creative', '%{user} being %{game}', {
|
||||
user: user_link,
|
||||
game: game_link
|
||||
}) :
|
||||
t.i18n.tList('directory.user-playing', '%{user} playing %{game}', {
|
||||
user: user_link,
|
||||
game: game_link
|
||||
})
|
||||
: user_link
|
||||
}</p>
|
||||
</div>
|
||||
<div data-test-selector="preview-card-titles__subtitle">
|
||||
<p class="tw-c-text-alt tw-ellipsis">
|
||||
Hosted by {nodes.length > 1 ? `${nodes.length} channels` : nodes[0].displayName}
|
||||
</p>
|
||||
<p class="tw-c-text-alt tw-ellipsis">{
|
||||
nodes.length > 1 ?
|
||||
t.i18n.t('directory.hosted.by-many', 'Hosted by %{count} channel%{count|en_plural}', nodes.length) :
|
||||
t.i18n.tList('directory.hosted.by-one', 'Hosted by %{user}', {
|
||||
user: <a href={`/${nodes[0].login}`} data-href={`/${nodes[0].login}`} onClick={t.routeClick} class="tw-link tw-link--inherit">{nodes[0].displayName}</a>
|
||||
})
|
||||
}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
|
@ -194,7 +211,7 @@ export default class Directory extends SiteModule {
|
|||
// Game Directory Channel Cards
|
||||
// TODO: Better query handling.
|
||||
this.apollo.ensureQuery(
|
||||
'GamePage_Game',
|
||||
'GamePage_Game_RENAME2',
|
||||
'data.directory.streams.edges.0.node.createdAt'
|
||||
);
|
||||
|
||||
|
@ -216,9 +233,7 @@ export default class Directory extends SiteModule {
|
|||
return;
|
||||
|
||||
const props = inst.props,
|
||||
game = props.gameTitle || props.playerMetadataGame,
|
||||
is_video = props.durationInSeconds != null,
|
||||
is_host = props.hostedByChannelLogin != null;
|
||||
game = props.gameTitle || props.playerMetadataGame;
|
||||
|
||||
container.classList.toggle('ffz-hide-thumbnail', this.settings.provider.get('directory.game.hidden-thumbnails', []).includes(game));
|
||||
|
||||
|
@ -237,18 +252,22 @@ export default class Directory extends SiteModule {
|
|||
processNodes(edges, is_game_query = false, blocked_games) {
|
||||
const out = [];
|
||||
|
||||
if ( blocked_games === undefined )
|
||||
if ( ! Array.isArray(blocked_games) )
|
||||
blocked_games = this.settings.provider.get('directory.game.blocked-games', []);
|
||||
|
||||
for(const edge of edges) {
|
||||
const node = edge.node || edge,
|
||||
store = {}; // node.viewersCount = new Number(node.viewersCount || 0);
|
||||
stream = node.stream || node;
|
||||
|
||||
store.createdAt = node.createdAt;
|
||||
store.title = node.title;
|
||||
store.game = node.game;
|
||||
if ( stream.viewersCount ) {
|
||||
const store = stream.viewersCount = new Number(stream.viewersCount || 0);
|
||||
|
||||
if ( is_game_query || (! node.game || node.game && ! blocked_games.includes(node.game.game)) )
|
||||
store.createdAt = stream.createdAt;
|
||||
store.title = stream.title;
|
||||
//store.game = stream.game;
|
||||
}
|
||||
|
||||
if ( is_game_query || (! stream.game || stream.game && ! blocked_games.includes(stream.game.name)) )
|
||||
out.push(edge);
|
||||
}
|
||||
|
||||
|
@ -257,8 +276,6 @@ export default class Directory extends SiteModule {
|
|||
|
||||
|
||||
modifyStreams(res) { // eslint-disable-line class-methods-use-this
|
||||
this.log.info('Modify Streams', res);
|
||||
|
||||
const is_game_query = get('data.directory.__typename', res) === 'Game',
|
||||
edges = get('data.directory.streams.edges', res);
|
||||
|
||||
|
@ -310,7 +327,7 @@ export default class Directory extends SiteModule {
|
|||
</div>
|
||||
{inst.ffz_uptime_span = <p />}
|
||||
</div>
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-center">
|
||||
<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>
|
||||
|
@ -366,10 +383,12 @@ export default class Directory extends SiteModule {
|
|||
inst.ffz_av_login = props.channelLogin;
|
||||
inst.ffz_av_src = src;
|
||||
|
||||
const link = props.channelLinkTo && props.channelLinkTo.pathname;
|
||||
|
||||
card.appendChild(<a
|
||||
class="ffz-channel-avatar"
|
||||
href={props.channelLinkTo && props.channelLinkTo.pathname}
|
||||
onClick={e => this.routeClick(e, props.channelLinkTo)} // eslint-disable-line react/jsx-no-bind
|
||||
href={link}
|
||||
onClick={e => this.routeClick(e, link)} // eslint-disable-line react/jsx-no-bind
|
||||
>
|
||||
<div class={`tw-absolute tw-right-0 tw-border-l tw-c-background ${is_video ? 'tw-top-0 tw-border-b' : 'tw-bottom-0 tw-border-t'}`}>
|
||||
<figure class="tw-aspect tw-aspect--align-top">
|
||||
|
@ -380,12 +399,25 @@ export default class Directory extends SiteModule {
|
|||
}
|
||||
|
||||
|
||||
routeClick(event, route) {
|
||||
routeClick(event, url) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
if ( route && route.pathname )
|
||||
this.router.history.push(route.pathname);
|
||||
if ( ! url ) {
|
||||
const target = event.currentTarget;
|
||||
|
||||
if ( target ) {
|
||||
const ds = target.dataset;
|
||||
if ( ds && ds.href )
|
||||
url = ds.href;
|
||||
|
||||
else if ( target.href )
|
||||
url = target.href;
|
||||
}
|
||||
}
|
||||
|
||||
if ( url )
|
||||
this.router.history.push(url);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
query {
|
||||
currentUser {
|
||||
subscribedChannels {
|
||||
edges {
|
||||
node {
|
||||
stream {
|
||||
type
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue