mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-03 00:18:31 +00:00
Hide Vodcasts & Host Button (#353)
* Fix occasional vanishing of stream card uptime, avatar and other info * Additionally fix routing issues with the modified avatar div * Add hide vodcast functionality * Add `vuedraggable` to dependencies * Remove LegacyAPI * Add host button with aut-host management menu Messy code, but it works. * Only add `disabled` class to button if chat connection isn't there * Fix for host button not updating it's text properly after un-/hosting * Add tooltip to host button and use alternative way to get the chat inst. * Rework host button stuff into new metadata All is functional, even though the code might be a mess. * Implement Auto-Host settings tab * Fix reassignment to const, hehe * Custom TMI events for Host and Unhost, plus use existing chat connection * Code adjustments, disabling the button when host is loading, etc. * Address code-review suggestions Translation support and a few other fixes * Remove inline styling * Show error in tooltip if hosting didn't work properly or similar issues * Address change requests * Fix mixup * Fix host options not having a background * Fix styling for the host options This adds a small border * Hide host button on own channel * Fix popper * Move `isChannelHosted` method further up * Adjust handle of auto host menu and fix vodcast hiding * Replace loading icon with text * Add setting for host button; Also another small issue * Fix joining your own channel multiple times
This commit is contained in:
parent
ac35ee5fab
commit
941aab9feb
11 changed files with 767 additions and 367 deletions
|
@ -42,6 +42,7 @@
|
|||
"sortablejs": "^1.6.1",
|
||||
"vue": "^2.5.2",
|
||||
"vue-clickaway": "^2.1.0",
|
||||
"vue-template-compiler": "^2.5.2"
|
||||
"vue-template-compiler": "^2.5.2",
|
||||
"vuedraggable": "^2.15.0"
|
||||
}
|
||||
}
|
||||
|
|
293
src/api.js
293
src/api.js
|
@ -1,293 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// ========================================================================
|
||||
// Legacy API
|
||||
// ========================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {has} from 'utilities/object';
|
||||
import {EventEmitter} from 'utilities/events';
|
||||
|
||||
|
||||
export default class ApiModule extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.inject('chat');
|
||||
this.inject('chat.emotes');
|
||||
|
||||
this._apis = {};
|
||||
|
||||
if ( ! this._known_apis ) {
|
||||
this._known_apis = {};
|
||||
const stored_val = localStorage.getItem(`ffz_known_apis`);
|
||||
if ( stored_val !== null )
|
||||
try {
|
||||
this._known_apis = JSON.parse(stored_val);
|
||||
} catch(err) {
|
||||
this.log.error(`Error loading known APIs`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
create(...args) {
|
||||
return new LegacyAPI(this, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class LegacyAPI extends EventEmitter {
|
||||
constructor(instance, name, icon = null, version = null, name_key = null) {
|
||||
super();
|
||||
|
||||
this.ffz = instance.root;
|
||||
this.parent = instance;
|
||||
|
||||
if ( name ) {
|
||||
for(const id in this.parent._known_apis) {
|
||||
if ( this.parent._known_apis[id] === name ) {
|
||||
this.id = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! this.id ) {
|
||||
let i = 0;
|
||||
while ( ! this.id ) {
|
||||
if ( ! has(this.parent._known_apis, i) ) {
|
||||
this.id = i;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if ( name ) {
|
||||
this.parent._known_apis[this.id] = name;
|
||||
localStorage.ffz_known_apis = JSON.stringify(this.parent._known_apis);
|
||||
}
|
||||
}
|
||||
|
||||
this.parent._apis[this.id] = this;
|
||||
|
||||
this.emote_sets = {};
|
||||
this.global_sets = [];
|
||||
this.default_sets = [];
|
||||
|
||||
this.badges = {};
|
||||
this.users = {};
|
||||
|
||||
this.name = name || `Extension#${this.id}`;
|
||||
this.name_key = name_key || this.name.replace(/[^A-Z0-9_-]/g, '').toLowerCase();
|
||||
|
||||
if ( /^[0-9]/.test(this.name_key) )
|
||||
this.name_key = `_${this.name_key}`;
|
||||
|
||||
this.icon = icon;
|
||||
this.version = version;
|
||||
|
||||
this.parent.log.info(`Registered New Extension #${this.id} (${this.name_key}): ${this.name}`);
|
||||
}
|
||||
|
||||
log(msg, data) {
|
||||
this.parent.log.info(`Ext #${this.id} (${this.name_key}): ${msg}`, data);
|
||||
}
|
||||
|
||||
error(msg, error) {
|
||||
this.parent.log.error(`Ext #${this.id} (${this.name_key}): ${msg}`, error);
|
||||
}
|
||||
|
||||
register_metadata(key, data) { } // eslint-disable-line
|
||||
unregister_metadata(key, data) { } // eslint-disable-line
|
||||
update_metadata(key, full_update) { } // eslint-disable-line
|
||||
|
||||
|
||||
_load_set(real_id, set_id, data) {
|
||||
if ( ! data )
|
||||
return null;
|
||||
|
||||
const emote_set = Object.assign({
|
||||
source: this.name,
|
||||
icon: this.icon || null,
|
||||
title: 'Global Emoticons',
|
||||
_type: 0
|
||||
}, data, {
|
||||
source_ext: this.id,
|
||||
source_id: set_id,
|
||||
id: real_id,
|
||||
count: 0
|
||||
});
|
||||
|
||||
this.emote_sets[set_id] = emote_set;
|
||||
this.parent.emotes.loadSetData(real_id, emote_set);
|
||||
|
||||
return emote_set;
|
||||
}
|
||||
|
||||
|
||||
load_set(set_id, emote_set) {
|
||||
const real_id = `${this.id}-${set_id}`;
|
||||
return this._load_set(real_id, set_id, emote_set);
|
||||
}
|
||||
|
||||
|
||||
unload_set(set_id) {
|
||||
const real_id = `${this.id}-${set_id}`,
|
||||
emote_set = this.emote_sets[set_id];
|
||||
|
||||
if ( ! emote_set )
|
||||
return;
|
||||
|
||||
this.unregister_global_set(set_id);
|
||||
|
||||
// TODO: Unload sets
|
||||
|
||||
return emote_set;
|
||||
}
|
||||
|
||||
|
||||
get_set(set_id) {
|
||||
return this.emote_sets[set_id];
|
||||
}
|
||||
|
||||
|
||||
register_global_set(set_id, emote_set) {
|
||||
const real_id = `${this.id}-${set_id}`;
|
||||
if ( emote_set )
|
||||
emote_set = this.load_set(set_id, emote_set);
|
||||
else
|
||||
emote_set = this.emote_sets[set_id];
|
||||
|
||||
if ( ! emote_set )
|
||||
throw new Error('Invalid set ID.');
|
||||
|
||||
if ( this.parent.emotes.emote_sets && ! this.parent.emotes.emote_sets[real_id] )
|
||||
this.parent.emotes.emote_sets[real_id] = emote_set;
|
||||
|
||||
if ( this.global_sets.indexOf(set_id) === -1 )
|
||||
this.global_sets.push(set_id);
|
||||
|
||||
if ( this.default_sets.indexOf(set_id) === -1 )
|
||||
this.default_sets.push(set_id);
|
||||
|
||||
this.parent.emotes.global_sets.push(`api--${this.id}`, real_id);
|
||||
this.parent.emotes.default_sets.push(`api--${this.id}`, real_id);
|
||||
}
|
||||
|
||||
unregister_global_set(set_id) {
|
||||
const real_id = `${this.id}-${set_id}`,
|
||||
emote_set = this.emote_sets[set_id];
|
||||
|
||||
if ( ! emote_set )
|
||||
return;
|
||||
|
||||
let ind = this.global_sets.indexOf(set_id);
|
||||
if ( ind !== -1 )
|
||||
this.global_sets.splice(ind,1);
|
||||
|
||||
ind = this.default_sets.indexOf(set_id);
|
||||
if ( ind !== -1 )
|
||||
this.default_sets.splice(ind,1);
|
||||
|
||||
this.parent.emote.global_sets.remove(`api--${this.id}`, real_id);
|
||||
this.parent.emote.default_sets.remove(`api--${this.id}`, real_id);
|
||||
}
|
||||
|
||||
|
||||
register_room_set(room_login, set_id, emote_set) {
|
||||
const real_id = `${this.id}-${set_id}`,
|
||||
room = this.parent.chat.getRoom(null, room_login, true);
|
||||
|
||||
if ( ! room )
|
||||
throw new Error('Room not loaded');
|
||||
|
||||
if ( emote_set ) {
|
||||
emote_set.title = emote_set.title || `Channel: ${room.data && room.data.display_name || room_login}`;
|
||||
emote_set._type = emote_set._type || 1;
|
||||
|
||||
emote_set = this.load_set(set_id, emote_set);
|
||||
} else
|
||||
emote_set = this.emote_sets[set_id];
|
||||
|
||||
if ( ! emote_set )
|
||||
throw new Error('Invalid set ID.');
|
||||
|
||||
if ( this.parent.emotes.emote_sets && ! this.parent.emotes.emote_sets[real_id] )
|
||||
this.parent.emotes.emote_sets[real_id] = emote_set;
|
||||
|
||||
room.emote_sets.push(`api--${this.id}`, real_id);
|
||||
emote_set.users++;
|
||||
}
|
||||
|
||||
unregister_room_set(room_login, set_id) {
|
||||
const real_id = `${this.id}-${set_id}`,
|
||||
emote_set = this.emote_sets[set_id],
|
||||
room = this.parent.chat.getRoom(null, room_login, true);
|
||||
|
||||
if ( ! emote_set || ! room )
|
||||
return;
|
||||
|
||||
room.emote_sets.remove(`api--${this.id}`, real_id);
|
||||
emote_set.users--;
|
||||
}
|
||||
|
||||
|
||||
add_badge() { } // eslint-disable-line
|
||||
remove_badge() { } // eslint-disable-line
|
||||
user_add_badge() { } // eslint-disable-line
|
||||
user_remove_badge() { } // eslint-disable-line
|
||||
room_add_user_badge() { } // eslint-disable-line
|
||||
room_remove_user_badge() { } // eslint-disable-line
|
||||
|
||||
user_add_set(username, set_id) { // eslint-disable-line
|
||||
|
||||
}
|
||||
|
||||
user_remove_set(username, set_id) { // eslint-disable-line
|
||||
|
||||
}
|
||||
|
||||
|
||||
retokenize_messages() { } // eslint-disable-line
|
||||
|
||||
|
||||
register_chat_filter(filter) {
|
||||
this.on('room-message', filter);
|
||||
}
|
||||
|
||||
unregister_chat_filter(filter) {
|
||||
this.off('room-message', filter);
|
||||
}
|
||||
|
||||
|
||||
iterate_chat_views(func) { } // eslint-disable-line
|
||||
iterate_rooms(func) {
|
||||
if ( func === undefined )
|
||||
func = this.emit.bind(this, 'room-add');
|
||||
|
||||
const chat = this.parent.resolve('chat');
|
||||
for(const room_id in chat.rooms)
|
||||
if ( has(chat.rooms, room_id) )
|
||||
func(room_id);
|
||||
}
|
||||
|
||||
register_on_room_callback(callback, dont_iterate) {
|
||||
const thing = room_id => callback(room_id, this.register_room_set.bind(this, room_id));
|
||||
|
||||
thing.original_func = callback;
|
||||
callback.__wrapped = thing;
|
||||
|
||||
this.on('room-add', thing);
|
||||
if ( ! dont_iterate )
|
||||
this.iterate_rooms(thing);
|
||||
}
|
||||
|
||||
unregister_on_room_callback(callback) {
|
||||
if ( ! callback.__wrapped )
|
||||
return;
|
||||
|
||||
this.off('room-add', callback.__wrapped);
|
||||
callback.__wrapped = null;
|
||||
}
|
||||
|
||||
}
|
13
src/main.js
13
src/main.js
|
@ -9,7 +9,6 @@ import SettingsManager from './settings/index';
|
|||
import {TranslationManager} from './i18n';
|
||||
import SocketClient from './socket';
|
||||
import Site from 'site';
|
||||
import LegacyAPI from './api';
|
||||
import Vue from 'utilities/vue';
|
||||
|
||||
class FrankerFaceZ extends Module {
|
||||
|
@ -39,8 +38,6 @@ class FrankerFaceZ extends Module {
|
|||
this.inject('socket', SocketClient);
|
||||
this.inject('site', Site);
|
||||
|
||||
this.inject('_api', LegacyAPI);
|
||||
|
||||
this.register('vue', Vue);
|
||||
|
||||
|
||||
|
@ -121,13 +118,3 @@ FrankerFaceZ.utilities = {
|
|||
|
||||
window.FrankerFaceZ = FrankerFaceZ;
|
||||
window.ffz = new FrankerFaceZ();
|
||||
|
||||
// Make FFZ:AP Run
|
||||
FrankerFaceZ.chat_commands = {};
|
||||
FrankerFaceZ.settings_info = {};
|
||||
FrankerFaceZ.utils = {
|
||||
process_int: a => a
|
||||
}
|
||||
window.App = true;
|
||||
if ( window.jQuery )
|
||||
window.jQuery.noty = {themes: {}};
|
|
@ -13,15 +13,6 @@ import SettingsContext from './context';
|
|||
import MigrationManager from './migration';
|
||||
|
||||
|
||||
const OVERRIDE_GET = {
|
||||
ffz_enable_highlight_sound: false,
|
||||
ffz_highlight_sound_volume: 0,
|
||||
bttv_channel_emotes: true,
|
||||
bttv_global_emotes: true,
|
||||
bttv_gif_emotes: 1
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// SettingsManager
|
||||
// ============================================================================
|
||||
|
@ -331,12 +322,7 @@ export default class SettingsManager extends Module {
|
|||
// ========================================================================
|
||||
|
||||
context(env) { return this.main_context.context(env) }
|
||||
get(key) {
|
||||
if ( has(OVERRIDE_GET, key) )
|
||||
return OVERRIDE_GET[key];
|
||||
|
||||
return this.main_context.get(key);
|
||||
}
|
||||
get(key) { return this.main_context.get(key); }
|
||||
uses(key) { return this.main_context.uses(key) }
|
||||
update(key) { return this.main_context.update(key) }
|
||||
|
||||
|
|
|
@ -428,6 +428,18 @@ export default class ChatHook extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
const old_host = this.onHostingEvent;
|
||||
this.onHostingEvent = function (e, _t) {
|
||||
t.emit('tmi:host', e, _t);
|
||||
return old_host.call(i, e, _t);
|
||||
}
|
||||
|
||||
const old_unhost = this.onUnhostEvent;
|
||||
this.onUnhostEvent = function (e, _t) {
|
||||
t.emit('tmi:unhost', e, _t);
|
||||
return old_unhost.call(i, e, _t);
|
||||
}
|
||||
|
||||
this.postMessage = function(e) {
|
||||
const original = this._wrapped;
|
||||
if ( original ) {
|
||||
|
|
|
@ -112,6 +112,20 @@ export default class Following extends SiteModule {
|
|||
}
|
||||
}`);
|
||||
|
||||
this.apollo.registerModifier('FollowedChannels', `query {
|
||||
currentUser {
|
||||
followedLiveUsers {
|
||||
nodes {
|
||||
profileImageURL(width: 70)
|
||||
stream {
|
||||
type
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`);
|
||||
|
||||
this.ChannelCard = this.fine.define(
|
||||
'following-channel-card',
|
||||
n => n.renderGameBoxArt && n.renderContentType
|
||||
|
@ -125,7 +139,9 @@ export default class Following extends SiteModule {
|
|||
this.on('settings:changed:directory.uptime', () => this.ChannelCard.forceUpdate());
|
||||
this.on('settings:changed:directory.show-channel-avatars', () => this.ChannelCard.forceUpdate());
|
||||
this.on('settings:changed:directory.show-boxart', () => this.ChannelCard.forceUpdate());
|
||||
this.on('settings:changed:directory.hide-vodcasts', () => this.ChannelCard.forceUpdate());
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -170,13 +186,10 @@ export default class Following extends SiteModule {
|
|||
const s = node.hosting.stream.viewersCount = new Number(node.hosting.stream.viewersCount || 0);
|
||||
s.profileImageURL = node.hosting.profileImageURL;
|
||||
s.createdAt = node.hosting.stream.createdAt;
|
||||
s.hostData = {
|
||||
channel: node.hosting.login,
|
||||
displayName: node.hosting.displayName
|
||||
};
|
||||
|
||||
if (!this.hosts[node.hosting.displayName]) {
|
||||
this.hosts[node.hosting.displayName] = {
|
||||
channel: node.hosting.login,
|
||||
nodes: [node],
|
||||
channels: [node.displayName]
|
||||
};
|
||||
|
@ -194,36 +207,48 @@ export default class Following extends SiteModule {
|
|||
return res;
|
||||
}
|
||||
|
||||
ensureQueries () {
|
||||
if (this.router && this.router.match) {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowedChannels',
|
||||
'data.currentUser.followedLiveUsers.nodes.0.profileImageURL'
|
||||
);
|
||||
|
||||
if (this.router.match[1] === 'following') {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowedIndex_CurrentUser',
|
||||
n =>
|
||||
get('data.currentUser.followedLiveUsers.nodes.0.profileImageURL', n) !== undefined
|
||||
||
|
||||
get('data.currentUser.followedHosts.nodes.0.hosting.profileImageURL', n) !== undefined
|
||||
);
|
||||
} else if (this.router.match[1] === 'live') {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowingLive_CurrentUser',
|
||||
'data.currentUser.followedLiveUsers.nodes.0.profileImageURL'
|
||||
);
|
||||
} else if (this.router.match[1] === 'hosts') {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowingHosts_CurrentUser',
|
||||
'data.currentUser.followedHosts.nodes.0.hosting.profileImageURL'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.ChannelCard.ready((cls, instances) => {
|
||||
if (this.router && this.router.match) {
|
||||
if (this.router.match[1] === 'following') {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowedIndex_CurrentUser',
|
||||
n =>
|
||||
get('data.currentUser.followedLiveUsers.nodes.0.profileImageURL', n) !== undefined
|
||||
||
|
||||
get('data.currentUser.followedHosts.nodes.0.hosting.profileImageURL', n) !== undefined
|
||||
);
|
||||
} else if (this.router.match[1] === 'live') {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowingLive_CurrentUser',
|
||||
'data.currentUser.followedLiveUsers.nodes.0.profileImageURL'
|
||||
);
|
||||
} else if (this.router.match[1] === 'hosts') {
|
||||
this.apollo.ensureQuery(
|
||||
'FollowingHosts_CurrentUser',
|
||||
'data.currentUser.followedHosts.nodes.0.hosting.profileImageURL'
|
||||
);
|
||||
}
|
||||
}
|
||||
this.ensureQueries();
|
||||
|
||||
for(const inst of instances) this.updateChannelCard(inst);
|
||||
});
|
||||
|
||||
this.ChannelCard.on('update', inst => this.updateChannelCard(inst), this);
|
||||
this.ChannelCard.on('mount', inst => this.updateChannelCard(inst), this);
|
||||
this.ChannelCard.on('unmount', inst => this.parent.clearUptime(inst), this);
|
||||
this.ChannelCard.on('update', inst => {
|
||||
this.ensureQueries();
|
||||
this.updateChannelCard(inst)
|
||||
}, this);
|
||||
this.ChannelCard.on('mount', this.updateChannelCard, this);
|
||||
this.ChannelCard.on('unmount', this.parent.clearUptime, this);
|
||||
|
||||
document.body.addEventListener('click', this.destroyHostMenu.bind(this));
|
||||
}
|
||||
|
@ -246,6 +271,7 @@ export default class Following extends SiteModule {
|
|||
|
||||
this.hostMenu && this.hostMenu.remove();
|
||||
|
||||
const hostData = this.hosts[inst.props.channelName];
|
||||
const simplebarContentChildren = [];
|
||||
|
||||
// Hosted Channel Header
|
||||
|
@ -260,11 +286,11 @@ export default class Following extends SiteModule {
|
|||
simplebarContentChildren.push(
|
||||
e('a', {
|
||||
className: 'tw-interactable',
|
||||
href: `/${inst.props.viewerCount.hostData.channel}`,
|
||||
href: `/${hostData.channel}`,
|
||||
onclick: event =>
|
||||
this.parent.hijackUserClick(
|
||||
event,
|
||||
inst.props.viewerCount.hostData.channel,
|
||||
hostData.channel,
|
||||
this.destroyHostMenu.bind(this)
|
||||
)
|
||||
}, e('div', 'tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1 tw-mg-y-05',
|
||||
|
@ -292,9 +318,8 @@ export default class Following extends SiteModule {
|
|||
);
|
||||
|
||||
// Hosting Channels Content
|
||||
const hosts = this.hosts[inst.props.channelName];
|
||||
for (let i = 0; i < hosts.nodes.length; i++) {
|
||||
const node = hosts.nodes[i];
|
||||
for (let i = 0; i < hostData.nodes.length; i++) {
|
||||
const node = hostData.nodes[i];
|
||||
simplebarContentChildren.push(
|
||||
e('a', {
|
||||
className: 'tw-interactable',
|
||||
|
@ -346,8 +371,6 @@ export default class Following extends SiteModule {
|
|||
}
|
||||
|
||||
updateChannelCard(inst) {
|
||||
//if (!this.isRouteAcceptable()) return;
|
||||
|
||||
this.parent.updateUptime(inst, 'props.viewerCount.createdAt', '.tw-card .tw-aspect > div');
|
||||
|
||||
const container = this.fine.getHostNode(inst),
|
||||
|
@ -356,6 +379,9 @@ export default class Following extends SiteModule {
|
|||
if ( container === null || card === null )
|
||||
return;
|
||||
|
||||
if (inst.props.streamType === 'watch_party')
|
||||
container.parentElement.classList.toggle('tw-hide', this.settings.get('directory.hide-vodcasts'));
|
||||
|
||||
// Remove old elements
|
||||
const hiddenBodyCard = card.querySelector('.tw-card-body.tw-hide');
|
||||
if (hiddenBodyCard !== null) hiddenBodyCard.classList.remove('tw-hide');
|
||||
|
@ -367,11 +393,11 @@ export default class Following extends SiteModule {
|
|||
if (channelAvatar !== null) channelAvatar.remove();
|
||||
|
||||
if (inst.props.viewerCount.profileImageURL) {
|
||||
const hosting = inst.props.channelNameLinkTo.state.content === 'live_host' && inst.props.viewerCount.hostData;
|
||||
const hosting = inst.props.channelNameLinkTo.state.content === 'live_host' && this.hosts[inst.props.channelName];
|
||||
let channel, displayName;
|
||||
if (hosting) {
|
||||
channel = inst.props.viewerCount.hostData.channel;
|
||||
displayName = inst.props.viewerCount.hostData.displayName;
|
||||
channel = this.hosts[inst.props.channelName].channel;
|
||||
displayName = inst.props.channelName;
|
||||
}
|
||||
|
||||
const avatarSetting = this.settings.get('directory.show-channel-avatars');
|
||||
|
@ -380,11 +406,26 @@ export default class Following extends SiteModule {
|
|||
innerHTML: cardDiv.innerHTML
|
||||
});
|
||||
|
||||
const broadcasterLogin = inst.props.linkTo.pathname.substring(1);
|
||||
modifiedDiv.querySelector('.live-channel-card__channel').onclick = event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.router.navigate('user', { userName: broadcasterLogin });
|
||||
};
|
||||
modifiedDiv.querySelector('.live-channel-card__videos').onclick = event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
this.router.navigate('user-videos', { userName: broadcasterLogin });
|
||||
};
|
||||
|
||||
let avatarDiv;
|
||||
if (avatarSetting === 1) {
|
||||
avatarDiv = e('a', {
|
||||
className: 'ffz-channel-avatar tw-mg-r-05 tw-mg-t-05',
|
||||
href: hosting ? `/${channel}` : inst.props.linkTo.pathname,
|
||||
onclick: event => this.parent.hijackUserClick(event, broadcasterLogin)
|
||||
}, e('img', {
|
||||
title: inst.props.channelName,
|
||||
src: inst.props.viewerCount.profileImageURL
|
||||
|
@ -393,7 +434,7 @@ export default class Following extends SiteModule {
|
|||
const avatarElement = e('a', {
|
||||
className: 'ffz-channel-avatar',
|
||||
href: hosting ? `/${channel}` : inst.props.linkTo.pathname,
|
||||
onclick: event => this.parent.hijackUserClick(event, inst.props.streamNode.broadcaster.login)
|
||||
onclick: event => this.parent.hijackUserClick(event, broadcasterLogin)
|
||||
}, e('div', 'live-channel-card__boxart tw-bottom-0 tw-absolute',
|
||||
e('figure', 'tw-aspect tw-aspect--align-top',
|
||||
e('img', {
|
||||
|
|
|
@ -106,6 +106,20 @@ export default class Directory extends SiteModule {
|
|||
this.ChannelCard.forceUpdate();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.settings.add('directory.hide-vodcasts', {
|
||||
default: false,
|
||||
|
||||
ui: {
|
||||
path: 'Directory > Channels >> Appearance',
|
||||
title: 'Hide Vodcasts',
|
||||
description: 'Hide vodcasts in the directories.',
|
||||
component: 'setting-check-box'
|
||||
},
|
||||
|
||||
changed: () => this.ChannelCard.forceUpdate()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
@ -146,6 +160,10 @@ export default class Directory extends SiteModule {
|
|||
const hiddenPreview = 'https://static-cdn.jtvnw.net/ttv-static/404_preview-320x180.jpg';
|
||||
|
||||
const container = this.fine.getHostNode(inst);
|
||||
|
||||
if (inst.props.streamNode.type === 'watch_party')
|
||||
container.classList.toggle('tw-hide', this.settings.get('directory.hide-vodcasts'));
|
||||
|
||||
const img = container && container.querySelector && container.querySelector(`${uptimeSel} img`);
|
||||
if (img === null) return;
|
||||
|
||||
|
|
171
src/sites/twitch-twilight/modules/host-options.vue
Normal file
171
src/sites/twitch-twilight/modules/host-options.vue
Normal file
|
@ -0,0 +1,171 @@
|
|||
<template lang="html">
|
||||
<div class="ffz-auto-host-options tw-c-background">
|
||||
<header class="tw-full-width tw-align-items-center tw-flex tw-flex-nowrap">
|
||||
<h4>{{ t('metadata.host.title', 'Auto Host Management') }}</h4>
|
||||
</header>
|
||||
<div class="tab tw-overflow-hidden"
|
||||
v-show="activeTab === 'auto-host'"
|
||||
:class="{ active: activeTab === 'auto-host'}">
|
||||
<section class="tw-border-t tw-full-width tw-full-height">
|
||||
<main class="tw-flex-grow-1 scrollable-area" data-simplebar="init">
|
||||
<div class="simplebar-scroll-content">
|
||||
<draggable v-model="hosts" class="simplebar-content" :options="{
|
||||
draggable: '.ffz--host-user',
|
||||
animation: 150,
|
||||
}" @update="rearrangeHosts">
|
||||
<div v-for="host in hosts" class="tw-border-t ffz--host-user" :key="host._id" :data-id="host._id">
|
||||
<div class="tw-interactable">
|
||||
<div class="tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1">
|
||||
<figure class="ffz-i-ellipsis-vert handle"></figure>
|
||||
<div class="ffz-channel-avatar">
|
||||
<img :src="host.logo" :alt="host.display_name + '(' + host.name + ')'">
|
||||
</div>
|
||||
<p class="tw-ellipsis tw-flex-grow-1 tw-mg-l-1 tw-font-size-5">{{ host.name }}</p>
|
||||
<div class="tw-flex-grow-1 tw-pd-x-2"></div>
|
||||
<button class="tw-button-icon tw-mg-x-05 ffz--host-remove-user" @click="removeFromHosts">
|
||||
<figure class="ffz-i-trash"></figure>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</draggable>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
<header class="tw-border-t tw-full-width tw-align-items-center tw-flex tw-flex-noxwrap tw-pd-1">
|
||||
<div class="tw-flex-grow-1 tw-pd-x-2"></div>
|
||||
<button class="tw-button tw-button--hollow tw-mg-x-05" :class="{'tw-button--disabled': addedToHosts}" @click="addToAutoHosts">
|
||||
<span class="tw-button__text">{{ t('metadata.host.add-channel', 'Add To Auto Host') }}</span>
|
||||
</button>
|
||||
</header>
|
||||
</div>
|
||||
<div class="tab tw-overflow-hidden"
|
||||
v-show="activeTab === 'settings'"
|
||||
:class="{ active: activeTab === 'settings'}">
|
||||
<section class="tw-border-t tw-full-width tw-full-height">
|
||||
<main class="tw-flex-grow-1 scrollable-area" data-simplebar="init">
|
||||
<div class="simplebar-scroll-content">
|
||||
<div class="simplebar-content">
|
||||
<div class="tw-pd-1">
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<input type="checkbox" class="tw-checkbox__input"
|
||||
id="autoHostSettings:enabled"
|
||||
data-setting="enabled"
|
||||
:checked="autoHostSettings.enabled"
|
||||
@change="updateCheckbox">
|
||||
<label for="autoHostSettings:enabled" class="tw-checkbox__label">
|
||||
{{ t('metadata.host.setting.auto-hosting.title', 'Auto Hosting') }}
|
||||
</label>
|
||||
</div>
|
||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||
{{ t('metadata.host.setting.auto-hosting.description', 'Toggle all forms of auto hosting: teammates, host list, and similar channels.') }}<br>
|
||||
<a href="https://blog.twitch.tv/grow-your-community-with-auto-hosting-e80c1460f6e1" target="_blank" rel="noopener">{{ t('metadata.host.setting.auto-hosting.link', 'Learn More') }}</a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<input type="checkbox" class="tw-checkbox__input"
|
||||
id="autoHostSettings:team_host"
|
||||
data-setting="team_host"
|
||||
:checked="autoHostSettings.team_host"
|
||||
@change="updateCheckbox">
|
||||
<label for="autoHostSettings:team_host" class="tw-checkbox__label">
|
||||
{{ t('metadata.host.setting.team-hosting.title', 'Team Hosting') }}
|
||||
</label>
|
||||
</div>
|
||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||
{{ t('metadata.host.setting.team-hosting.description',
|
||||
'Automatically host random channels from your team when you\'re not live. ' +
|
||||
'Team channels will be hosted before any channels in your host list.') }}
|
||||
</section>
|
||||
</div>
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<input type="checkbox" class="tw-checkbox__input"
|
||||
id="autoHostSettings:vodcast_hosting"
|
||||
data-setting="deprioritize_vodcast"
|
||||
:checked="!autoHostSettings.deprioritize_vodcast"
|
||||
@change="updateCheckbox">
|
||||
<label for="autoHostSettings:vodcast_hosting" class="tw-checkbox__label">
|
||||
{{ t('metadata.host.setting.vodcast-hosting.title', 'Vodcast Hosting') }}
|
||||
</label>
|
||||
</div>
|
||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||
{{ t('metadata.host.setting.vodcast-hosting.description', 'Include Vodcasts in auto host.') }}
|
||||
<a href="https://blog.twitch.tv/vodcast-brings-the-twitch-community-experience-to-uploads-54098498715" target="_blank" rel="noopener">{{ t('metadata.host.setting.vodcast-hosting.link', 'Learn about Vodcasts') }}</a>
|
||||
</section>
|
||||
</div>
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<input type="checkbox" class="tw-checkbox__input"
|
||||
id="autoHostSettings:recommended_host"
|
||||
data-setting="recommended_host"
|
||||
:checked="autoHostSettings.recommended_host"
|
||||
@change="updateCheckbox">
|
||||
<label for="autoHostSettings:recommended_host" class="tw-checkbox__label">
|
||||
{{ t('metadata.host.setting.recommended-hosting.title', 'Auto Host Channels Similar To Yours') }}
|
||||
</label>
|
||||
</div>
|
||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||
{{ t('metadata.host.setting.recommended-hosting.description', 'Streamers on your primary team & host list will always be hosted first') }}
|
||||
</section>
|
||||
</div>
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<input type="checkbox" class="tw-checkbox__input"
|
||||
id="autoHostSettings:strategy"
|
||||
data-setting="strategy"
|
||||
:checked="autoHostSettings.strategy === 'random'"
|
||||
@change="updateCheckbox">
|
||||
<label for="autoHostSettings:strategy" class="tw-checkbox__label">
|
||||
{{ t('metadata.host.setting.strategy.title', 'Randomize Host Order') }}
|
||||
</label>
|
||||
</div>
|
||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||
{{ t('metadata.host.setting.strategy.description',
|
||||
'If enabled, auto-hosts will be picked at random. ' +
|
||||
'Otherwise they\'re picked in order.') }}
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</section>
|
||||
</div>
|
||||
<footer>
|
||||
<div class="host-options__tabs-container tw-border-t">
|
||||
<div id="host-options__auto-host" class="host-options__tab tw-pd-x-1"
|
||||
@click="setActiveTab('auto-host')"
|
||||
:class="{active: activeTab === 'auto-host'}">
|
||||
<span>{{ t('metadata.host.tab.auto-host', 'Auto Host') }}</span>
|
||||
</div>
|
||||
<div id="host-options__settings" class="host-options__tab tw-pd-x-1"
|
||||
@click="setActiveTab('settings')"
|
||||
:class="{active: activeTab === 'settings'}">
|
||||
<span>{{ t('metadata.host.tab.settings', 'Settings') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import draggable from 'vuedraggable';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
draggable
|
||||
},
|
||||
|
||||
data() {
|
||||
return this.$vnode.data;
|
||||
},
|
||||
|
||||
updated() {
|
||||
this.updatePopper();
|
||||
}
|
||||
}
|
||||
</script>
|
422
src/sites/twitch-twilight/modules/host_button.js
Normal file
422
src/sites/twitch-twilight/modules/host_button.js
Normal file
|
@ -0,0 +1,422 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Host Button
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {createElement as e} from 'utilities/dom';
|
||||
|
||||
const HOST_ERRORS = {
|
||||
COMMAND_EXECUTION: {
|
||||
key: 'command-execution',
|
||||
text: 'There was an error executing the host command. Please try again later.',
|
||||
},
|
||||
CHAT_CONNECTION: {
|
||||
key: 'chat-connection',
|
||||
text: 'There was an issue connecting to chat. Please try again later.',
|
||||
}
|
||||
};
|
||||
|
||||
export default class HostButton extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.should_enable = true;
|
||||
|
||||
this.inject('site');
|
||||
this.inject('site.fine');
|
||||
this.inject('site.chat');
|
||||
this.inject('i18n');
|
||||
this.inject('metadata');
|
||||
this.inject('settings');
|
||||
|
||||
this.settings.add('metadata.host-button', {
|
||||
default: true,
|
||||
|
||||
ui: {
|
||||
path: 'Channel > Metadata >> Player',
|
||||
title: 'Host Button',
|
||||
description: 'Show a host button with the current hosted channel in the tooltip.',
|
||||
component: 'setting-check-box'
|
||||
},
|
||||
|
||||
changed: () => {
|
||||
const ffz_user = this.site.getUser(),
|
||||
userLogin = ffz_user && ffz_user.login;
|
||||
|
||||
if (userLogin)
|
||||
this.joinChannel(userLogin);
|
||||
|
||||
this.metadata.updateMetadata('host');
|
||||
}
|
||||
});
|
||||
|
||||
this.metadata.definitions.host = {
|
||||
order: 150,
|
||||
button: true,
|
||||
|
||||
disabled: () => {
|
||||
return this._host_updating || this._host_error;
|
||||
},
|
||||
|
||||
click: data => {
|
||||
if (data.channel) this.sendHostUnhostCommand(data.channel.login);
|
||||
},
|
||||
|
||||
popup: async (data, tip) => {
|
||||
const vue = this.resolve('vue'),
|
||||
_host_options_vue = import(/* webpackChunkName: "host-options" */ './host-options.vue'),
|
||||
_autoHosts = this.fetchAutoHosts(),
|
||||
_autoHostSettings = this.fetchAutoHostSettings();
|
||||
|
||||
const [, host_options_vue, autoHosts, autoHostSettings] = await Promise.all([vue.enable(), _host_options_vue, _autoHosts, _autoHostSettings]);
|
||||
|
||||
this._auto_host_tip = tip;
|
||||
tip.element.classList.remove('tw-pd-1');
|
||||
tip.element.classList.add('tw-balloon--lg');
|
||||
vue.component('host-options', host_options_vue.default);
|
||||
return this.buildAutoHostMenu(vue, autoHosts, autoHostSettings, data.channel);
|
||||
},
|
||||
|
||||
label: data => {
|
||||
if (!this.settings.get('metadata.host-button')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const ffz_user = this.site.getUser(),
|
||||
userLogin = ffz_user && ffz_user.login;
|
||||
|
||||
if (data.channel && data.channel.login === userLogin) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (this._host_updating) {
|
||||
return 'Updating...';
|
||||
}
|
||||
|
||||
return (this._last_hosted_channel && this.isChannelHosted(data.channel && data.channel.login))
|
||||
? this.i18n.t('metadata.host.button.unhost', 'Unhost')
|
||||
: this.i18n.t('metadata.host.button.host', 'Host');
|
||||
},
|
||||
|
||||
tooltip: () => {
|
||||
if (this._host_error) {
|
||||
return this.i18n.t(
|
||||
`metadata.host.button.tooltip.error.${this._host_error.key}`,
|
||||
this._host_error.text);
|
||||
} else {
|
||||
return this.i18n.t('metadata.host.button.tooltip',
|
||||
'Currently hosting: %{channel}',
|
||||
{
|
||||
channel: this._last_hosted_channel || this.i18n.t('metadata.host.button.tooltip.none', 'None')
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
isChannelHosted(channelLogin) {
|
||||
return this._last_hosted_channel === channelLogin;
|
||||
}
|
||||
|
||||
sendHostUnhostCommand(channel) {
|
||||
if (!this._chat_con) {
|
||||
this._host_error = HOST_ERRORS.CHAT_CONNECTION;
|
||||
this._host_updating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const ffz_user = this.site.getUser(),
|
||||
userLogin = ffz_user && ffz_user.login;
|
||||
|
||||
const commandData = {channel: userLogin, username: channel};
|
||||
|
||||
this._host_updating = true;
|
||||
this.metadata.updateMetadata('host');
|
||||
|
||||
this._host_feedback = setTimeout(() => {
|
||||
if (this._last_hosted_channel === null) {
|
||||
this._host_error = HOST_ERRORS.COMMAND_EXECUTION;
|
||||
this._host_updating = false;
|
||||
this.metadata.updateMetadata('host');
|
||||
}
|
||||
}, 3000);
|
||||
|
||||
if (this.isChannelHosted(channel)) {
|
||||
this._chat_con.commands.unhost.execute(commandData);
|
||||
} else {
|
||||
this._chat_con.commands.host.execute(commandData);
|
||||
}
|
||||
}
|
||||
|
||||
joinChannel(channel) {
|
||||
if (this._chat_con) {
|
||||
if (this.settings.get('metadata.host-button') && !this._chat_con.session.channelstate[`#${channel}`]) {
|
||||
this._chat_con.joinChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hookIntoChatConnection(inst) {
|
||||
const userLogin = inst.props.userLogin;
|
||||
|
||||
if (this._chat_con) {
|
||||
this.joinChannel(userLogin);
|
||||
return;
|
||||
}
|
||||
|
||||
this.on('tmi:host', e => {
|
||||
if (e.channel.substring(1) !== userLogin) return;
|
||||
|
||||
clearTimeout(this._host_feedback);
|
||||
this._host_error = false;
|
||||
this._last_hosted_channel = e.target;
|
||||
|
||||
this._host_updating = false;
|
||||
this.metadata.updateMetadata('host');
|
||||
});
|
||||
|
||||
this.on('tmi:unhost', e => {
|
||||
if (e.channel.substring(1) !== userLogin) return;
|
||||
|
||||
clearTimeout(this._host_feedback);
|
||||
this._host_error = false;
|
||||
this._last_hosted_channel = null;
|
||||
|
||||
this._host_updating = false;
|
||||
this.metadata.updateMetadata('host');
|
||||
});
|
||||
|
||||
const chatServiceClient = inst.chatService.client;
|
||||
|
||||
this._chat_con = chatServiceClient;
|
||||
if (this.settings.get('metadata.host-button'))
|
||||
this.joinChannel(userLogin);
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.metadata.updateMetadata('host');
|
||||
|
||||
this.chat.ChatController.ready((cls, instances) => {
|
||||
for(const inst of instances) {
|
||||
if (inst && inst.chatService) this.hookIntoChatConnection(inst);
|
||||
}
|
||||
});
|
||||
|
||||
this.chat.ChatController.on('mount', this.hookIntoChatConnection, this);
|
||||
}
|
||||
|
||||
buildAutoHostMenu(vue, hosts, autoHostSettings, data) {
|
||||
this._current_channel_id = data.id;
|
||||
this.activeTab = this.activeTab || 'auto-host';
|
||||
|
||||
this.vueEl = new vue.Vue({
|
||||
el: e('div'),
|
||||
render: h => h('host-options', {
|
||||
hosts,
|
||||
autoHostSettings,
|
||||
activeTab: this.activeTab,
|
||||
|
||||
addedToHosts: this.currentRoomInHosts(),
|
||||
addToAutoHosts: () => this.addCurrentRoomToHosts(),
|
||||
rearrangeHosts: event => this.rearrangeHosts(event.oldIndex, event.newIndex),
|
||||
removeFromHosts: event => this.removeUserFromHosts(event),
|
||||
setActiveTab: tab => {
|
||||
this.vueEl.$children[0]._data.activeTab = this.activeTab = tab;
|
||||
},
|
||||
updatePopper: () => {
|
||||
if (this._auto_host_tip) this._auto_host_tip.update();
|
||||
},
|
||||
updateCheckbox: e => {
|
||||
const t = e.target,
|
||||
setting = t.dataset.setting;
|
||||
let state = t.checked;
|
||||
|
||||
if ( setting === 'strategy' )
|
||||
state = state ? 'random' : 'ordered';
|
||||
else if ( setting === 'deprioritize_vodcast' )
|
||||
state = ! state;
|
||||
|
||||
this.updateAutoHostSetting(setting, state);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return this.vueEl.$el;
|
||||
}
|
||||
|
||||
async fetchAutoHosts() {
|
||||
const user = this.site.getUser();
|
||||
if ( ! user )
|
||||
return;
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await fetch('https://api.twitch.tv/kraken/autohost/list', {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.twitchtv.v4+json',
|
||||
'Authorization': `OAuth ${user.authToken}`
|
||||
}
|
||||
}).then(r => {
|
||||
if ( r.ok )
|
||||
return r.json();
|
||||
|
||||
throw r.status;
|
||||
});
|
||||
|
||||
} catch(err) {
|
||||
this.log.error('Error loading auto host list.', err);
|
||||
return;
|
||||
}
|
||||
|
||||
return this.autoHosts = data.targets;
|
||||
}
|
||||
|
||||
async fetchAutoHostSettings() {
|
||||
const user = this.site.getUser();
|
||||
if ( ! user )
|
||||
return;
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = await fetch('https://api.twitch.tv/kraken/autohost/settings', {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.twitchtv.v4+json',
|
||||
'Authorization': `OAuth ${user.authToken}`
|
||||
}
|
||||
}).then(r => {
|
||||
if ( r.ok )
|
||||
return r.json();
|
||||
|
||||
throw r.status;
|
||||
});
|
||||
|
||||
} catch(err) {
|
||||
this.log.error('Error loading auto host settings.', err);
|
||||
return;
|
||||
}
|
||||
|
||||
return this.autoHostSettings = data.settings;
|
||||
}
|
||||
|
||||
queueHostUpdate() {
|
||||
if (this._host_update_timer) clearTimeout(this._host_update_timer);
|
||||
|
||||
this._host_update_timer = setTimeout(() => {
|
||||
this._host_update_timer = undefined;
|
||||
this.updateAutoHosts(this.autoHosts);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
rearrangeHosts(oldIndex, newIndex) {
|
||||
const host = this.autoHosts.splice(oldIndex, 1)[0];
|
||||
this.autoHosts.splice(newIndex, 0, host);
|
||||
|
||||
this.queueHostUpdate();
|
||||
}
|
||||
|
||||
currentRoomInHosts() {
|
||||
return this.getAutoHostIDs(this.autoHosts).includes(parseInt(this._current_channel_id, 10));
|
||||
}
|
||||
|
||||
addCurrentRoomToHosts() {
|
||||
const newHosts = this.autoHosts.slice(0);
|
||||
newHosts.push({ _id: parseInt(this._current_channel_id, 10)});
|
||||
|
||||
this.updateAutoHosts(newHosts);
|
||||
}
|
||||
|
||||
removeUserFromHosts(event) {
|
||||
const id = event.target.closest('.ffz--host-user').dataset.id;
|
||||
|
||||
const newHosts = [];
|
||||
for (let i = 0; i < this.autoHosts.length; i++) {
|
||||
if (this.autoHosts[i]._id != id) newHosts.push(this.autoHosts[i]);
|
||||
}
|
||||
|
||||
this.updateAutoHosts(newHosts);
|
||||
}
|
||||
|
||||
getAutoHostIDs(hosts) { // eslint-disable-line class-methods-use-this
|
||||
const ids = [];
|
||||
if (hosts) {
|
||||
for (let i = 0; i < hosts.length; i++) {
|
||||
ids.push(hosts[i]._id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
async updateAutoHosts(newHosts) {
|
||||
const user = this.site.getUser();
|
||||
if ( ! user )
|
||||
return;
|
||||
|
||||
let data;
|
||||
try {
|
||||
const form = new URLSearchParams();
|
||||
const autoHosts = this.getAutoHostIDs(newHosts);
|
||||
form.append('targets', autoHosts.join(','));
|
||||
|
||||
data = await fetch('https://api.twitch.tv/kraken/autohost/list', {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.twitchtv.v4+json',
|
||||
'Authorization': `OAuth ${user.authToken}`
|
||||
},
|
||||
method: autoHosts.length ? 'PUT' : 'DELETE',
|
||||
body: autoHosts.length ? form : undefined
|
||||
}).then(r => {
|
||||
if ( r.ok )
|
||||
return r.json();
|
||||
|
||||
throw r.status;
|
||||
});
|
||||
|
||||
} catch(err) {
|
||||
this.log.error('Error updating auto host list.', err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.autoHosts = data.targets;
|
||||
if (this.vueEl) {
|
||||
this.vueEl.$children[0]._data.hosts = this.autoHosts;
|
||||
this.vueEl.$children[0]._data.addedToHosts = this.currentRoomInHosts();
|
||||
}
|
||||
}
|
||||
|
||||
async updateAutoHostSetting(setting, newValue) {
|
||||
const user = this.site.getUser();
|
||||
if ( ! user )
|
||||
return;
|
||||
|
||||
let data;
|
||||
try {
|
||||
const form = new URLSearchParams();
|
||||
form.append(setting, newValue);
|
||||
|
||||
data = await fetch('https://api.twitch.tv/kraken/autohost/settings', {
|
||||
headers: {
|
||||
'Accept': 'application/vnd.twitchtv.v4+json',
|
||||
'Authorization': `OAuth ${user.authToken}`
|
||||
},
|
||||
method: 'PUT',
|
||||
body: form
|
||||
}).then(r => {
|
||||
if ( r.ok )
|
||||
return r.json();
|
||||
|
||||
throw r.status;
|
||||
});
|
||||
|
||||
} catch(err) {
|
||||
this.log.error('Error updating auto host setting.', err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.autoHostSettings = data.settings;
|
||||
if (this.vueEl) {
|
||||
this.vueEl.$children[0]._data.autoHostSettings = this.autoHostSettings;
|
||||
}
|
||||
}
|
||||
}
|
53
src/sites/twitch-twilight/styles/host_options.scss
Normal file
53
src/sites/twitch-twilight/styles/host_options.scss
Normal file
|
@ -0,0 +1,53 @@
|
|||
.ffz-auto-host-options {
|
||||
.scrollable-area {
|
||||
max-height: 25vh;
|
||||
}
|
||||
|
||||
.handle {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
.sortable-ghost {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.ffz--host-user {
|
||||
.handle {
|
||||
padding: 0 0.4rem 0 0;
|
||||
}
|
||||
|
||||
.ffz--host-remove-user {
|
||||
> figure {
|
||||
padding: 0.4rem 0.2rem;
|
||||
}
|
||||
|
||||
&:hover { background: #a94444 !important }
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
padding: .9rem 1rem .9rem 2rem;
|
||||
}
|
||||
|
||||
.ffz-checkbox-description {
|
||||
padding-left: 2.2rem;
|
||||
}
|
||||
|
||||
.host-options__tabs-container {
|
||||
height: 3rem;
|
||||
|
||||
> .host-options__tab {
|
||||
position: relative;
|
||||
top: -.1rem;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
line-height: 3rem;
|
||||
margin-right: .5rem;
|
||||
|
||||
&:hover, &.active {
|
||||
border-top: 1px solid #6441a4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,3 +9,5 @@
|
|||
@import 'directory';
|
||||
|
||||
@import 'fixes';
|
||||
|
||||
@import 'host_options';
|
Loading…
Add table
Add a link
Reference in a new issue