mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-05 02:28:31 +00:00
3.5.328. Big refactor to how channel metadata is rendered. Follow buttons and SRL races are working again. Better debugging info for logs. Closes #37.
This commit is contained in:
parent
4e2c2f5056
commit
0e939e30ee
16 changed files with 859 additions and 902 deletions
|
@ -1,3 +1,12 @@
|
|||
<div class="list-header">3.5.328 <time datetime="2016-10-12">(2016-10-13)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Changed: Refactor channel metadata to make it easier to add with less code duplication.</li>
|
||||
<li>Fixed: Follow buttons for Featured channels now appear again.</li>
|
||||
<li>Fixed: SRL race data should now appear again.</li>
|
||||
<li>Changed: Always show the Broadcaster separately in the viewer list, even if you're not currently watching that channel.</li>
|
||||
<li>Changed: Export the entirety of the available debugging information when someone uploads logs.</li>
|
||||
</ul>
|
||||
|
||||
<div class="list-header">3.5.327 <time datetime="2016-10-12">(2016-10-12)</time></div>
|
||||
<ul class="chat-menu-content menu-side-padding">
|
||||
<li>Fixed: More changes to work with the new chat room manager component. Now with less breaking!</li>
|
||||
|
|
|
@ -160,7 +160,7 @@ FFZ.settings_info.hidden_badges = {
|
|||
if ( new_val === null || new_val === undefined )
|
||||
return;
|
||||
|
||||
f.settings.set("hidden_badges", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)));
|
||||
f.settings.set("hidden_badges", _.unique(new_val.trim().toLowerCase().split(/\s*,\s*/)).without(""));
|
||||
}, 600
|
||||
)
|
||||
}
|
||||
|
|
|
@ -154,11 +154,13 @@ FFZ.prototype.cache_command_aliases = function() {
|
|||
// -----------------
|
||||
|
||||
FFZ.ffz_commands.log = function(room, args) {
|
||||
this._pastebin(this._log_data.join("\n"), function(url) {
|
||||
if ( ! url )
|
||||
return this.room_message(room, "There was an error uploading the FrankerFaceZ log.");
|
||||
|
||||
this.room_message(room, "Your FrankerFaceZ log has been pasted to: " + url);
|
||||
var f = this;
|
||||
this.get_debugging_info().then(function(result) {
|
||||
f._pastebin(result).then(function(url) {
|
||||
f.room_message(room, "Your FrankerFaceZ logs have been pasted to: " + url);
|
||||
}).catch(function() {
|
||||
f.room_message(room, "An error occured uploading the logs to a pastebin.");
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -48,38 +48,6 @@ FFZ.prototype.setup_channel = function() {
|
|||
this.log("Hooking the Ember Channel controller.");
|
||||
|
||||
Channel.reopen({
|
||||
/*isEditable: function() {
|
||||
var channel_id = this.get('content.id'),
|
||||
user = this.get('login.userData');
|
||||
|
||||
if ( ! user || ! user.login )
|
||||
return false;
|
||||
|
||||
else if ( user.login === channel_id || user.is_admin || user.is_staff)
|
||||
return true;
|
||||
|
||||
// Okay, have we loaded this user's editor status? Try that.
|
||||
if ( f._editor_of )
|
||||
return f._editor_of.indexOf(channel_id) !== -1;
|
||||
|
||||
var t = this;
|
||||
f.get_user_editor_of().then(function(result) {
|
||||
// Once editor status is loaded, if the user does have editor
|
||||
// status for this channel, update this property.
|
||||
if ( result.indexOf(channel_id) !== -1 )
|
||||
Ember.propertyDidChange(t, 'isEditable');
|
||||
});
|
||||
|
||||
return false;
|
||||
|
||||
}.property('content.id', 'login.userData', 'login.userData.login'),*/
|
||||
|
||||
/*ffzUpdateUptime: function() {
|
||||
if ( f._cindex )
|
||||
f._cindex.ffzUpdateUptime();
|
||||
|
||||
}.observes("isLive", "channel.id"),*/
|
||||
|
||||
ffzUpdateInfo: function() {
|
||||
if ( this._ffz_update_timer )
|
||||
clearTimeout(this._ffz_update_timer);
|
||||
|
@ -134,17 +102,6 @@ FFZ.prototype.setup_channel = function() {
|
|||
id = target && target.get('id'),
|
||||
display_name = target && target.get('display_name');
|
||||
|
||||
/*if ( id !== f.__old_host_target ) {
|
||||
if ( f.__old_host_target )
|
||||
f.ws_send("unsub", "channel." + f.__old_host_target);
|
||||
|
||||
if ( id ) {
|
||||
f.ws_send("sub", "channel." + id);
|
||||
f.__old_host_target = id;
|
||||
} else
|
||||
delete f.__old_host_target;
|
||||
}*/
|
||||
|
||||
if ( display_name )
|
||||
FFZ.capitalization[name] = [display_name, Date.now()];
|
||||
|
||||
|
@ -163,6 +120,9 @@ FFZ.prototype.setup_channel = function() {
|
|||
Channel.ffzUpdateInfo();
|
||||
}
|
||||
|
||||
|
||||
// These have to be done in order to ensure the channel metadata all sorts correctly.
|
||||
|
||||
FFZ.prototype.modify_channel_share_box = function(view) {
|
||||
utils.ember_reopen_view(view, {
|
||||
ffz_init: function() {
|
||||
|
@ -188,6 +148,8 @@ FFZ.prototype.modify_channel_broadcast_link = function(view) {
|
|||
}
|
||||
|
||||
|
||||
// Channel Live
|
||||
|
||||
FFZ.prototype.modify_channel_live = function(view) {
|
||||
var f = this;
|
||||
utils.ember_reopen_view(view, {
|
||||
|
@ -202,10 +164,8 @@ FFZ.prototype.modify_channel_live = function(view) {
|
|||
|
||||
this.ffzUpdateAttributes();
|
||||
this.ffzFixTitle();
|
||||
this.ffzUpdateUptime();
|
||||
this.ffzUpdateChatters();
|
||||
this.ffzUpdateHostButton();
|
||||
this.ffzUpdatePlayerStats();
|
||||
|
||||
this.ffzUpdateMetadata();
|
||||
|
||||
if ( f.settings.auto_theater ) {
|
||||
var layout = this.get('layout'),
|
||||
|
@ -298,89 +258,93 @@ FFZ.prototype.modify_channel_live = function(view) {
|
|||
el && el.html(f.render_tokens(tokens));
|
||||
}.observes('channel.id', 'channel.status', 'channel.game'),
|
||||
|
||||
ffzUpdateUptime: function() {
|
||||
if ( this._ffz_update_uptime ) {
|
||||
clearTimeout(this._ffz_update_uptime);
|
||||
delete this._ffz_update_uptime;
|
||||
}
|
||||
ffzUpdateMetadata: function(key) {
|
||||
var t = this,
|
||||
keys = key ? [key] : Object.keys(FFZ.channel_metadata),
|
||||
basic_info = [this, this.get('channel')],
|
||||
timers = this.ffz_timers = this.ffz_timers || {},
|
||||
|
||||
var container = this.get('element');
|
||||
if ( this.isDestroyed || ! container || ! f.settings.stream_uptime || ! this.get('isLiveAccordingToKraken') )
|
||||
return container && this.$("#ffz-uptime-display").remove();
|
||||
container = this.get('element'),
|
||||
metabar = container && container.querySelector('.cn-metabar__more');
|
||||
|
||||
// Schedule an update.
|
||||
this._ffz_update_uptime = setTimeout(this.ffzUpdateUptime.bind(this), 1000);
|
||||
|
||||
// Determine when the channel last went live.
|
||||
var online = this.get("channel.stream.createdAt"),
|
||||
now = Date.now() - (f._ws_server_offset || 0);
|
||||
|
||||
var uptime = online && Math.floor((now - online.getTime()) / 1000) || -1;
|
||||
if ( uptime < 0 )
|
||||
return this.$("#ffz-uptime-display").remove();
|
||||
|
||||
var el = container.querySelector('#ffz-uptime-display span');
|
||||
if ( ! el ) {
|
||||
var cont = container.querySelector('.cn-metabar__more');
|
||||
if ( ! cont )
|
||||
// Stop once this is destroyed.
|
||||
if ( this.isDestroyed || ! metabar )
|
||||
return;
|
||||
|
||||
var stat = utils.createElement('span'),
|
||||
figure = utils.createElement('figure', 'icon cn-metabar__icon', constants.CLOCK + ' '),
|
||||
stat_wrapper = utils.createElement('div', 'cn-metabar__ffz html-tooltip flex__item', figure);
|
||||
for(var i=0; i < keys.length; i++)
|
||||
this._ffzUpdateStat(keys[i], basic_info, timers, metabar);
|
||||
},
|
||||
|
||||
stat_wrapper.appendChild(stat);
|
||||
stat_wrapper.id = 'ffz-uptime-display';
|
||||
stat_wrapper.title = 'Stream Uptime <nobr>(since ' + online.toLocaleString() + ')</nobr>';
|
||||
|
||||
cont.appendChild(stat_wrapper);
|
||||
el = stat;
|
||||
}
|
||||
|
||||
el.innerHTML = utils.time_to_string(uptime, false, false, false, f.settings.stream_uptime === 1 || f.settings.stream_uptime === 3);
|
||||
}.observes('channel.stream.createdAt', 'isLiveAccordingToKraken'),
|
||||
|
||||
ffzUpdatePlayerStats: function() {
|
||||
if ( this._ffz_update_stats ) {
|
||||
clearTimeout(this._ffz_update_stats);
|
||||
this._ffz_update_stats = null;
|
||||
}
|
||||
|
||||
// Stop scheduling this so it can die.
|
||||
if ( this.isDestroyed )
|
||||
_ffzUpdateStat: function(key, basic_info, timers, metabar) {
|
||||
var t = this,
|
||||
info = FFZ.channel_metadata[key];
|
||||
if ( ! info )
|
||||
return;
|
||||
|
||||
// Schedule an update.
|
||||
if ( f.settings.player_stats )
|
||||
this._ffz_update_stats = setTimeout(this.ffzUpdatePlayerStats.bind(this), 1000);
|
||||
if ( timers[key] )
|
||||
clearTimeout(timers[key]);
|
||||
|
||||
var channel_id = this.get("channel.id"),
|
||||
container = this.get("element"),
|
||||
player_cont = f.players && f.players[channel_id],
|
||||
player, stats;
|
||||
// Build the data we use for function calls.
|
||||
var data = info.setup ? info.setup.apply(f, basic_info) : basic_info,
|
||||
refresh = typeof info.refresh === "function" ? info.refresh.apply(f, data) : info.refresh;
|
||||
|
||||
try {
|
||||
player = player_cont && player_cont.get('player');
|
||||
stats = player && player.getVideoInfo();
|
||||
} catch(err) { } // This gets spammy if we try logging it.
|
||||
// If we have a positive refresh value, schedule another go.
|
||||
if ( refresh )
|
||||
timers[key] = setTimeout(this.ffzUpdateMetadata.bind(this, key), typeof refresh === "number" ? refresh : 1000);
|
||||
|
||||
if ( ! container || ! f.settings.player_stats || ! stats || ! stats.hls_latency_broadcaster )
|
||||
return container && this.$("#ffz-player-stats").remove();
|
||||
var el = metabar.querySelector('.cn-metabar__ffz[data-key="' + key + '"]'),
|
||||
je,
|
||||
stat,
|
||||
dynamic_tooltip = typeof info.tooltip === "function",
|
||||
label = typeof info.label === "function" ? info.label.apply(f, data) : info.label;
|
||||
|
||||
if ( ! label ) {
|
||||
if ( el )
|
||||
el.parentElement.removeChild(el);
|
||||
|
||||
if ( f._popup && f._popup.id === 'ffz-metadata-popup' && f._popup.getAttribute('data-key') === key )
|
||||
f.close_popup();
|
||||
|
||||
var je, el = container.querySelector("#ffz-player-stats");
|
||||
if ( ! el ) {
|
||||
var cont = container.querySelector('.cn-metabar__more');
|
||||
if ( ! cont )
|
||||
return;
|
||||
|
||||
var stat = utils.createElement('span'),
|
||||
figure = utils.createElement('figure', 'icon cn-metabar__icon', constants.GRAPH + ' ');
|
||||
} else if ( ! el ) {
|
||||
var btn,
|
||||
static_label = typeof info.static_label === "function" ? info.static_label.apply(f, data) : info.static_label;
|
||||
|
||||
el = utils.createElement('div', 'cn-metabar__ffz flex__item', figure);
|
||||
el.id = 'ffz-player-stats';
|
||||
el.appendChild(stat);
|
||||
if ( ! static_label )
|
||||
static_label = '';
|
||||
else if ( static_label.substr(0,4) === '<svg' )
|
||||
static_label = utils.createElement('figure', 'icon cn-metabar__icon', static_label + ' ');
|
||||
|
||||
je = jQuery(el);
|
||||
if ( info.popup ) {
|
||||
btn = utils.createElement('button', 'button button--dropmenu', static_label)
|
||||
el = utils.createElement('div', 'cn-metabar__ffz flex__item ember-view balloon-wrapper inline-block', btn);
|
||||
|
||||
btn.classList.add(info.button ? 'button--hollow' : 'button--text');
|
||||
|
||||
} else if ( info.button ) {
|
||||
btn = utils.createElement('button', 'button', static_label);
|
||||
el = utils.createElement('div', 'cn-metabar__ffz flex__item ember-view inline-block', btn);
|
||||
|
||||
btn.classList.add(typeof info.button === 'string' ? info.button : 'button--hollow');
|
||||
|
||||
} else
|
||||
btn = el = utils.createElement('div', 'cn-metabar__ffz flex__item', static_label);
|
||||
|
||||
el.setAttribute('data-key', key);
|
||||
if ( info.order )
|
||||
el.style.order = info.order;
|
||||
|
||||
if ( ! dynamic_tooltip && info.tooltip ) {
|
||||
btn.classList.add('html-tooltip');
|
||||
btn.title = info.tooltip;
|
||||
}
|
||||
|
||||
stat = utils.createElement('span', 'ffz-label');
|
||||
btn.appendChild(stat);
|
||||
|
||||
if ( dynamic_tooltip ) {
|
||||
je = jQuery(btn)
|
||||
je.hover(
|
||||
function() { je.data("hover", true).tipsy("show") },
|
||||
function() { je.data("hover", false).tipsy("hide") })
|
||||
|
@ -388,169 +352,89 @@ FFZ.prototype.modify_channel_live = function(view) {
|
|||
.tipsy({
|
||||
trigger: 'manual',
|
||||
html: true,
|
||||
gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')
|
||||
gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n'),
|
||||
title: function() {
|
||||
var data = [t, t.get('channel')];
|
||||
data = info.setup ? info.setup.apply(f, data) : data;
|
||||
return info.tooltip.apply(f, data);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if ( info.click )
|
||||
btn.addEventListener('click', function(e) {
|
||||
if ( btn.disabled || btn.classList.contains('disabled') )
|
||||
return false;
|
||||
|
||||
e.update_stat = t._ffzUpdateStat.bind(t, key, basic_info, timers, metabar);
|
||||
|
||||
var data = [t, t.get('channel')];
|
||||
data = info.setup ? info.setup.apply(f, data) : data;
|
||||
data.unshift(btn);
|
||||
data.unshift(e);
|
||||
return info.click.apply(f, data);
|
||||
});
|
||||
|
||||
cont.appendChild(el);
|
||||
} else
|
||||
je = jQuery(el)
|
||||
if ( info.popup ) {
|
||||
btn.classList.add('button--dropmenu');
|
||||
btn.addEventListener('click', function(el, e) {
|
||||
if ( btn.disabled || btn.classList.contains('disabled') )
|
||||
return false;
|
||||
|
||||
var stat = el.querySelector('span'),
|
||||
var popup = f.close_popup();
|
||||
if ( popup && popup.id === 'ffz-metadata-popup' && popup.getAttribute('data-key') === key )
|
||||
return;
|
||||
|
||||
delay = Math.round(stats.hls_latency_broadcaster / 10) / 100,
|
||||
dropped = utils.number_commas(stats.dropped_frames || 0),
|
||||
bitrate;
|
||||
var data = [t, t.get('channel')];
|
||||
data = info.setup ? info.setup.apply(f, data) : data;
|
||||
|
||||
if ( stats.playback_bytes_per_second )
|
||||
bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 100;
|
||||
else
|
||||
bitrate = Math.round(stats.current_bitrate * 100) / 100;
|
||||
var balloon = utils.createElement('div', 'balloon balloon--up show');
|
||||
data.unshift(balloon);
|
||||
|
||||
var is_old = delay > 180;
|
||||
if ( is_old ) {
|
||||
delay = Math.floor(delay);
|
||||
stat.textContent = utils.time_to_string(delay, true, delay > 172800) + ' old';
|
||||
} else {
|
||||
delay = delay.toString();
|
||||
var ind = delay.indexOf('.');
|
||||
delay += (ind === -1 ? '.00' : (ind >= delay.length - 2 ? '0' : '')) + 's';
|
||||
stat.textContent = delay;
|
||||
balloon.id = 'ffz-metadata-popup';
|
||||
balloon.setAttribute('data-key', key);
|
||||
|
||||
var result = info.popup.apply(f, data);
|
||||
if ( result === false )
|
||||
return false;
|
||||
|
||||
// Set the balloon to face away from the nearest side of the channel.
|
||||
var container = t.get('element'),
|
||||
outer = container.getBoundingClientRect(),
|
||||
rect = el.getBoundingClientRect();
|
||||
|
||||
balloon.classList.toggle('balloon--right', (rect.left - outer.left) > (outer.right - rect.right));
|
||||
|
||||
f._popup_kill = info.on_popup_close ? function() { info.on_popup_close.apply(f, data) } : null;
|
||||
f._popup_allow_parent = true;
|
||||
f._popup = balloon;
|
||||
|
||||
el.appendChild(balloon);
|
||||
}.bind(this, el));
|
||||
}
|
||||
|
||||
el.setAttribute('original-title', (is_old ? 'Video Information<br>' +
|
||||
'Broadcast ' + utils.time_to_string(delay, true) + ' Ago<br><br>' : 'Stream Latency<br>') +
|
||||
'Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p ' + stats.current_fps + ' fps<br>' +
|
||||
'Playback Rate: ' + bitrate + ' Kbps<br>' +
|
||||
'Dropped Frames: ' + dropped);
|
||||
metabar.appendChild(el);
|
||||
el = btn;
|
||||
|
||||
if ( je.data("hover") )
|
||||
} else {
|
||||
stat = el.querySelector('span.ffz-label');
|
||||
if ( dynamic_tooltip )
|
||||
je = jQuery(el);
|
||||
}
|
||||
|
||||
stat.innerHTML = label;
|
||||
|
||||
if ( dynamic_tooltip && je.data("hover") )
|
||||
je.tipsy("hide").tipsy("show");
|
||||
|
||||
if ( info.hasOwnProperty('disabled') )
|
||||
el.classList.toggle('disabled', typeof info.disabled === "function" ? info.disabled.apply(f, data) : info.disabled);
|
||||
},
|
||||
|
||||
ffzUpdateChatters: function() {
|
||||
var channel_id = this.get("channel.id"),
|
||||
room = f.rooms && f.rooms[channel_id],
|
||||
container = this.get('element');
|
||||
|
||||
if ( ! container || ! room || ! f.settings.chatter_count )
|
||||
return container && this.$("#ffz-chatter-display").remove();
|
||||
|
||||
var chatter_count = Object.keys(room.room.get('ffz_chatters') || {}).length,
|
||||
el = container.querySelector('#ffz-chatter-display span');
|
||||
|
||||
if ( ! el ) {
|
||||
var cont = container.querySelector('.cn-metabar__more');
|
||||
if ( ! cont )
|
||||
return;
|
||||
|
||||
var stat = utils.createElement('span'),
|
||||
figure = utils.createElement('figure', 'icon cn-metabar__icon', constants.ROOMS + ' '),
|
||||
balloon = utils.createElement('div', 'balloon balloon--tooltip balloon--down balloon--center', 'Currently in Chat'),
|
||||
balloon_wrapper = utils.createElement('div', 'balloon-wrapper', figure),
|
||||
stat_wrapper = utils.createElement('div', 'cn-metabar__ffz flex__item mg-l-1', balloon_wrapper);
|
||||
|
||||
balloon_wrapper.appendChild(stat);
|
||||
balloon_wrapper.appendChild(balloon);
|
||||
|
||||
stat_wrapper.id = 'ffz-chatter-display';
|
||||
|
||||
var viewers = cont.querySelector('#ffz-player-stats') || cont.querySelector('#ffz-uptime-display') || cont.querySelector(".cn-metabar__livecount") || cont.querySelector(".cn-metabar__viewcount");
|
||||
if ( viewers )
|
||||
cont.insertBefore(stat_wrapper, viewers.nextSibling);
|
||||
else
|
||||
cont.appendChild(stat_wrapper);
|
||||
|
||||
el = stat;
|
||||
}
|
||||
|
||||
el.innerHTML = utils.number_commas(chatter_count);
|
||||
}.observes('channel.id'),
|
||||
|
||||
ffzUpdateHostButton: function() {
|
||||
var t = this,
|
||||
channel_id = this.get("channel.id"),
|
||||
hosted_id = this.get("channel.hostModeTarget.id"),
|
||||
|
||||
user = f.get_user(),
|
||||
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
|
||||
now_hosting = room && room.ffz_host_target,
|
||||
hosts_left = room && room.ffz_hosts_left,
|
||||
|
||||
el = this.get("element"),
|
||||
|
||||
update_button = function(channel, container, before) {
|
||||
if ( ! container )
|
||||
return;
|
||||
|
||||
var btn = container.querySelector('#ffz-ui-host-button');
|
||||
|
||||
if ( ! f.settings.stream_host_button || ! user || user.login === channel ) {
|
||||
if ( btn )
|
||||
btn.parentElement.removeChild(btn);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! btn ) {
|
||||
btn = utils.createElement('button', 'button button--hollow mg-l-1'),
|
||||
|
||||
btn.id = 'ffz-ui-host-button';
|
||||
btn.addEventListener('click', t.ffzClickHost.bind(t, channel !== channel_id));
|
||||
|
||||
if ( before )
|
||||
container.insertBefore(btn, before);
|
||||
else
|
||||
container.appendChild(btn);
|
||||
|
||||
jQuery(btn).tipsy({html: true, gravity: utils.tooltip_placement(constants.TOOLTIP_DISTANCE, 'n')});
|
||||
}
|
||||
|
||||
btn.classList.remove('disabled');
|
||||
btn.innerHTML = channel === now_hosting ? 'Unhost' : 'Host';
|
||||
if ( now_hosting ) {
|
||||
var name = FFZ.get_capitalization(now_hosting);
|
||||
btn.title = 'You are now hosting ' + f.format_display_name(name, now_hosting, true)[0] + '.';
|
||||
} else
|
||||
btn.title = 'You are not hosting any channel.';
|
||||
|
||||
if ( typeof hosts_left === 'number' )
|
||||
btn.title += ' You have ' + hosts_left + ' host command' + utils.pluralize(hosts_left) + ' remaining this half hour.';
|
||||
};
|
||||
|
||||
if ( ! el )
|
||||
return;
|
||||
|
||||
this.set("ffz_host_updating", false);
|
||||
|
||||
if ( channel_id ) {
|
||||
var container = el.querySelector('.cn-metabar__more'),
|
||||
share = container && container.querySelector('.js-share-box');
|
||||
|
||||
update_button(channel_id, container, share ? share.parentElement : null);
|
||||
}
|
||||
|
||||
if ( hosted_id )
|
||||
update_button(hosted_id, el.querySelector('.cn-hosting--bottom'));
|
||||
}.observes('channel.id', 'channel.hostModeTarget.id'),
|
||||
|
||||
ffzClickHost: function(is_host, e) {
|
||||
var btn = e.target,
|
||||
target = this.get(is_host ? 'channel.hostModeTarget.id' : 'channel.id'),
|
||||
user = f.get_user(),
|
||||
room = user && f.rooms && f.rooms[user.login] && f.rooms[user.login].room,
|
||||
now_hosting = room && room.ffz_host_target;
|
||||
|
||||
if ( ! room || this.get('ffz_host_updating') )
|
||||
return;
|
||||
|
||||
btn.classList.add('disabled');
|
||||
btn.title = 'Updating...';
|
||||
|
||||
this.set('ffz_host_updating', true);
|
||||
if ( now_hosting === target )
|
||||
room.send('/unhost', true);
|
||||
else
|
||||
room.send('/host ' + target, true);
|
||||
}
|
||||
this.set('ffz_host_updating', false);
|
||||
return this.ffzUpdateMetadata('host');
|
||||
}.observes('channel.id', 'channel.hostModeTarget.id')
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ FFZ.settings_info.player_stats = {
|
|||
if ( ! this._cindex )
|
||||
return;
|
||||
|
||||
this._cindex.ffzUpdatePlayerStats();
|
||||
this._cindex.ffzUpdateMetadata('player_stats');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -2232,7 +2232,7 @@ FFZ.prototype._modify_room = function(room) {
|
|||
return;
|
||||
|
||||
if ( f._cindex )
|
||||
f._cindex.ffzUpdateChatters();
|
||||
f._cindex.ffzUpdateMetadata('chatters');
|
||||
|
||||
if ( window !== window.parent && parent.postMessage )
|
||||
parent.postMessage({from_ffz: true, command: 'chatter_count', data: Object.keys(this.get('ffz_chatters') || {}).length}, "*"); //location.protocol + "//www.twitch.tv/");
|
||||
|
|
|
@ -49,22 +49,15 @@ FFZ.prototype.modify_viewer_list = function(component) {
|
|||
|
||||
// Get the broadcaster name.
|
||||
var Channel = utils.ember_lookup('controller:channel'),
|
||||
room_id = this.get('model.id'),
|
||||
broadcaster = Channel && Channel.get('model.id');
|
||||
broadcaster = room_id = this.get('model.id');
|
||||
|
||||
// We can get capitalization for the broadcaster from the channel.
|
||||
if ( broadcaster ) {
|
||||
var display_name = Channel.get('model.display_name');
|
||||
if ( Channel && Channel.get('channelModel.id') === room_id ) {
|
||||
var display_name = Channel.get('channelModel.displayName');
|
||||
if ( display_name )
|
||||
FFZ.capitalization[broadcaster] = [display_name, Date.now()];
|
||||
}
|
||||
|
||||
// If the current room isn't the channel's chat, then we shouldn't
|
||||
// display them as the broadcaster.
|
||||
if ( room_id !== broadcaster )
|
||||
broadcaster = null;
|
||||
|
||||
|
||||
// Iterate over everything~!
|
||||
for(var i=0; i < VIEWER_CATEGORIES.length; i++) {
|
||||
var data = raw_viewers[VIEWER_CATEGORIES[i][0]],
|
||||
|
|
34
src/main.js
34
src/main.js
|
@ -30,11 +30,12 @@ FFZ.get = function() { return FFZ.instance; }
|
|||
|
||||
// TODO: This should be in a module.
|
||||
FFZ.msg_commands = {};
|
||||
FFZ.channel_metadata = {};
|
||||
|
||||
|
||||
// Version
|
||||
var VER = FFZ.version_info = {
|
||||
major: 3, minor: 5, revision: 327,
|
||||
major: 3, minor: 5, revision: 328,
|
||||
toString: function() {
|
||||
return [VER.major, VER.minor, VER.revision].join(".") + (VER.extra || "");
|
||||
}
|
||||
|
@ -86,21 +87,29 @@ FFZ.prototype.error = function(msg, error, to_json, log_json) {
|
|||
|
||||
|
||||
FFZ.prototype.paste_logs = function() {
|
||||
this._pastebin(this._log_data.join("\n"), function(url) {
|
||||
if ( ! url )
|
||||
return console.log("FFZ Error: Unable to upload log to pastebin.");
|
||||
var f = this,
|
||||
output = function(result) {
|
||||
f._pastebin(result).then(function(url) {
|
||||
f.log("Your FrankerFaceZ logs have been uploaded to: " + url);
|
||||
}).catch(function() {
|
||||
f.error("An error occured uploading the logs to a pastebin.");
|
||||
});
|
||||
}
|
||||
|
||||
console.log("FFZ: Your FrankerFaceZ log has been pasted to: " + url);
|
||||
this.get_debugging_info().then(function(data) {
|
||||
output(data);
|
||||
}).catch(function(err) {
|
||||
f.error("Error building debugging information.", err);
|
||||
output(f._log_data.join("\n"));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
FFZ.prototype._pastebin = function(data, callback) {
|
||||
jQuery.ajax({url: "https://putco.de/", type: "PUT", data: data, context: this})
|
||||
.success(function(e) {
|
||||
callback.call(this, e.trim() + ".log");
|
||||
}).fail(function(e) {
|
||||
callback.call(this, null);
|
||||
FFZ.prototype._pastebin = function(data) {
|
||||
return new Promise(function(succeed, fail) {
|
||||
jQuery.ajax({url: "https://putco.de/", type: "PUT", data: data})
|
||||
.success(function(e) { succeed(e.trim() + ".log"); })
|
||||
.fail(function(e) { fail(null); });
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -202,6 +211,7 @@ require('./ext/emote_menu');
|
|||
|
||||
require('./featurefriday');
|
||||
|
||||
require('./ui/channel_stats');
|
||||
require('./ui/logviewer');
|
||||
//require('./ui/chatpane');
|
||||
require('./ui/popups');
|
||||
|
@ -209,7 +219,7 @@ require('./ui/styles');
|
|||
require('./ui/dark');
|
||||
require('./ui/tooltips');
|
||||
require('./ui/notifications');
|
||||
require('./ui/viewer_count');
|
||||
//require('./ui/viewer_count');
|
||||
require('./ui/sub_count');
|
||||
require('./ui/dash_stats');
|
||||
require('./ui/dash_feed');
|
||||
|
|
|
@ -115,7 +115,7 @@ FFZ.prototype.reset_settings = function() {
|
|||
}
|
||||
|
||||
|
||||
FFZ.prototype._get_settings_object = function() {
|
||||
FFZ.prototype._get_settings_object = function(skip_default) {
|
||||
var data = {
|
||||
version: 1,
|
||||
script_version: FFZ.version_info + '',
|
||||
|
@ -131,7 +131,7 @@ FFZ.prototype._get_settings_object = function() {
|
|||
var info = FFZ.settings_info[key],
|
||||
ls_key = info.storage_key || make_ls(key);
|
||||
|
||||
if ( localStorage.hasOwnProperty(ls_key) )
|
||||
if ( localStorage.hasOwnProperty(ls_key) && (!skip_default || this.settings[key] !== info.value) )
|
||||
data.settings[key] = this.settings[key];
|
||||
}
|
||||
|
||||
|
|
|
@ -3,11 +3,7 @@ var FFZ = window.FrankerFaceZ,
|
|||
utils = require("../utils"),
|
||||
createElement = utils.createElement,
|
||||
|
||||
NICE_DESCRIPTION = {
|
||||
"cluster": null,
|
||||
"manifest_cluster": null,
|
||||
"user_ip": null
|
||||
};
|
||||
BANNED_KEYS = ['user_ip'];
|
||||
|
||||
|
||||
// -------------------
|
||||
|
@ -156,18 +152,24 @@ FFZ.debugging_blocks = {
|
|||
return succeed(output);
|
||||
}
|
||||
|
||||
var perms = [],
|
||||
ul = data.me.valid ? data.me.level : 0,
|
||||
chan = data.channel;
|
||||
|
||||
ul >= chan.viewlogs && perms.push('view');
|
||||
ul >= chan.viewmodlogs && perms.push('view-mod');
|
||||
ul >= chan.viewcomments && perms.push('comment-view');
|
||||
ul >= chan.writecomments && perms.push('comment-write');
|
||||
ul >= chan.deletecomments && perms.push('comment-delete');
|
||||
|
||||
output.push(['Logging Enabled', data.channel.active === 1]);
|
||||
output.push(['User Level', data.me.valid ? data.me.level : '<i>invalid</i>']);
|
||||
output.push(['Level: View Logs', data.channel.viewlogs]);
|
||||
output.push(['Level: View Moderation Logs', data.channel.viewmodlogs]);
|
||||
output.push(['Level: View Comments', data.channel.viewcomments]);
|
||||
output.push(['Level: Write Comments', data.channel.writecomments]);
|
||||
output.push(['Level: Delete Comments', data.channel.deletecomments]);
|
||||
output.push(['User Permissions', perms.join(', ') || '<i>none</i>']);
|
||||
|
||||
succeed(output);
|
||||
|
||||
}).catch(function(err) {
|
||||
succeed(['Authentication', '<i>unable to get token</i>']);
|
||||
succeed([['Authentication', '<i>unable to get token</i>']]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -286,13 +288,33 @@ FFZ.debugging_blocks = {
|
|||
var sorted_keys = Object.keys(data).sort(),
|
||||
output = [];
|
||||
|
||||
for(var i=0; i < sorted_keys.length; i++)
|
||||
output.push([sorted_keys[i], data[sorted_keys[i]]]);
|
||||
for(var i=0; i < sorted_keys.length; i++) {
|
||||
var key = sorted_keys[i];
|
||||
if ( BANNED_KEYS.indexOf(key) === -1 )
|
||||
output.push([key, data[key]]);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
},
|
||||
|
||||
settings: {
|
||||
order: 8,
|
||||
title: "Current Settings",
|
||||
refresh: false,
|
||||
type: "text",
|
||||
|
||||
render: function() {
|
||||
var output = this._get_settings_object(true).settings;
|
||||
delete output.favorite_settings;
|
||||
delete output.mod_card_reasons;
|
||||
delete output.emote_menu_collapsed;
|
||||
delete output.favorite_emotes;
|
||||
|
||||
return JSON.stringify(output, null, 2);
|
||||
}
|
||||
},
|
||||
|
||||
logs: {
|
||||
order: 100,
|
||||
title: "Logs",
|
||||
|
@ -305,6 +327,78 @@ FFZ.debugging_blocks = {
|
|||
}
|
||||
}
|
||||
|
||||
FFZ.prototype._sorted_debug_blocks = function() {
|
||||
var segments = [];
|
||||
for(var key in FFZ.debugging_blocks) {
|
||||
var info = FFZ.debugging_blocks[key];
|
||||
if ( ! info )
|
||||
continue;
|
||||
|
||||
var visible = info.visible || true;
|
||||
if ( typeof visible === "function" )
|
||||
visible = visible.call(this);
|
||||
|
||||
if ( ! visible )
|
||||
continue;
|
||||
|
||||
segments.push([info.order || 50, info]);
|
||||
}
|
||||
|
||||
segments.sort(function(a,b) { return a[0] > b[0] });
|
||||
return segments;
|
||||
}
|
||||
|
||||
FFZ.prototype.get_debugging_info = function() {
|
||||
var f = this;
|
||||
return new Promise(function(succeed, fail) {
|
||||
var output = [
|
||||
'FrankerFaceZ - Debugging Information',
|
||||
(new Date).toISOString(), ''];
|
||||
|
||||
var segments = f._sorted_debug_blocks(),
|
||||
promises = [];
|
||||
|
||||
for(var i=0; i < segments.length; i++) {
|
||||
var info = segments[i][1];
|
||||
promises.push(new Promise(function(info, s) {
|
||||
var result = info.render.call(f);
|
||||
if (!( result instanceof Promise ))
|
||||
result = Promise.resolve(result);
|
||||
|
||||
result.then(function(data) {
|
||||
var el = utils.createElement('span'),
|
||||
out = [info.title, '----------------------------------------'];
|
||||
if ( info.type === 'list' )
|
||||
for(var x=0; x < data.length; x++) {
|
||||
if ( data[x] ) {
|
||||
el.innerHTML = data[x].join(': ');
|
||||
out.push(el.textContent);
|
||||
} else
|
||||
out.push('');
|
||||
}
|
||||
else if ( info.type === 'text' )
|
||||
out.push(data);
|
||||
|
||||
s(out);
|
||||
}).catch(function(err) {
|
||||
s(['', info.title, 'Error: ' + err]);
|
||||
});
|
||||
|
||||
}.bind(f, info)));
|
||||
}
|
||||
|
||||
Promise.all(promises).then(function(result) {
|
||||
for(var i=0; i < result.length; i++) {
|
||||
output.push.apply(output, result[i]);
|
||||
output.push('');
|
||||
output.push('');
|
||||
}
|
||||
|
||||
succeed(output.join('\n').trim());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
var include_html = function(heading_text, filename, callback) {
|
||||
return function(view, container) {
|
||||
|
@ -436,12 +530,15 @@ FFZ.menu_pages.about = {
|
|||
return;
|
||||
|
||||
getting_logs = true;
|
||||
f._pastebin(f._log_data.join("\n"), function(url) {
|
||||
|
||||
f.get_debugging_info().then(function(data) {
|
||||
f._pastebin(data).then(function(url) {
|
||||
getting_logs = false;
|
||||
if ( ! url )
|
||||
alert("There was an error uploading the FrankerFaceZ logs.");
|
||||
else
|
||||
prompt("Your FrankerFaceZ logs have been uploaded to the URL:", url);
|
||||
}).catch(function() {
|
||||
getting_logs = false;
|
||||
alert("An error occured uploading your FrankerFaceZ logs.");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -493,24 +590,7 @@ FFZ.menu_pages.about = {
|
|||
container.appendChild(createElement('div', 'chat-menu-content center',
|
||||
'<h1>FrankerFaceZ</h1><div class="ffz-about-subheading">woofs for nerds</div>'));
|
||||
|
||||
var segments = [];
|
||||
for(var key in FFZ.debugging_blocks) {
|
||||
var info = FFZ.debugging_blocks[key];
|
||||
if ( ! info )
|
||||
continue;
|
||||
|
||||
var visible = info.visible || true;
|
||||
if ( typeof visible === "function" )
|
||||
visible = visible.call(this);
|
||||
|
||||
if ( ! visible )
|
||||
continue;
|
||||
|
||||
segments.push([info.order || 50, info]);
|
||||
}
|
||||
|
||||
segments.sort(function(a,b) { return a[0] > b[0] });
|
||||
|
||||
var segments = this._sorted_debug_blocks();
|
||||
for(var i=0; i < segments.length; i++) {
|
||||
var info = segments[i][1],
|
||||
output;
|
||||
|
|
|
@ -1,13 +1,165 @@
|
|||
var FFZ = window.FrankerFaceZ,
|
||||
constants = require('../constants'),
|
||||
utils = require('../utils');
|
||||
utils = require('../utils'),
|
||||
|
||||
metadata = FFZ.channel_metadata;
|
||||
|
||||
|
||||
// --------------
|
||||
// Channel Stats
|
||||
// --------------
|
||||
|
||||
FFZ.stat_info = {};
|
||||
metadata.uptime = {
|
||||
refresh: function(channel) { return this.settings.stream_uptime > 0; },
|
||||
|
||||
setup: function(view, channel) {
|
||||
var online = channel.get('stream.createdAt'),
|
||||
now = Date.now() - (this._ws_server_offset || 0),
|
||||
|
||||
uptime = online && Math.floor((now - online.getTime()) / 1000) || -1;
|
||||
|
||||
return [online, uptime];
|
||||
},
|
||||
|
||||
order: 2,
|
||||
static_label: constants.CLOCK,
|
||||
label: function(online, uptime) {
|
||||
var setting = this.settings.stream_uptime;
|
||||
if ( uptime < 0 || ! setting )
|
||||
return null;
|
||||
|
||||
return utils.time_to_string(uptime, false, false, false, setting === 1 || setting === 3);
|
||||
},
|
||||
|
||||
tooltip: function(online) {
|
||||
return 'Stream Uptime <nobr>(since ' + online.toLocaleString() + ')</nobr>';
|
||||
}
|
||||
};
|
||||
|
||||
metadata.chatters = {
|
||||
refresh: false,
|
||||
|
||||
static_label: constants.ROOMS,
|
||||
label: function(view, channel) {
|
||||
var channel_id = channel.get('id'),
|
||||
room = this.rooms[channel_id];
|
||||
|
||||
if ( ! room || ! this.settings.chatter_count )
|
||||
return null;
|
||||
|
||||
return utils.number_commas(Object.keys(room.room.get('ffz_chatters') || {}).length);
|
||||
},
|
||||
|
||||
tooltip: 'Currently in Chat'
|
||||
};
|
||||
|
||||
metadata.player_stats = {
|
||||
refresh: function() { return this.settings.player_stats },
|
||||
|
||||
setup: function(view, channel) {
|
||||
var channel_id = channel.get('id'),
|
||||
player_cont = this.players && this.players[channel_id],
|
||||
player = player_cont && player_cont.player,
|
||||
stats;
|
||||
|
||||
try {
|
||||
stats = player.getVideoInfo();
|
||||
} catch(err) { }
|
||||
|
||||
var delay = stats && Math.round(stats.hls_latency_broadcaster / 10) / 100;
|
||||
return [stats, delay, delay > 180, player_cont];
|
||||
},
|
||||
|
||||
order: 3,
|
||||
static_label: constants.GRAPH,
|
||||
label: function(stats, delay, is_old) {
|
||||
if ( ! this.settings.player_stats || ! stats || ! stats.hls_latency_broadcaster )
|
||||
return null;
|
||||
|
||||
if ( is_old )
|
||||
return utils.time_to_string(Math.floor(delay), true, delay > 172800) + ' old'
|
||||
else {
|
||||
delay = delay.toString();
|
||||
var ind = delay.indexOf('.');
|
||||
return delay + (ind === -1 ? '.00' : (ind >= delay.length - 2 ? '0' : '')) + 's';
|
||||
}
|
||||
},
|
||||
|
||||
click: function(event, button, stats, delay, is_old, player_cont) {
|
||||
player_cont.$('.js-stats-toggle').click();
|
||||
},
|
||||
|
||||
tooltip: function(stats, delay, is_old) {
|
||||
if ( ! stats || ! stats.hls_latency_broadcaster )
|
||||
return 'Stream Latency';
|
||||
|
||||
var bitrate;
|
||||
if ( stats.playback_bytes_per_second )
|
||||
bitrate = Math.round(stats.playback_bytes_per_second * 8 / 10.24) / 1000;
|
||||
else
|
||||
bitrate = Math.round(stats.current_bitrate * 100) / 100;
|
||||
|
||||
return (is_old ? 'Video Information<br>' +
|
||||
'Broadcast ' + utils.time_to_string(Math.floor(delay), true) + ' Ago<br><br>' : 'Stream Latency<br>') +
|
||||
'Video: ' + stats.vid_width + 'x' + stats.vid_height + 'p' + stats.current_fps + '<br>' +
|
||||
'Playback Rate: ' + utils.number_commas(bitrate) + ' Kbps<br>' +
|
||||
'Dropped Frames: ' + utils.number_commas(stats.dropped_frames || 0);
|
||||
}
|
||||
};
|
||||
|
||||
metadata.host = {
|
||||
refresh: false,
|
||||
|
||||
setup: function(view, channel) {
|
||||
var channel_id = channel.get('id'),
|
||||
user = this.get_user(),
|
||||
room = user && this.rooms[user.login] && this.rooms[user.login].room,
|
||||
now_hosting = room && room.ffz_host_target,
|
||||
hosts_remaining = room && room.ffz_hosts_left;
|
||||
|
||||
return [user, channel_id, now_hosting, hosts_remaining, view.get('ffz_host_updating'), view];
|
||||
},
|
||||
|
||||
order: 98,
|
||||
label: function(user, channel_id, hosting_id) {
|
||||
if ( ! user || user.login === channel_id )
|
||||
return null;
|
||||
|
||||
return channel_id === hosting_id ? 'Unhost' : 'Host';
|
||||
},
|
||||
|
||||
button: true,
|
||||
disabled: function(user, channel_id, hosting_id, hosts_remaining, updating, view) {
|
||||
return !!view.get('ffz_host_updating')
|
||||
},
|
||||
|
||||
click: function(event, button, user, channel_id, hosting_id, hosts_remaining, updating, view) {
|
||||
view.set('ffz_host_updating', true);
|
||||
event.update_stat();
|
||||
|
||||
var room = user && this.rooms[user.login] && this.rooms[user.login].room;
|
||||
if ( channel_id === hosting_id )
|
||||
room.send('/unhost', true);
|
||||
else
|
||||
room.send('/host ' + channel_id, true);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
tooltip: function(user, channel_id, hosting_id, hosts_remaining, updating) {
|
||||
var out;
|
||||
if ( updating )
|
||||
return 'Updating...';
|
||||
|
||||
if ( hosting_id ) {
|
||||
var display_name = FFZ.get_capitalization(hosting_id);
|
||||
out = 'You are now hosting ' + this.format_display_name(display_name, hosting_id, true)[0] + '.';
|
||||
} else
|
||||
out = 'You are not hosting any channel.';
|
||||
|
||||
return out + (hosts_remaining ? ' You have ' + hosts_remaining + ' host command' + utils.pluralize(hosts_remaining) + ' remaining this half hour.' : '');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// ---------------
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
var FFZ = window.FrankerFaceZ,
|
||||
utils = require('../utils'),
|
||||
constants = require('../constants'),
|
||||
|
||||
VALID_CHANNEL = /^[A-Za-z0-9_]+$/,
|
||||
TWITCH_URL = /^(?:https?:\/\/)?(?:www\.)?twitch\.tv\/([A-Za-z0-9_]+)/i;
|
||||
|
@ -26,12 +27,13 @@ FFZ.settings_info.follow_buttons = {
|
|||
no_mobile: true,
|
||||
|
||||
category: "Channel Metadata",
|
||||
name: "Relevant Follow Buttons",
|
||||
help: 'Display additional Follow buttons for channels relevant to the stream, such as people participating in co-operative gameplay.',
|
||||
name: "Featured Channels",
|
||||
help: 'Display additional Follow buttons for channels featured by the stream, such as people participating in co-operative gameplay.',
|
||||
on_update: function(val) {
|
||||
this.rebuild_following_ui();
|
||||
if ( this._cindex )
|
||||
this._cindex.ffzUpdateMetadata('following');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// ---------------
|
||||
|
@ -105,8 +107,8 @@ FFZ.ws_on_close.push(function() {
|
|||
}
|
||||
}
|
||||
|
||||
if ( need_update )
|
||||
this.rebuild_following_ui();
|
||||
if ( need_update && this._cindex )
|
||||
this._cindex.ffzUpdateMetadata('following');
|
||||
});
|
||||
|
||||
|
||||
|
@ -124,8 +126,8 @@ FFZ.ws_commands.follow_buttons = function(data) {
|
|||
need_update = true;
|
||||
}
|
||||
|
||||
if ( need_update )
|
||||
this.rebuild_following_ui();
|
||||
if ( need_update && this._cindex )
|
||||
this._cindex.ffzUpdateMetadata('following');
|
||||
}
|
||||
|
||||
|
||||
|
@ -186,157 +188,99 @@ FFZ.ws_commands.follow_sets = function(data) {
|
|||
// Following UI
|
||||
// ---------------
|
||||
|
||||
FFZ.prototype.rebuild_following_ui = function() {
|
||||
if ( ! this._cindex )
|
||||
return;
|
||||
FFZ.channel_metadata.following = {
|
||||
refresh: false,
|
||||
|
||||
var channel_id = this._cindex.get('channel.id'),
|
||||
hosted_id = this._cindex.get('channel.hostModeTarget.id');
|
||||
setup: function(view, channel) {
|
||||
var channel_id = channel.get('id'),
|
||||
data = this.follow_data && this.follow_data[channel_id];
|
||||
|
||||
if ( channel_id ) {
|
||||
var data = this.follow_data && this.follow_data[channel_id],
|
||||
return [_.unique(data).without("")];
|
||||
},
|
||||
|
||||
el = this._cindex.get('element'),
|
||||
container = el && el.querySelector('.stats-and-actions .channel-actions'),
|
||||
cont = container && container.querySelector('#ffz-ui-following');
|
||||
order: 97,
|
||||
button: true,
|
||||
static_label: constants.HEART,
|
||||
label: function(data) {
|
||||
if ( ! data || ! data.length )
|
||||
return null;
|
||||
|
||||
if ( ! container || ! this.settings.follow_buttons || ! data || ! data.length ) {
|
||||
if ( cont )
|
||||
cont.parentElement.removeChild(cont);
|
||||
return 'Featured';
|
||||
},
|
||||
|
||||
} else {
|
||||
if ( ! cont ) {
|
||||
cont = document.createElement('span');
|
||||
cont.id = 'ffz-ui-following';
|
||||
|
||||
var before = null;
|
||||
try {
|
||||
var before_btn = container.querySelector('.subscribe-button');
|
||||
if ( before_btn )
|
||||
before = before_btn.parentElement.nextSibling;
|
||||
else {
|
||||
before_btn = container.querySelector('.notification-controls');
|
||||
if ( before_btn )
|
||||
before = before_btn.nextSibling;
|
||||
popup: function(container, data) {
|
||||
var user = this.get_user();
|
||||
if ( ! user || ! user.login ) {
|
||||
Ember.$.login({mpSourceAction: "follow-button"});
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch(err) { }
|
||||
container.classList.add('balloon--md');
|
||||
var scroller = utils.createElement('div', 'scroller');
|
||||
container.appendChild(scroller);
|
||||
|
||||
if ( before )
|
||||
container.insertBefore(cont, before);
|
||||
else
|
||||
container.appendChild(cont);
|
||||
} else
|
||||
cont.innerHTML = '';
|
||||
|
||||
var processed = [channel_id];
|
||||
for(var i=0; i < data.length && i < 10; i++) {
|
||||
var cid = data[i];
|
||||
if ( processed.indexOf(cid) !== -1 )
|
||||
continue;
|
||||
this._build_following_button(cont, cid);
|
||||
processed.push(cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ( hosted_id ) {
|
||||
var data = this.follow_data && this.follow_data[hosted_id],
|
||||
|
||||
el = this._cindex.get('element'),
|
||||
container = el && el.querySelector('#hostmode .channel-actions'),
|
||||
cont = container && container.querySelector('#ffz-ui-following');
|
||||
|
||||
if ( ! container || ! this.settings.follow_buttons || ! data || ! data.length ) {
|
||||
if ( cont )
|
||||
cont.parentElement.removeChild(cont);
|
||||
|
||||
} else {
|
||||
if ( ! cont ) {
|
||||
cont = document.createElement('span');
|
||||
cont.id = 'ffz-ui-following';
|
||||
|
||||
var before = null;
|
||||
try {
|
||||
var before_btn = container.querySelector('.subscribe-button');
|
||||
if ( before_btn )
|
||||
before = before_btn.parentElement.nextSibling;
|
||||
else {
|
||||
before_btn = container.querySelector('.notification-controls');
|
||||
if ( before_btn )
|
||||
before = before_btn.nextSibling;
|
||||
}
|
||||
} catch(err) { }
|
||||
|
||||
if ( before )
|
||||
container.insertBefore(cont, before);
|
||||
else
|
||||
container.appendChild(cont);
|
||||
} else
|
||||
cont.innerHTML = '';
|
||||
|
||||
var processed = [hosted_id];
|
||||
for(var i=0; i < data.length && i < 10; i++) {
|
||||
var cid = data[i];
|
||||
if ( processed.indexOf(cid) !== -1 )
|
||||
continue;
|
||||
this._build_following_button(cont, cid);
|
||||
processed.push(cid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------
|
||||
// UI Construction
|
||||
// ---------------
|
||||
|
||||
FFZ.prototype._build_following_button = function(cont, channel_id) {
|
||||
if ( ! VALID_CHANNEL.test(channel_id) )
|
||||
return this.log("Ignoring Invalid Channel: " + utils.sanitize(channel_id));
|
||||
for(var i=0; i < data.length && i < 50; i++)
|
||||
FFZ.channel_metadata.following.draw_row.call(this, scroller, data[i]);
|
||||
},
|
||||
|
||||
draw_row: function(container, user_id) {
|
||||
var f = this,
|
||||
btn = utils.createElement('button', 'follow-button button html-tooltip'),
|
||||
user = this.get_user(),
|
||||
|
||||
noti = utils.createElement('a', 'toggle-notification-menu js-toggle-notification-menu'),
|
||||
noti_c = utils.createElement('div', 'notification-controls v2 hidden', noti),
|
||||
el = utils.createElement('div', 'ffz-following-row'),
|
||||
|
||||
display_name,
|
||||
tooltip,
|
||||
avatar = utils.createElement('img', 'image'),
|
||||
name_el = utils.createElement('a', 'html-tooltip'),
|
||||
|
||||
following = false,
|
||||
notifications = false,
|
||||
btn_follow = utils.createElement('button', 'follow-button button'),
|
||||
sw_notif = utils.createElement('a', 'switch html-tooltip', '<span>'),
|
||||
|
||||
channel = {
|
||||
name: user_id
|
||||
},
|
||||
|
||||
is_following = null,
|
||||
is_notified = false,
|
||||
|
||||
update = function() {
|
||||
btn.classList.toggle('is-following', following);
|
||||
btn.classList.toggle('button--status', following);
|
||||
btn.title = tooltip ? (following ? "Unf" : "F") + "ollow " + tooltip : '';
|
||||
btn.innerHTML = (following ? "" : "Follow ") + display_name;
|
||||
noti_c.classList.toggle('hidden', !following);
|
||||
if ( channel.logo )
|
||||
avatar.src = channel.logo;
|
||||
|
||||
var name = f.format_display_name(channel.display_name || user_id, user_id);
|
||||
name_el.innerHTML = name[0];
|
||||
name_el.setAttribute('original-title', name[1] || '');
|
||||
|
||||
el.setAttribute('data-loaded', is_following !== null);
|
||||
el.setAttribute('data-following', is_following);
|
||||
|
||||
btn_follow.textContent = is_following ? 'Unfollow' : 'Follow';
|
||||
btn_follow.classList.toggle('is-following', is_following);
|
||||
btn_follow.classList.toggle('button--status', is_following);
|
||||
sw_notif.classList.toggle('active', is_notified);
|
||||
sw_notif.setAttribute('original-title', 'Notify me when ' + name[0] + ' goes live.');
|
||||
},
|
||||
|
||||
check_following = function() {
|
||||
var user = f.get_user();
|
||||
if ( ! user || ! user.login ) {
|
||||
following = false;
|
||||
notification = false;
|
||||
btn.classList.add('is-initialized');
|
||||
return update();
|
||||
}
|
||||
|
||||
utils.api.get("users/" + user.login + "/follows/channels/" + channel_id)
|
||||
// Minimize our API calls.
|
||||
utils.api.get("users/:login/follows/channels/" + user_id)
|
||||
.done(function(data) {
|
||||
following = true;
|
||||
notifications = data.notifications;
|
||||
btn.classList.add('is-initialized');
|
||||
is_following = true;
|
||||
is_notified = data.notifications;
|
||||
channel = data.channel;
|
||||
update();
|
||||
}).fail(function(data) {
|
||||
following = false;
|
||||
notifications = false;
|
||||
btn.classList.add('is-initialized');
|
||||
|
||||
}).fail(function() {
|
||||
utils.api.get("channels/" + user_id)
|
||||
.done(function(data) {
|
||||
is_following = false;
|
||||
is_notified = false;
|
||||
channel = data;
|
||||
update();
|
||||
}).fail(function() {
|
||||
el.removeChild(btn_follow);
|
||||
el.removeChild(sw_notif);
|
||||
el.appendChild(utils.createElement('span', 'right', 'Invalid Channel'));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -344,101 +288,43 @@ FFZ.prototype._build_following_button = function(cont, channel_id) {
|
|||
if ( notice !== false )
|
||||
notice = true;
|
||||
|
||||
var user = f.get_user();
|
||||
if ( ! user || ! user.login )
|
||||
return null;
|
||||
|
||||
notifications = notice;
|
||||
return utils.api.put("users/:login/follows/channels/" + channel_id, {notifications: notifications})
|
||||
.fail(check_following);
|
||||
},
|
||||
|
||||
on_name = function(cap_name) {
|
||||
var results = f.format_display_name(cap_name, channel_id, true, true);
|
||||
display_name = results[0];
|
||||
tooltip = results[1];
|
||||
is_following = true;
|
||||
is_notified = notice;
|
||||
update();
|
||||
|
||||
return utils.api.put("users/:login/follows/channels/" + user_id, {notifications: notice})
|
||||
.fail(check_following);
|
||||
};
|
||||
|
||||
// The drop-down button!
|
||||
noti.href = '#';
|
||||
|
||||
// Event Listeners!
|
||||
btn.addEventListener('click', function(e) {
|
||||
var user = f.get_user();
|
||||
if ( ! user || ! user.login )
|
||||
// Show the login dialog~!
|
||||
return Ember.$.login({mpSourceAction: "follow-button", follow: channel_id});
|
||||
|
||||
// Immediate update for nice UI.
|
||||
following = ! following;
|
||||
btn_follow.addEventListener('click', function() {
|
||||
is_following = is_notified = ! is_following;
|
||||
update();
|
||||
|
||||
// Report it!
|
||||
f.ws_send("track_follow", [channel_id, following]);
|
||||
f.ws_send("track_follow", [user_id, is_following]);
|
||||
|
||||
// Do it, and make sure it happened.
|
||||
if ( following )
|
||||
do_follow()
|
||||
if ( is_following )
|
||||
do_follow();
|
||||
else
|
||||
utils.api.del("users/:login/follows/channels/" + channel_id)
|
||||
.done(check_following);
|
||||
|
||||
return false;
|
||||
utils.api.del("users/:login/follows/channels/" + user_id)
|
||||
.fail(check_following);
|
||||
});
|
||||
|
||||
btn.addEventListener('mousedown', function(e) {
|
||||
if ( e.button !== 1 )
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
window.open(Twitch.uri.profile(channel_id));
|
||||
sw_notif.addEventListener('click', function() {
|
||||
do_follow(!is_notified);
|
||||
});
|
||||
|
||||
noti.addEventListener('click', function() {
|
||||
var sw = f._build_following_popup(noti_c, channel_id, notifications);
|
||||
if ( sw )
|
||||
sw.addEventListener('click', function() {
|
||||
var notice = ! notifications;
|
||||
sw.classList.toggle('active', notice);
|
||||
do_follow(notice);
|
||||
return false;
|
||||
});
|
||||
return false;
|
||||
});
|
||||
el.setAttribute('data-user', user_id);
|
||||
|
||||
on_name(FFZ.get_capitalization(channel_id, on_name));
|
||||
name_el.href = 'https://www.twitch.tv/' + user_id;
|
||||
name_el.target = '_blank';
|
||||
|
||||
setTimeout(check_following, Math.random()*5000);
|
||||
el.appendChild(avatar);
|
||||
el.appendChild(name_el);
|
||||
el.appendChild(btn_follow);
|
||||
el.appendChild(sw_notif);
|
||||
container.appendChild(el);
|
||||
|
||||
cont.appendChild(btn);
|
||||
cont.appendChild(noti_c);
|
||||
}
|
||||
|
||||
|
||||
FFZ.prototype._build_following_popup = function(container, channel_id, notifications) {
|
||||
var popup = this.close_popup(), out = '',
|
||||
pos = container.offsetLeft + container.offsetWidth;
|
||||
|
||||
if ( popup && popup.id == "ffz-following-popup" && popup.getAttribute('data-channel') === channel_id )
|
||||
return null;
|
||||
|
||||
popup = this._popup = utils.createElement('div', 'dropmenu notify-menu js-notify');
|
||||
popup.id = 'ffz-following-popup';
|
||||
popup.setAttribute('data-channel', channel_id);
|
||||
|
||||
this._popup_allow_parent = true;
|
||||
this._popup_parent = container;
|
||||
|
||||
var results = this.format_display_name(FFZ.get_capitalization(channel_id), channel_id, true);
|
||||
|
||||
out = '<div class="header">You are following <span' + (results[1] ? ' class="html-tooltip" title="' + utils.quote_attr(results[1]) + '"' : '') + '>' + results[0] + '</span></div>';
|
||||
out += '<p class="clearfix">';
|
||||
out += '<a class="switch' + (notifications ? ' active' : '') + '"><span></span></a>';
|
||||
out += '<span class="switch-label">Notify me when the broadcaster goes live</span>';
|
||||
out += '</p>';
|
||||
|
||||
popup.innerHTML = out;
|
||||
container.insertBefore(popup, container.firstChild);
|
||||
return popup.querySelector('a.switch');
|
||||
check_following();
|
||||
update();
|
||||
}
|
||||
}
|
|
@ -588,7 +588,7 @@ FFZ.mod_card_pages.notes = {
|
|||
this.tokenize_chat_line(message, true, false);
|
||||
|
||||
var can_edit = false, // mod_card.lv_write_notes && user && user.login === message.from,
|
||||
can_delete = false, //mod_card.lv_delete_notes || (user && user.login === message.from),
|
||||
can_delete = mod_card.lv_delete_notes || (user && user.login === message.from),
|
||||
can_mod = can_edit || can_delete;
|
||||
|
||||
var output = this._build_mod_card_history(message, mod_card, true, false, can_mod);
|
||||
|
|
377
src/ui/races.js
377
src/ui/races.js
|
@ -25,9 +25,10 @@ FFZ.settings_info.srl_races = {
|
|||
name: "SRL Race Information",
|
||||
help: 'Display information about <a href="http://www.speedrunslive.com/" target="_new">SpeedRunsLive</a> races under channels.',
|
||||
on_update: function(val) {
|
||||
this.rebuild_race_ui();
|
||||
if ( this._cindex )
|
||||
this._cindex.ffzUpdateMetadata('srl_race');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
// ---------------
|
||||
|
@ -49,8 +50,8 @@ FFZ.ws_on_close.push(function() {
|
|||
need_update = true;
|
||||
}
|
||||
|
||||
if ( need_update )
|
||||
this.rebuild_race_ui();
|
||||
if ( need_update && this._cindex )
|
||||
this._cindex.ffzUpdateMetadata('srl_race');
|
||||
});
|
||||
|
||||
|
||||
|
@ -81,8 +82,8 @@ FFZ.ws_commands.srl_race = function(data) {
|
|||
}
|
||||
}
|
||||
|
||||
if ( need_update )
|
||||
this.rebuild_race_ui();
|
||||
if ( need_update && this._cindex )
|
||||
this._cindex.ffzUpdateMetadata('srl_race');
|
||||
}
|
||||
|
||||
|
||||
|
@ -90,246 +91,53 @@ FFZ.ws_commands.srl_race = function(data) {
|
|||
// Race UI
|
||||
// ---------------
|
||||
|
||||
FFZ.prototype.rebuild_race_ui = function() {
|
||||
if ( ! this._cindex )
|
||||
return;
|
||||
FFZ.channel_metadata.srl_race = {
|
||||
refresh: false,
|
||||
|
||||
var channel_id = this._cindex.get('channel.id'),
|
||||
hosted_id = this._cindex.get('channel.hostModeTarget.id');
|
||||
setup: function(view, channel) {
|
||||
var channel_id = channel.get('id'),
|
||||
race = this.srl_races && this.srl_races[channel_id],
|
||||
entrant_id = race && race.twitch_entrants[channel_id],
|
||||
entrant = entrant_id && race.entrants[entrant_id];
|
||||
|
||||
if ( channel_id ) {
|
||||
var race = this.srl_races && this.srl_races[channel_id],
|
||||
return [channel, channel_id, race, entrant];
|
||||
},
|
||||
|
||||
el = this._cindex.get('element'),
|
||||
container = el && el.querySelector('.stats-and-actions .channel-actions'),
|
||||
race_container = container && container.querySelector('#ffz-ui-race');
|
||||
static_label: '<figure class="icon cn-metabar__icon"><span class="srl-logo"></span></figure>',
|
||||
label: function(channel, channel_id, race, entrant) {
|
||||
if ( ! entrant )
|
||||
return null;
|
||||
|
||||
if ( ! container || ! this.settings.srl_races || ! race ) {
|
||||
if ( race_container )
|
||||
race_container.parentElement.removeChild(race_container);
|
||||
return utils.placement(entrant) || '​';
|
||||
},
|
||||
|
||||
} else {
|
||||
if ( ! race_container ) {
|
||||
race_container = utils.createElement('span', 'balloon-wrapper inline');
|
||||
race_container.id = 'ffz-ui-race';
|
||||
race_container.setAttribute('data-channel', channel_id);
|
||||
tooltip: "SpeedRunsLive Race",
|
||||
|
||||
var btn = document.createElement('span');
|
||||
btn.className = 'button button--text button--dropmenu';
|
||||
btn.title = "SpeedRunsLive Race";
|
||||
btn.innerHTML = '<span class="logo"></span>';
|
||||
|
||||
btn.addEventListener('click', this._build_race_popup.bind(this, race_container, channel_id));
|
||||
|
||||
race_container.appendChild(btn);
|
||||
container.appendChild(race_container);
|
||||
on_popup_close: function(container) {
|
||||
if ( this._race_interval ) {
|
||||
clearInterval(this._race_interval);
|
||||
this._race_interval = null;
|
||||
}
|
||||
},
|
||||
|
||||
this._update_race(race_container, true);
|
||||
}
|
||||
}
|
||||
|
||||
if ( hosted_id ) {
|
||||
var race = this.srl_races && this.srl_races[hosted_id],
|
||||
|
||||
el = this._cindex.get('element'),
|
||||
container = el && el.querySelector('#hostmode .channel-actions'),
|
||||
race_container = container && container.querySelector('#ffz-ui-race');
|
||||
|
||||
if ( ! container || ! this.settings.srl_races || ! race ) {
|
||||
if ( race_container )
|
||||
race_container.parentElement.removeChild(race_container);
|
||||
|
||||
} else {
|
||||
if ( ! race_container ) {
|
||||
race_container = utils.createElement('span', 'balloon-wrapper inline');
|
||||
race_container.id = 'ffz-ui-race';
|
||||
race_container.setAttribute('data-channel', hosted_id);
|
||||
|
||||
var btn = document.createElement('span');
|
||||
btn.className = 'button button--text button--dropmenu';
|
||||
btn.title = "SpeedRunsLive Race";
|
||||
btn.innerHTML = '<span class="logo"></span>';
|
||||
|
||||
btn.addEventListener('click', this._build_race_popup.bind(this, race_container, hosted_id));
|
||||
|
||||
race_container.appendChild(btn);
|
||||
container.appendChild(race_container);
|
||||
}
|
||||
|
||||
this._update_race(race_container, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------------
|
||||
// Race Popup
|
||||
// ---------------
|
||||
|
||||
FFZ.prototype._race_kill = function() {
|
||||
if ( this._race_timer ) {
|
||||
clearTimeout(this._race_timer);
|
||||
delete this._race_timer;
|
||||
}
|
||||
|
||||
delete this._race_game;
|
||||
delete this._race_goal;
|
||||
}
|
||||
|
||||
|
||||
FFZ.prototype._build_race_popup = function(container, channel_id) {
|
||||
var popup = this.close_popup();
|
||||
if ( popup && popup.id === "ffz-race-popup" && popup.getAttribute('data-channel') === channel_id )
|
||||
return;
|
||||
|
||||
if ( ! container )
|
||||
return;
|
||||
|
||||
var el = container.querySelector('.button'),
|
||||
pos = el.offsetLeft + el.offsetWidth,
|
||||
race = this.srl_races[channel_id];
|
||||
|
||||
var popup = utils.createElement('div', 'share balloon balloon--md balloon--up balloon--dropmenu'), out = '';
|
||||
popup.id = 'ffz-race-popup';
|
||||
popup.setAttribute('data-channel', channel_id);
|
||||
//popup.className = (pos >= 300 ? 'right' : 'left') + ' share dropmenu';
|
||||
|
||||
this._popup_kill = this._race_kill.bind(this);
|
||||
this._popup_allow_parent = true;
|
||||
this._popup = popup;
|
||||
|
||||
var link = 'http://kadgar.net/live',
|
||||
has_entrant = false;
|
||||
for(var ent in race.entrants) {
|
||||
var state = race.entrants[ent].state;
|
||||
if ( race.entrants.hasOwnProperty(ent) && race.entrants[ent].channel && (state == "racing" || state == "entered") ) {
|
||||
link += "/" + race.entrants[ent].channel;
|
||||
has_entrant = true;
|
||||
}
|
||||
}
|
||||
|
||||
var height = document.querySelector('.app-main.theatre') ? document.body.clientHeight - 300 : container.parentElement.parentElement.offsetTop - 175,
|
||||
controller = utils.ember_lookup('controller:channel'),
|
||||
display_name = controller && controller.get('content.id') === channel_id ? controller.get('content.display_name') : FFZ.get_capitalization(channel_id),
|
||||
tweet = encodeURIComponent("I'm watching " + display_name + " race " + race.goal + " in " + race.game + " on SpeedRunsLive!");
|
||||
|
||||
out = '<div class="heading"><div></div><span class="html-tooltip"></span></div>';
|
||||
out += '<div class="table" style="max-height:' + height + 'px"><table><thead><tr><th>#</th><th>Entrant</th><th> </th><th>Time</th></tr></thead>';
|
||||
out += '<tbody></tbody></table></div>';
|
||||
|
||||
out += '<iframe class="twitter_share_button" style="width:130px; height:25px" src="https://platform.twitter.com/widgets/tweet_button.html?text=' + tweet + '%20Watch%20at&via=Twitch&url=http://www.twitch.tv/' + channel_id + '"></iframe>';
|
||||
|
||||
out += '<p class="right"><a target="_new" href="http://www.speedrunslive.com/race/?id=' + race.id + '">SRL</a>';
|
||||
|
||||
if ( has_entrant )
|
||||
out += ' <a target="_new" href="' + link + '">Multitwitch</a>';
|
||||
|
||||
out += '</p>';
|
||||
popup.innerHTML = out;
|
||||
container.appendChild(popup);
|
||||
|
||||
this._update_race(container, true);
|
||||
}
|
||||
|
||||
|
||||
FFZ.prototype._update_race = function(container, not_timer) {
|
||||
if ( this._race_timer && not_timer ) {
|
||||
clearTimeout(this._race_timer);
|
||||
delete this._race_timer;
|
||||
}
|
||||
|
||||
if ( ! container )
|
||||
return;
|
||||
|
||||
var channel_id = container.getAttribute('data-channel'),
|
||||
race = this.srl_races[channel_id];
|
||||
|
||||
if ( ! race ) {
|
||||
// No race. Abort.
|
||||
container.parentElement.removeChild(container);
|
||||
if ( this._popup && this._popup.id === 'ffz-race-popup' && this._popup.getAttribute('data-channel') === channel_id )
|
||||
this.close_popup();
|
||||
return;
|
||||
}
|
||||
|
||||
var entrant_id = race.twitch_entrants[channel_id],
|
||||
entrant = race.entrants[entrant_id],
|
||||
|
||||
popup = container.querySelector('#ffz-race-popup'),
|
||||
now = (Date.now() - (this._ws_server_offset || 0)) / 1000,
|
||||
update_popup: function(container, channel, channel_id, race, entrant) {
|
||||
var now = (Date.now() - (this._ws_server_offset || 0)) / 1000,
|
||||
elapsed = Math.floor(now - race.time);
|
||||
|
||||
container.querySelector('.logo').innerHTML = utils.placement(entrant);
|
||||
var tbody = container.querySelector('tbody'),
|
||||
info = container.querySelector('.heading > div'),
|
||||
timer = container.querySelector('.heading > span');
|
||||
|
||||
if ( popup ) {
|
||||
var tbody = popup.querySelector('tbody'),
|
||||
timer = popup.querySelector('.heading > span'),
|
||||
info = popup.querySelector('.heading div');
|
||||
|
||||
// Make sure we don't leave any tooltips lying around when we update.
|
||||
// Of course, we should just rewrite logic to not constantly mutilate
|
||||
// rows.
|
||||
jQuery('.html-tooltip', tbody).trigger('mouseout');
|
||||
|
||||
tbody.innerHTML = '';
|
||||
var entrants = [], done = true;
|
||||
for(var ent in race.entrants) {
|
||||
if ( ! race.entrants.hasOwnProperty(ent) ) continue;
|
||||
if ( race.entrants[ent].state == "racing" )
|
||||
done = false;
|
||||
entrants.push(race.entrants[ent]);
|
||||
}
|
||||
|
||||
entrants.sort(function(a,b) {
|
||||
var a_place = a.place || 9999,
|
||||
b_place = b.place || 9999,
|
||||
|
||||
a_time = a.time || elapsed,
|
||||
b_time = b.time || elapsed;
|
||||
|
||||
if ( a.state == "forfeit" || a.state == "dq" )
|
||||
a_place = 10000;
|
||||
|
||||
if ( b.state == "forfeit" || b.state == "dq" )
|
||||
b_place = 10000;
|
||||
|
||||
if ( a_place < b_place ) return -1;
|
||||
else if ( a_place > b_place ) return 1;
|
||||
|
||||
else if ( a.name < b.name ) return -1;
|
||||
else if ( a.name > b.name ) return 1;
|
||||
|
||||
else if ( a_time < b_time ) return -1;
|
||||
else if ( a_time > b_time ) return 1;
|
||||
});
|
||||
|
||||
for(var i=0; i < entrants.length; i++) {
|
||||
var ent = entrants[i],
|
||||
name = '<a target="_new" href="http://www.speedrunslive.com/profiles/#!/' + utils.sanitize(ent.name) + '">' + ent.display_name + '</a>',
|
||||
twitch_link = ent.channel ? '<a target="_new" class="twitch" href="//www.twitch.tv/' + utils.sanitize(ent.channel) + '"></a>' : '',
|
||||
hitbox_link = ent.hitbox ? '<a target="_new" class="hitbox" href="http://www.hitbox.tv/' + utils.sanitize(ent.hitbox) + '"></a>' : '',
|
||||
time = elapsed ? utils.time_to_string(ent.time||elapsed) : "",
|
||||
place = utils.place_string(ent.place),
|
||||
comment = ent.comment ? utils.quote_san(ent.comment) : "";
|
||||
|
||||
tbody.innerHTML += '<tr' + (comment ? ' title="' + comment + '"' : '') + ' class="' + ent.state + (comment ? ' html-tooltip' : '') + '"><td>' + place + '</td><td>' + name + '</td><td>' + twitch_link + hitbox_link + '</td><td class="time">' + (ent.state == "forfeit" ? "Forfeit" : time) + '</td></tr>';
|
||||
}
|
||||
|
||||
if ( this._race_game != race.game || this._race_goal != race.goal ) {
|
||||
this._race_game = race.game;
|
||||
this._race_goal = race.goal;
|
||||
if ( info.getAttribute('data-game') != race.game || info.getAttribute('data-goal') != race.goal ) {
|
||||
info.setAttribute('data-game', race.game);
|
||||
info.setAttribute('data-goal', race.goal);
|
||||
|
||||
var game = utils.quote_san(race.game),
|
||||
goal = utils.unquote_attr(race.goal),
|
||||
old_goal = popup.getAttribute('data-old-goal');
|
||||
goal = utils.unquote_attr(race.goal);
|
||||
|
||||
if ( goal !== old_goal ) {
|
||||
popup.setAttribute('data-old-goal', goal);
|
||||
goal = goal ? this.render_tokens(this.tokenize_line("jtv", null, goal, true)) : '';
|
||||
info.innerHTML = '<h2 class="html-tooltip" title="' + game + '">' + game + '</h2><span class="goal"><b>Goal: </b>' + goal + '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
if ( race.time != timer.getAttribute('data-time') ) {
|
||||
timer.setAttribute('data-time', race.time);
|
||||
|
@ -337,12 +145,111 @@ FFZ.prototype._update_race = function(container, not_timer) {
|
|||
}
|
||||
|
||||
if ( ! elapsed )
|
||||
timer.innerHTML = "Entry Open";
|
||||
else if ( done )
|
||||
timer.innerHTML = "Done";
|
||||
else {
|
||||
timer.innerHTML = 'Entry Open';
|
||||
else
|
||||
timer.innerHTML = utils.time_to_string(elapsed);
|
||||
this._race_timer = setTimeout(this._update_race.bind(this, container), 1000);
|
||||
|
||||
|
||||
var entrants = [],
|
||||
done = true;
|
||||
|
||||
for(var ent in race.entrants) {
|
||||
var e = race.entrants[ent];
|
||||
if ( e.state === 'racing' )
|
||||
done = false;
|
||||
|
||||
entrants.push(e);
|
||||
}
|
||||
|
||||
entrants.sort(function(a,b) {
|
||||
var a_place = a.place || 9999,
|
||||
b_place = b.place || 9999;
|
||||
|
||||
if ( a.state === 'forfeit' || a.state === 'dq' )
|
||||
a_place = 10000;
|
||||
if ( b.state === 'forfeit' || b.state === 'dq' )
|
||||
b_place = 10000;
|
||||
|
||||
if ( a_place < b_place ) return -1;
|
||||
else if ( a_place > b_place ) return 1;
|
||||
|
||||
else if ( a.name < b.name ) return -1;
|
||||
else if ( a.name > b.name ) return 1;
|
||||
});
|
||||
|
||||
for(var i=0; i < entrants.length; i++) {
|
||||
var ent = entrants[i],
|
||||
line = tbody.children[i],
|
||||
matching = false;
|
||||
if ( line ) {
|
||||
matching = line.getAttribute('data-entrant') === ent.name;
|
||||
if ( ! matching )
|
||||
jQuery('.html-tooltip', line).trigger('mouseout');
|
||||
|
||||
} else {
|
||||
line = utils.createElement('tr', 'html-tooltip');
|
||||
tbody.appendChild(line);
|
||||
}
|
||||
|
||||
var place = utils.place_string(ent.place),
|
||||
comment = ent.comment ? utils.quote_san(ent.comment) : '',
|
||||
time = elapsed ? utils.time_to_string(ent.time || elapsed) : '';
|
||||
|
||||
if ( ! matching ) {
|
||||
var name = '<a target="_blank" href="http://www.speedrunslive.com/profiles/#1/' + utils.quote_san(ent.name) + '">' + this.format_display_name(ent.display_name, ent.name)[0] + '</a>',
|
||||
twitch_link = ent.channel ? '<a target="_blank" class="twitch" href="https://www.twitch.tv/' + utils.quote_san(ent.channel) + '"></a>' : '',
|
||||
hitbox_link = ent.hitbox ? '<a target="_blank" class="hitbox" href="https://www.hitbox.tv/' + uitls.quote_san(ent.hitbox) + '"></a>' : '';
|
||||
|
||||
line.setAttribute('data-entrant', ent.name);
|
||||
line.innerHTML = '<td></td><td>' + name + '</td><td>' + twitch_link + hitbox_link + '</td><td class="time"></td>';
|
||||
}
|
||||
|
||||
line.setAttribute('original-title', comment);
|
||||
line.setAttribute('data-state', ent.state);
|
||||
line.children[0].textContent = place;
|
||||
line.children[3].textContent = ent.state === 'forfeit' ? 'Forfeit' : time;
|
||||
}
|
||||
|
||||
while(tbody.children.length > entrants.length)
|
||||
tbody.removeChild(tbody.children[entrants.length]);
|
||||
},
|
||||
|
||||
popup: function(container, channel, channel_id, race, entrant) {
|
||||
if ( this._race_interval )
|
||||
clearInterval(this._race_interval);
|
||||
|
||||
container.classList.add('balloon--md');
|
||||
|
||||
var link = 'http://kadgar.net/live',
|
||||
has_racing_entrant = false;
|
||||
|
||||
for(var ent in race.entrants) {
|
||||
var state = race.entrants[ent].state,
|
||||
e_channel = race.entrants[ent].channel
|
||||
if ( e_channel && (state === 'racing' || state === 'entered') ) {
|
||||
link += '/' + e_channel;
|
||||
has_racing_entrant = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var display_name = channel.get('displayName'),
|
||||
tweet = encodeURIComponent("I'm watching " + display_name + " race " + race.goal + " in " + race.game + " on SpeedRunsLive! Watch at"),
|
||||
height = Math.max(300, document.querySelector('#player').clientHeight - 100);
|
||||
|
||||
container.innerHTML = '<div class="heading"><div></div><span class="html-tooltip"></span></div>' +
|
||||
'<div class="table" style="max-height:' + height + 'px"><table>' +
|
||||
'<thead><tr><th>#</th><th>Entrant</th><th> </th><th>Time</th></tr></thead>' +
|
||||
'<tbody></tbody></table></div>' +
|
||||
|
||||
'<iframe class="twitter_share_button" style="width:130px; height: 25px" src="https://platform.twitter.com/widgets/tweet_button.html?text=' + utils.quote_attr(tweet) + '&via=Twitch&url=https://www.twitch.tv/' + utils.quote_san(channel_id) + '"></iframe>' +
|
||||
|
||||
'<p class="right"><a target="_blank" href="http://www.speedrunslive.com/race/?id=' + race.id + '">SRL</a>' +
|
||||
(has_racing_entrant ? ' <a target="_blank" href="' + link + '">Multitwitch</a>' : '') +
|
||||
'</p>';
|
||||
|
||||
var func = FFZ.channel_metadata.srl_race.update_popup.bind(this, container, channel, channel_id, race, entrant);
|
||||
|
||||
func();
|
||||
this._race_interval = setInterval(func, 1000);
|
||||
}
|
||||
};
|
|
@ -19,7 +19,7 @@ FFZ.ws_commands.chatters = function(data) {
|
|||
if ( room ) {
|
||||
room.ffz_chatters = count;
|
||||
if ( this._cindex )
|
||||
this._cindex.ffzUpdateChatters();
|
||||
this._cindex.ffzUpdateMetadata('chatters');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ FFZ.ws_commands.viewers = function(data) {
|
|||
if ( room ) {
|
||||
room.ffz_viewers = count;
|
||||
if ( this._cindex )
|
||||
this._cindex.ffzUpdateChatters();
|
||||
this._cindex.ffzUpdateMetadata('chatters');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
164
style.css
164
style.css
|
@ -18,6 +18,9 @@ body > div.tipsy .tipsy-arrow { opacity: 0.8; }
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ffz-following-row[data-loaded="false"] .button,
|
||||
.ffz-following-row[data-loaded="false"] .switch,
|
||||
.ffz-following-row[data-following="false"] .switch,
|
||||
.ffz-moderation-card:not(.lv-notes) ul.menu li[data-page="notes"],
|
||||
.ffz-moderation-card:not(.lv-logs) ul.menu li[data-page="history"],
|
||||
.ffz-moderation-card:not(.lv-logs) ul.menu li[data-page="stats"],
|
||||
|
@ -240,18 +243,6 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
|||
|
||||
#dash_main #stats .stat.dark#ffz_count svg path { fill: #cacaca; }
|
||||
|
||||
#ffz-ui-following .follow-button a {
|
||||
padding: 0 10px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#ffz-following-popup {
|
||||
background-image: url('//cdn.frankerfacez.com/script/zreknarf-bg.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: 115% -75%;
|
||||
background-size: 50%;
|
||||
}
|
||||
|
||||
.ffz-live-team-channel .ffz-game {
|
||||
display: inline-block;
|
||||
max-width: 150px;
|
||||
|
@ -357,76 +348,61 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
|||
|
||||
#ffz-ui-host-button { vertical-align: middle }
|
||||
|
||||
#ffz-following-popup.right {
|
||||
right: 0;
|
||||
left: auto;
|
||||
}
|
||||
|
||||
#ffz-ui-following .notification-controls,
|
||||
#ffz-ui-race {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#ffz-ui-race .button span {
|
||||
.srl-logo {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
background: no-repeat 0 50%;
|
||||
}
|
||||
|
||||
#ffz-ui-race .button span.logo {
|
||||
padding-left: 44px;
|
||||
margin-bottom: -10px;
|
||||
width: 34px;
|
||||
height: 16px;
|
||||
background-image: url("//cdn.frankerfacez.com/script/srl_button.png");
|
||||
}
|
||||
|
||||
.cn-metabar__ffz .srl-logo {
|
||||
vertical-align: middle;
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
#ffz-race-popup {
|
||||
position: absolute;
|
||||
display: block;
|
||||
#ffz-metadata-popup[data-key="srl_race"] {
|
||||
background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png");
|
||||
background-repeat: no-repeat;
|
||||
padding: 1rem;
|
||||
background-position: 115% 110%;
|
||||
}
|
||||
|
||||
#ffz-race-popup.right { right: 10px; }
|
||||
|
||||
#ffz-race-popup .heading {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading {
|
||||
margin: -1rem -1rem 1rem;
|
||||
height: 65px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#ffz-race-popup .heading div {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading div {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
#ffz-race-popup .heading h2,
|
||||
#ffz-race-popup .heading .goal {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading h2,
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading .goal {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#ffz-race-popup .heading .goal {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading .goal {
|
||||
display: block;
|
||||
margin: calc(-1rem + 1px);
|
||||
padding: calc(1rem - 1px);
|
||||
}
|
||||
|
||||
#ffz-race-popup .heading .goal:hover {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading .goal:hover {
|
||||
white-space: normal;
|
||||
background-color: rgba(255,255,255,0.9);
|
||||
border-bottom: 1px solid rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.theatre #ffz-race-popup .heading .goal:hover,
|
||||
.ffz-dark #ffz-race-popup .heading .goal:hover {
|
||||
.theatre #ffz-metadata-popup[data-key="srl_race"] .heading .goal:hover,
|
||||
.ffz-dark #ffz-metadata-popup[data-key="srl_race"] .heading .goal:hover {
|
||||
background-color: rgba(16,16,16,0.9);
|
||||
border-color: rgba(255,255,255,0.2);
|
||||
}
|
||||
|
||||
#ffz-race-popup .heading h2 {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading h2 {
|
||||
max-width: 240px;
|
||||
font-size: 1.5em;
|
||||
padding-bottom: 5px;
|
||||
|
@ -435,7 +411,7 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
|||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#ffz-race-popup .heading > span {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .heading > span {
|
||||
line-height: 30px;
|
||||
position: absolute;
|
||||
top: 7.5px;
|
||||
|
@ -446,32 +422,37 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
#ffz-race-popup .right {
|
||||
#ffz-metadata-popup[data-key="srl_race"] p {
|
||||
float: right;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#ffz-metadata-popup[data-key="srl_race"] .right {
|
||||
padding-top: 0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#ffz-race-popup .table {
|
||||
#ffz-metadata-popup[data-key="srl_race"] .table {
|
||||
overflow-y: auto;
|
||||
border-bottom: 1px solid;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
#ffz-race-popup table {
|
||||
#ffz-metadata-popup[data-key="srl_race"] table {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
#ffz-race-popup table a {
|
||||
#ffz-metadata-popup[data-key="srl_race"] table a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.ffz-about-table a.twitch,
|
||||
.ffz-about-table a.youtube,
|
||||
.ffz-about-table a.twitter,
|
||||
#ffz-race-popup a.twitch,
|
||||
#ffz-race-popup a.hitbox {
|
||||
#ffz-metadata-popup[data-key="srl_race"] a.twitch,
|
||||
#ffz-metadata-popup[data-key="srl_race"] a.hitbox {
|
||||
display: inline-block;
|
||||
height: 16px;
|
||||
margin-left: 5px;
|
||||
|
@ -488,25 +469,25 @@ body.ffz-bttv-dark .ffz-ui-toggle.blue.live:hover svg.svg-emoticons path { fill:
|
|||
background-image: url("//cdn.frankerfacez.com/script/twitter_logo.png");
|
||||
}
|
||||
|
||||
#ffz-race-popup a.twitch,
|
||||
#ffz-metadata-popup[data-key="srl_race"] a.twitch,
|
||||
.ffz-about-table a.twitch {
|
||||
width: 15px;
|
||||
background-image: url("//cdn.frankerfacez.com/script/twitch_logo.png");
|
||||
}
|
||||
|
||||
#ffz-race-popup a.hitbox {
|
||||
#ffz-metadata-popup[data-key="srl_race"] a.hitbox {
|
||||
width: 12px;
|
||||
background-image: url("//cdn.frankerfacez.com/script/hitbox_logo.png");
|
||||
}
|
||||
|
||||
#ffz-race-popup table tbody tr.done:nth-child(0n+1) td { background-color: rgba(255,255,0,.2); }
|
||||
#ffz-race-popup table tbody tr.done:nth-child(0n+2) td { background-color: rgba(128,128,128,.2); }
|
||||
#ffz-race-popup table tbody tr.done:nth-child(0n+3) td { background-color: rgba(210,100,0,.2); }
|
||||
#ffz-race-popup table tbody tr.forfeit td { opacity: 0.5; background-color: rgba(210,100,100,.2); }
|
||||
#ffz-race-popup table tbody tr.racing td.time { opacity: 0.5; }
|
||||
#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="done"]:nth-child(0n+1) td { background-color: rgba(255,255,0,.2); }
|
||||
#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="done"]:nth-child(0n+2) td { background-color: rgba(128,128,128,.2); }
|
||||
#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="done"]:nth-child(0n+3) td { background-color: rgba(210,100,0,.2); }
|
||||
#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="forfeit"] td { opacity: 0.5; background-color: rgba(210,100,100,.2); }
|
||||
#ffz-metadata-popup[data-key="srl_race"] table tbody tr[data-state="racing"] td.time { opacity: 0.5; }
|
||||
|
||||
#ffz-race-popup table th, #ffz-race-popup td { padding: 1px; }
|
||||
#ffz-race-popup table th { border-bottom: 1px solid; }
|
||||
#ffz-metadata-popup[data-key="srl_race"] table th, #ffz-metadata-popup[data-key="srl_race"] td { padding: 1px; }
|
||||
#ffz-metadata-popup[data-key="srl_race"] table th { border-bottom: 1px solid; }
|
||||
|
||||
|
||||
/* Dark Menu */
|
||||
|
@ -1092,6 +1073,7 @@ body.ffz-bttv-dark .ffz-ui-popup .ffz-ui-menu-page { border-bottom: none }
|
|||
padding: 0 0 10px;
|
||||
}
|
||||
|
||||
#ffz-metadata-popup,
|
||||
.ffz-subwindow .card,
|
||||
.ffz-channel-selector {
|
||||
background-image: url("//cdn.frankerfacez.com/script/zreknarf-bg.png");
|
||||
|
@ -1194,6 +1176,7 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
|
|||
|
||||
/* Menu Scrollbar */
|
||||
|
||||
#ffz-metadata-popup .scroller::-webkit-scrollbar,
|
||||
.searchPanel .collectionWrapper::-webkit-scrollbar,
|
||||
.activity-react__all::-webkit-scrollbar,
|
||||
.conversations-list .scroll-container::-webkit-scrollbar,
|
||||
|
@ -1203,7 +1186,7 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
|
|||
.conversations-list .conversations-list-inner::-webkit-scrollbar,
|
||||
.conversation-window .conversation-content::-webkit-scrollbar,
|
||||
.chat-history::-webkit-scrollbar,
|
||||
#ffz-race-popup .table::-webkit-scrollbar,
|
||||
#ffz-metadata-popup[data-key="srl_race"] .table::-webkit-scrollbar,
|
||||
.emoticon-selector-box .all-emotes::-webkit-scrollbar,
|
||||
.ffz-ui-sub-menu-page::-webkit-scrollbar,
|
||||
.ffz-ui-menu-page::-webkit-scrollbar {
|
||||
|
@ -1211,6 +1194,7 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
|
|||
width: 6px;
|
||||
}
|
||||
|
||||
#ffz-metadata-popup .scroller::-webkit-scrollbar-thumb,
|
||||
.searchPanel .collectionWrapper::-webkit-scrollbar-thumb,
|
||||
.activity-react__all::-webkit-scrollbar-thumb,
|
||||
.conversations-list .scroll-container::-webkit-scrollbar-thumb,
|
||||
|
@ -1220,7 +1204,7 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
|
|||
.conversations-list .conversations-list-inner::-webkit-scrollbar-thumb,
|
||||
.conversation-window .conversation-content::-webkit-scrollbar-thumb,
|
||||
.chat-history::-webkit-scrollbar-thumb,
|
||||
#ffz-race-popup .table::-webkit-scrollbar-thumb,
|
||||
#ffz-metadata-popup[data-key="srl_race"] .table::-webkit-scrollbar-thumb,
|
||||
.emoticon-selector-box .all-emotes::-webkit-scrollbar-thumb,
|
||||
.ffz-ui-sub-menu-page::-webkit-scrollbar-thumb,
|
||||
.ffz-ui-menu-page::-webkit-scrollbar-thumb {
|
||||
|
@ -1229,6 +1213,9 @@ body:not(.ffz-bttv) .dropmenu.share { margin-bottom: 0; }
|
|||
box-shadow: 0 0 1px 1px rgba(255,255,255,0.25);
|
||||
}
|
||||
|
||||
.ffz-dark #ffz-metadata-popup .scroller::-webkit-scrollbar-thumb,
|
||||
.theatre #ffz-metadata-popup .scroller::-webkit-scrollbar-thumb,
|
||||
|
||||
.ffz-dark .searchPanel .collectionWrapper::-webkit-scrollbar-thumb,
|
||||
.ffz-dark .activity-react__all::-webkit-scrollbar-thumb,
|
||||
.ffz-dark .conversations-list .scroll-container::-webkit-scrollbar-thumb,
|
||||
|
@ -1419,6 +1406,9 @@ img.channel_background[src="null"] { display: none; }
|
|||
|
||||
.ffz-moderation-card ul.menu span { text-decoration: underline }
|
||||
|
||||
.ffz-dark .ffz-following-row,
|
||||
.theatre .ffz-following-row,
|
||||
|
||||
.ffz-dark .moderation-card .interface,
|
||||
.theatre .moderation-card .interface,
|
||||
.dark .moderation-card .interface,
|
||||
|
@ -3591,10 +3581,10 @@ body:not(.ffz-channel-bar-bottom).ffz-small-player.ffz-minimal-channel-bar #play
|
|||
.ffz-share-box .qa-share-box__button { margin: 0 !important }
|
||||
|
||||
.cn-metabar__more > .cn-metabar__livecount { order: 1 }
|
||||
.cn-metabar__more > #ffz-uptime-display { order: 2 }
|
||||
.cn-metabar__more > #ffz-player-stats { order: 3 }
|
||||
.cn-metabar__more > .cn-metabar__viewcount { order: 50; flex-grow: 0 !important }
|
||||
/*.cn-metabar__more > .cn-metabar__ffz[data-key="uptime"] { order: 2 }
|
||||
.cn-metabar__more > #ffz-chatter-display { order: 51 }
|
||||
.cn-metabar__more > #ffz-player-stats { order: 3 }*/
|
||||
.cn-metabar__more > .cn-metabar__viewcount { order: 50; flex-grow: 0 !important }
|
||||
|
||||
.cn-metabar__more > #ffz-ui-host-button { order: 98 }
|
||||
.cn-metabar__more > .ffz-channel-broadcast-link { order: 100 }
|
||||
|
@ -3620,3 +3610,47 @@ body:not(.ffz-channel-bar-bottom).ffz-small-player.ffz-minimal-channel-bar #play
|
|||
background-image: url("//cdn.frankerfacez.com/script/button_edit.svg");
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Following Menu */
|
||||
|
||||
#ffz-metadata-popup .scroller {
|
||||
max-height: 420px;
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
margin: -1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
/* Remove BTTV's ugly styling */
|
||||
.ffz-following-row .button {
|
||||
color: #fff !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.ffz-following-row {
|
||||
padding: 1rem 0;
|
||||
line-height: 30px;
|
||||
border-bottom: 1px solid rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.ffz-following-row:first-child { padding-top: 0 }
|
||||
.ffz-following-row:last-child { padding-bottom: 0; border-bottom: none }
|
||||
|
||||
.ffz-following-row .image:not([src]) { visibility: hidden }
|
||||
|
||||
.ffz-following-row .image {
|
||||
display: inline-block;
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.ffz-following-row .right,
|
||||
.ffz-following-row .button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.ffz-following-row .switch {
|
||||
float: right;
|
||||
margin: 7px 1rem 0 0;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue