1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-09-15 17:46:55 +00:00
* Added: New appearance type for chat actions that includes both an icon and text.
* Changed: The emote menu now displays channel avatars rather than sub badges.
* Changed: Emotes unlocked via points or other methods will appear as unlocked on the Channel page of the emote menu, even if you aren't subscribed.
* Changed: Display the number of months someone has been subscribed on the Founder badge. (Closes #694)
* Removed: Setting to not automatically redirect to Squad Stream pages, as Twitch is no longer doing that.
* Fixed: The emote menu now correctly groups emotes with no specific source channel.
* Fixed: The emote menu should no longer display sets as `Set #Number`.
* Fixed: The emote data module constantly trying to load data for a set that does not exist rather than storing that it does not exist.
* Fixed: Cache emote set data from the emote menu for use with tool-tips.
* Fixed: Clicking buttons in the FFZ Control Center's header now prevents dragging from starting.
* Fixed: Remove several Fine instances that were not resolving to anything useful.
* Fixed: Alignment of in-line mod actions.
* Behind the Scenes: Started fresh work on custom viewer cards.
This commit is contained in:
SirStendec 2019-10-28 01:06:02 -04:00
parent 5a235f9847
commit 8cfbc95821
39 changed files with 610 additions and 303 deletions

View file

@ -0,0 +1,42 @@
<template lang="html">
<div>
<div class="tw-flex tw-align-items-start">
<label class="tw-mg-y-05">
{{ t('setting.actions.icon', 'Icon') }}
</label>
<icon-picker
:value="value.icon"
class="tw-full-width"
@input="change"
/>
</div>
<div class="tw-flex tw-align-items-center">
<label for="edit_text">
{{ t('setting.actions.text', 'Label') }}
</label>
<input
id="edit_text"
v-model="value.text"
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
@input="$emit('input', value)"
>
</div>
</div>
</template>
<script>
export default {
props: ['value'],
methods: {
change(val) {
this.value.icon = val;
this.$emit('input', this.value);
}
}
}
</script>

View file

@ -0,0 +1,3 @@
<template functional>
<span :style="{color:props.color}" :class="props.data.icon">{{ props.data.text }}</span>
</template>

View file

@ -1,5 +1,3 @@
<template functional>
<span :style="{color: props.color}">
{{ props.data.text }}
</span>
<span :style="{color: props.color}">{{ props.data.text }}</span>
</template>

View file

@ -177,7 +177,7 @@ export default class Actions extends Module {
default: [
{v: {action: 'friend'}},
{v: {action: 'whisper', appearance: {type: 'text', text: 'Whisper', button: true}}},
{v: {action: 'whisper', appearance: {type: 'combo', icon: '', text: 'Whisper', button: true}}},
{v: {type: 'space'}},
{v: {action: 'card_menu'}},
{v: {type: 'new-line'}},

View file

@ -48,6 +48,34 @@ export const icon = {
}
// ============================================================================
// Text + Icon
// ============================================================================
export const combo = {
title: 'Text and Icon',
title_i18n: 'setting.actions.appearance.combo',
colored: true,
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-combo.vue'),
load(data) {
if ( data.icon && data.icon.startsWith('ffz-fa') )
loadFontAwesome();
return true;
},
component: () => import(/* webpackChunkName: 'main-menu' */ './components/preview-combo.vue'),
render(data, createElement, color) {
return (<span style={{color}} class={data.icon}>
{data.text}
</span>)
}
}
// ============================================================================
// Image
// ============================================================================

View file

@ -288,6 +288,11 @@ export default class Badges extends Module {
title,
count: d.data
});
} else if ( d.badge === 'founder' ) {
title = this.i18n.t('badges.founder.months', '{title}\n(Subscribed for {count,number} Month{count,en_plural})', {
title,
count: d.data
});
}
}
@ -371,7 +376,7 @@ export default class Badges extends Module {
else
slot = last_slot++;
const data = dynamic_data[badge_id],
const data = dynamic_data[badge_id] || (badge_id === 'founder' && dynamic_data['subscriber']),
urls = badge_id === 'moderator' && custom_mod && room && room.data && room.data.mod_urls,
badges = [];

View file

@ -13,7 +13,7 @@ import GET_EMOTE from './emote_info.gql';
const MOD_KEY = IS_OSX ? 'metaKey' : 'ctrlKey';
const EXTRA_INVENTORY = [33563];
//const EXTRA_INVENTORY = [33563];
const MODIFIERS = {
59847: {
@ -64,7 +64,7 @@ export default class Emotes extends Module {
this.inject('settings');
this.inject('experiments');
this.twitch_inventory_sets = new Set(EXTRA_INVENTORY);
this.twitch_inventory_sets = new Set; //(EXTRA_INVENTORY);
this.__twitch_emote_to_set = new Map;
this.__twitch_set_to_channel = new Map;
@ -127,7 +127,7 @@ export default class Emotes extends Module {
onEnable() {
// Just in case there's a weird load order going on.
this.on('site:enabled', this.loadTwitchInventory);
// this.on('site:enabled', this.loadTwitchInventory);
this.style = new ManagedStyle('emotes');
@ -146,7 +146,7 @@ export default class Emotes extends Module {
this.socket.on(':command:follow_sets', this.updateFollowSets, this);
this.loadGlobalSets();
this.loadTwitchInventory();
//this.loadTwitchInventory();
}
@ -788,6 +788,21 @@ export default class Emotes extends Module {
}
setTwitchEmoteSet(emote_id, set_id) {
if ( isNaN(emote_id) || ! isFinite(emote_id) )
return;
this.__twitch_emote_to_set.set(emote_id, set_id);
}
setTwitchSetChannel(set_id, channel) {
if ( isNaN(set_id) || ! isFinite(set_id) )
return;
this.__twitch_set_to_channel.set(set_id, channel);
}
getTwitchEmoteSet(emote_id, callback) {
const tes = this.__twitch_emote_to_set;
@ -869,6 +884,9 @@ export default class Emotes extends Module {
return data;
} catch(err) {
if ( err === 'No known Twitch emote set with that ID.' )
return null;
tes.delete(set_id);
}
}
@ -896,6 +914,15 @@ export default class Emotes extends Module {
if ( callback )
callback(data);
}).catch(() => tes.delete(set_id));
}).catch(err => {
if ( err === 'No known Twitch emote set with that ID.' ) {
if ( callback )
callback(null);
return;
}
tes.delete(set_id)
});
}
}

View file

@ -59,7 +59,7 @@ export default class Logviewer extends Module {
if ( ! card.channel || ! card.user )
return;
card.lv_topic = `logs-${card.channel.login}-${card.channel.user}`;
card.lv_topic = `logs-${card.channel.login}-${card.user.login}`;
this.lv_socket.subscribe(card, card.lv_topic);
}

View file

@ -219,7 +219,10 @@ export default {
this.displace = displace(this.$el, {
handle: this.$el.querySelector('header'),
highlightInputs: true,
constrain: true
constrain: true,
ignoreFn(event) {
return event.target.closest('button') != null
}
});
})
},

View file

@ -1,96 +1,115 @@
<template>
<div
:style="{zIndex: z}"
class="ffz-mod-card tw-elevation-3 tw-c-background-alt tw-c-text-base tw-border tw-flex tw-flex-nowrap tw-flex-column"
class="ffz-viewer-card tw-border-radius-medium tw-c-background-base tw-c-text-base tw-elevation-2 tw-flex tw-flex-column viewer-card"
tabindex="0"
@focusin="onFocus"
@keyup.esc="close"
>
<header
<div
:style="loaded && `background-image: url('${user.bannerImageURL}');`"
class="tw-full-width tw-align-items-center tw-flex tw-flex-nowrap tw-relative"
class="ffz-viewer-card__header tw-c-background-accent-alt tw-flex-grow-0 tw-flex-shrink-0 viewer-card__background tw-relative"
>
<div class="tw-full-width tw-align-items-center tw-flex tw-flex-nowrap tw-pd-1 ffz--background-dimmer">
<figure class="tw-avatar tw-avatar--size-50">
<div v-if="loaded" class="tw-overflow-hidden ">
<img
:src="user.profileImageURL"
class="tw-image"
>
<div class="tw-flex tw-flex-column tw-full-height tw-full-width viewer-card__overlay">
<div class="tw-align-center tw-align-items-start tw-c-background-overlay tw-c-text-overlay tw-flex tw-flex-grow-1 tw-flex-row tw-full-width tw-justify-content-start tw-pd-05 tw-relative viewer-card__banner">
<div class="tw-mg-l-05 tw-mg-y-05 tw-inline-flex viewer-card-drag-cancel">
<figure class="tw-avatar tw-avatar--size-50">
<img
v-if="loaded"
:src="user.profileImageURL"
:alt="displayName"
class="tw-block tw-border-radius-rounded tw-image tw-image-avatar"
>
</figure>
</div>
</figure>
<div class="tw-ellipsis tw-inline-block">
<div class="tw-align-items-center tw-mg-l-1 ffz--info-lines">
<h4>
<a :href="`/${login}`" class="tw-link tw-link--hover-underline-none tw-link--inherit" target="_blank">
{{ displayName }}
</a>
</h4>
<h5
v-if="displayName && displayName.toLowerCase() !== login"
>
<a :href="`/${login}`" class="tw-link tw-link--hover-underline-none tw-link--inherit" target="_blank">
{{ login }}
</a>
</h5>
<div class="tw-align-left tw-flex-grow-1 tw-ellipsis tw-mg-x-1 tw-mg-y-05 viewer-card__display-name">
<div class="tw-inline-flex">
<h4 class="viewer-card-drag-cancel">
<a
:href="`/${login}`"
target="_blank"
rel="noopener noreferrer"
class="tw-interactive tw-link tw-link--hover-color-inherit tw-link--inherit"
>
{{ displayName }}
</a>
</h4>
</div>
<div v-if="loaded" class="tw-pd-t-05">
<span
<!--span
:data-title="t('viewer-card.views', 'Views')"
class="ffz-tooltip tw-mg-r-05 ffz-i-views"
class="ffz-tooltip tw-mg-r-05 ffz-i-views viewer-card-drag-cancel"
>
{{ t(null, '{views,number}', {views: user.profileViewCount}) }}
</span>
<span
{{ t('viewer-card.views.number', '{views,number}', {views: user.profileViewCount}) }}
</span-->
<!--span
:data-title="t('viewer-card.followers', 'Followers')"
class="ffz-tooltip tw-mg-r-05 ffz-i-heart"
class="ffz-tooltip tw-mg-r-05 ffz-i-heart viewer-card-drag-cancel"
>
{{ t(null, '{followers,number}', {followers: user.followers.totalCount}) }}
</span>
{{ t('viewer-card.followers.number', '{followers,number}', {followers: user.followers.totalCount}) }}
</span-->
<span
v-if="userAge"
:data-title="t('viewer-card.age-tip', 'Member Since: {age,datetime}', {age: userAge})"
class="ffz-tooltip ffz-i-clock"
:data-title="t('viewer-card.age-tip', 'Account Created at: {created,datetime}', {created: userAge})"
class="ffz-tooltip ffz-i-cake viewer-card-drag-cancel"
>
{{ t('viewer-card.age', '{age,humantime}', {age: userAge}) }}
{{ t('viewer-card.age', '{created,humantime}', {created: userAge}) }}
</span>
<span
v-if="followAge"
:data-title="t('viewer-card.follow-tip', 'Followed at: {followed,datetime}', {followed: followAge})"
class="ffz-tooltip ffz-i-heart viewer-card-drag-cancel"
>
{{ t('viewer-card.follow', '{followed,humantime}', {followed: followAge}) }}
</span>
<span
v-if="subscription"
:data-title="t('viewer-card.months-tip', 'Subscribed for {months,number} month{months,en_plural}', {months: subscription.months})"
class="ffz-tooltip ffz-i-star viewer-card-drag-cancel"
>
{{ t('viewer-card.months', '{months,number} month{months,en_plural}', {months: subscription.months}) }}
</span>
</div>
</div>
<div class="tw-flex tw-flex-column">
<button
:data-title="t('viewer-card.close', 'Close')"
:aria-label="t('viewer-card.close', 'Close')"
class="viewer-card-drag-cancel tw-align-items-center tw-align-middle tw-border-radius-medium tw-button-icon tw-button-icon--overlay tw-core-button tw-core-button--overlay tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip"
@click="close"
>
<span class="tw-button-icon__icon">
<figure class="ffz-i-cancel" />
</span>
</button>
<button
v-if="! pinned"
:data-title="t('viewer-card.pin', 'Pin')"
:aria-label="t('viewer-card.pin', 'Pin')"
class="viewer-card-drag-cancel tw-align-items-center tw-align-middle tw-border-radius-medium tw-button-icon tw-button-icon--overlay tw-core-button tw-core-button--overlay tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative ffz-tooltip"
@click="pin"
>
<span class="tw-button-icon__icon">
<figure class="ffz-i-pin" />
</span>
</button>
</div>
</div>
<div class="tw-flex-grow-1 tw-pd-x-2" />
<button
:data-title="t('viewer-card.close', 'Close')"
class="ffz-tooltip tw-button-icon tw-absolute tw-right-0 tw-top-0 tw-mg-t-05 tw-mg-r-05"
@click="close"
>
<span class="tw-button-icon__icon">
<figure class="ffz-i-cancel" />
</span>
</button>
<button
v-show="! pinned"
:data-title="t('viewer-card.pin', 'Pin')"
class="ffz-tooltip tw-button-icon tw-absolute tw-right-0 tw-bottom-0 tw-mg-b-05 tw-mg-r-05"
@click="pin"
>
<span class="tw-button-icon__icon">
<figure class="ffz-i-pin" />
</span>
</button>
</div>
</header>
</div>
<error-tab v-if="errored" />
<template v-else-if="loaded">
<section class="tw-c-background-base">
<div class="mod-cards__tabs-container tw-border-t">
<div class="viewer-card__tabs-container tw-border-t">
<div
v-for="(data, key) in tabs"
v-for="(d, key) in tabs"
:id="`viewer-card__${key}`"
:key="key"
:id="`mod-cards__${key}`"
:class="{active: active_tab === key}"
class="mod-cards__tab tw-pd-x-1"
class="viewer-card__tab tw-pd-x-1"
@click="active_tab = key"
>
<span>{{ data.label }}</span>
<span>{{ d.label }}</span>
</div>
</div>
</section>
@ -115,6 +134,8 @@
import LoadingTab from './components/loading-tab.vue';
import ErrorTab from './components/error-tab.vue';
import {deep_copy} from 'utilities/object';
import displace from 'displacejs';
export default {
@ -123,7 +144,7 @@ export default {
'loading-tab': LoadingTab
},
props: ['tabs', 'room', 'raw_user', 'pos_x', 'pos_y', 'data', 'getZ', 'getFFZ'],
props: ['tabs', 'room', 'raw_user', 'pos_x', 'pos_y', 'data', 'ban_info', 'getZ', 'getFFZ'],
data() {
return {
@ -133,10 +154,12 @@ export default {
loaded: false,
errored: false,
pinned: false,
twitch_banned: false,
user: null,
channel: null,
self: null
self: null,
ban: null
}
},
@ -155,6 +178,13 @@ export default {
return this.raw_user.displayName || this.raw_user.login;
},
subscription() {
if ( this.loaded )
return this.user.relationship?.cumulativeTenure;
return null;
},
userAge() {
if ( this.loaded )
return new Date(this.user.createdAt);
@ -162,6 +192,11 @@ export default {
return null
},
followAge() {
const age = this.loaded && this.user?.relationship?.followedAt;
return age ? new Date(age) : null;
},
current_tab() {
return this.tabs[this.active_tab];
}
@ -171,9 +206,10 @@ export default {
this.$emit('emit', ':open', this);
this.data.then(data => {
this.user = data.data.targetUser;
this.channel = data.data.channelUser;
this.self = data.data.currentUser;
this.twitch_banned = data?.data?.activeTargetUser?.id !== data?.data?.targetUser?.id;
this.user = deep_copy(data?.data?.targetUser);
this.channel = deep_copy(data?.data?.channelUser);
this.self = deep_copy(data?.data?.currentUser);
this.loaded = true;
this.$emit('emit', ':load', this);
@ -182,6 +218,14 @@ export default {
console.error(err); // eslint-disable-line no-console
this.errored = true;
});
this.ban_info.then(data => {
this.ban = deep_copy(data?.data?.chatRoomBanStatus);
}).catch(err => {
console.error(err);
this.ban = null;
});
},
mounted() {
@ -236,6 +280,14 @@ export default {
this.pinned = true;
this.$emit('pin');
this.$emit('emit', ':pin', this);
this.cleanTips();
},
cleanTips() {
this.$nextTick(() => {
this.getFFZ().emit('tooltips:cleanup');
});
},
close() {
@ -245,9 +297,10 @@ export default {
createDrag() {
this.$nextTick(() => {
this.displace = displace(this.$el, {
handle: this.$el.querySelector('header'),
handle: this.$el.querySelector('.ffz-viewer-card__header'),
highlightInputs: true,
constrain: true
constrain: true,
ignoreFn: e => e.target.closest('.viewer-card-drag-cancel') != null
});
})
},

View file

@ -0,0 +1,12 @@
query FFZ_BanStatus($channelID: ID!, $targetUserID: ID!) {
chatRoomBanStatus(channelID: $channelID, userID: $targetUserID) {
createdAt
expiresAt
isPermanent
moderator {
id
login
displayName
}
}
}

View file

@ -0,0 +1,79 @@
query FFZ_ViewerCard($channelID: ID!, $targetID: ID, $targetLogin: String) {
activeTargetUser: user(id: $targetID, login: $targetLogin) {
id
}
targetUser: user(id: $targetID, login: $targetLogin, lookupType: ALL) {
id
login
displayName
bannerImageURL
profileImageURL(width: 70)
createdAt
profileViewCount
followers {
totalCount
}
...friendButtonFragment
relationship(targetUserID: $channelID) {
cumulativeTenure: subscriptionTenure(tenureMethod: CUMULATIVE) {
daysRemaining
months
}
followedAt
subscriptionBenefit {
id
tier
purchasedWithPrime
gift {
isGift
}
}
}
}
channelUser: user(id: $channelID) {
id
login
displayName
subscriptionProducts {
id
name
price
url
emoteSetID
emotes {
id
}
tier
}
modLogsRoleAccess(role: MODERATOR) {
accessLevel
}
self {
banStatus {
isPermanent
}
isModerator
}
}
currentUser {
id
login
roles {
isSiteAdmin
isStaff
isGlobalMod
}
blockedUsers {
id
}
}
}
fragment friendButtonFragment on User {
id
self {
friendship {
__typename
}
}
}

View file

@ -1,50 +0,0 @@
query FFZ_ViewerCard($targetLogin: String!, $channelID: ID!) {
targetUser: user(login: $targetLogin) {
id
login
displayName
bannerImageURL
profileImageURL(width: 70)
createdAt
profileViewCount
followers {
totalCount
}
...friendButtonFragment
}
channelUser: user(id: $channelID) {
id
login
displayName
subscriptionProducts {
id
price
url
emoteSetID
emotes {
id
}
}
self {
isModerator
}
}
currentUser {
id
login
roles {
isSiteAdmin
isStaff
isGlobalMod
}
}
}
fragment friendButtonFragment on User {
id
self {
friendship {
__typename
}
}
}

View file

@ -7,7 +7,8 @@
import Module from 'utilities/module';
import {createElement} from 'utilities/dom';
import GET_USER_INFO from './get_user_info.gql';
import GET_CARD_VIEWER from './get_card_viewer.gql';
import GET_BAN_STATUS from './get_ban_status.gql';
export default class ViewerCards extends Module {
constructor(...args) {
@ -16,6 +17,7 @@ export default class ViewerCards extends Module {
this.inject('i18n');
this.inject('settings');
this.inject('site.apollo');
this.inject('site.twitch_data');
this.tabs = {};
@ -71,7 +73,17 @@ export default class ViewerCards extends Module {
}
async openCard(room, user, event) {
async openCard(room, user, msg, event) {
if ( typeof room === 'number' )
room = await this.twitch_data.getUser(room);
else if ( typeof room === 'string' )
room = await this.twitch_data.getUser(undefined, room);
if ( typeof user === 'number' )
user = await this.twitch_data.getUser(user);
else if ( typeof user === 'string' )
user = await this.twitch_data.getUser(undefined, user);
if ( user.userLogin && ! user.login )
user = {
login: user.userLogin,
@ -79,6 +91,18 @@ export default class ViewerCards extends Module {
displayName: user.userDisplayName,
};
if ( ! room || (! room.id && ! room.login) )
return;
if ( ! user || (! user.id && ! user.login) )
return;
if ( ! user.id || ! user.login )
user = await this.twitch_data.getUser(user.id, user.login);
if ( ! room.id || ! room.login )
room = await this.twitch_data.getUser(room.id, room.login);
const old_card = this.open_cards[user.login];
if ( old_card ) {
old_card.$el.style.zIndex = ++this.last_z;
@ -100,13 +124,21 @@ export default class ViewerCards extends Module {
// We start this first...
const user_info = this.apollo.client.query({
query: GET_USER_INFO,
query: GET_CARD_VIEWER,
variables: {
targetLogin: user.login,
channelID: room.id
}
});
const ban_info = this.apollo.client.query({
query: GET_BAN_STATUS,
variables: {
targetUserID: user.id,
channelID: room.id
}
});
// But we only wait on loading Vue, since we can show a loading indicator.
await this.loadVue();
@ -115,13 +147,14 @@ export default class ViewerCards extends Module {
room,
user,
user_info,
ban_info,
pos_x,
pos_y,
);
}
buildCard(room, user, data, pos_x, pos_y) {
buildCard(room, user, data, ban_info, pos_x, pos_y) {
let child;
const component = new this.vue.Vue({
el: createElement('div'),
@ -131,6 +164,7 @@ export default class ViewerCards extends Module {
room,
raw_user: user,
data,
ban_info,
getFFZ: () => this,
getZ: () => ++this.last_z

View file

@ -41,14 +41,14 @@ export default class Channel extends Module {
}
});
this.settings.add('channel.squads.no-autojoin', {
/*this.settings.add('channel.squads.no-autojoin', {
default: false,
ui: {
path: 'Channel > Behavior >> Squads',
title: 'Do not automatically redirect to Squad Streams.',
component: 'setting-check-box'
}
});
});*/
this.ChannelPage = this.fine.define(
@ -63,11 +63,11 @@ export default class Channel extends Module {
Twilight.CHAT_ROUTES
);
this.SquadController = this.fine.define(
/*this.SquadController = this.fine.define(
'squad-controller',
n => n.onSquadPage && n.isValidSquad && n.handleLeaveSquad,
Twilight.CHAT_ROUTES
);
);*/
}
@ -76,8 +76,8 @@ export default class Channel extends Module {
this.RaidController.on('mount', this.wrapRaidController, this);
this.RaidController.on('update', this.noAutoRaids, this);
this.SquadController.on('mount', this.noAutoSquads, this);
this.SquadController.on('update', this.noAutoSquads, this);
//this.SquadController.on('mount', this.noAutoSquads, this);
//this.SquadController.on('update', this.noAutoSquads, this);
this.RaidController.ready((cls, instances) => {
for(const inst of instances)

View file

@ -51,13 +51,6 @@ export default class ChannelBar extends Module {
n => n.getTitle && n.getGame && n.renderGame,
['user']
);
this.HostBar = this.fine.define(
'host-container',
n => n.handleReportHosterClick,
['user']
)
}
onEnable() {

View file

@ -13,6 +13,23 @@ import Module from 'utilities/module';
import SUB_STATUS from './sub_status.gql';
const GLOBAL_SETS = [
0,
33,
42
];
const POINTS_SETS = [
300238151
];
const PRIME_SETS = [
457,
793,
19151,
19194
];
const TIERS = {
1000: 'Tier 1',
2000: 'Tier 2',
@ -541,7 +558,7 @@ export default class EmoteMenu extends Module {
let image;
if ( data.image )
image = (<img class="ffz--menu-badge" src={data.image} srcSet={data.image_set} />);
image = (<img class={`ffz--menu-badge${data.image_large ? ' ffz--menu-badge__large' : ''}`} src={data.image} srcSet={data.image_set} />);
else
image = (<figure class={`ffz--menu-badge ffz-i-${data.icon || 'zreknarf'}`} />);
@ -827,7 +844,7 @@ export default class EmoteMenu extends Module {
this.handleObserve = this.handleObserve.bind(this);
this.pickTone = this.pickTone.bind(this);
this.clickTab = this.clickTab.bind(this);
this.clickRefresh = this.clickRefresh.bind(this);
//this.clickRefresh = this.clickRefresh.bind(this);
this.handleFilterChange = this.handleFilterChange.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
}
@ -946,7 +963,7 @@ export default class EmoteMenu extends Module {
});
}
clickRefresh(event) {
/*clickRefresh(event) {
const target = event.currentTarget,
tt = target && target._ffz_tooltip$0;
@ -978,7 +995,7 @@ export default class EmoteMenu extends Module {
Object.assign({}, this.state, {set_sets: sets, set_data: data, loading: false})
)));
});
}
}*/
handleFilterChange(event) {
this.setState(this.filterState(event.target.value, this.state));
@ -1006,18 +1023,10 @@ export default class EmoteMenu extends Module {
this.setState({loading: true}, () => {
t.getData(sets, force).then(d => {
const promises = [];
for(const set_id of sets)
if ( ! has(d, set_id) )
promises.push(t.emotes.awaitTwitchSetChannel(set_id))
Promise.all(promises).then(() => {
this.setState(this.filterState(this.state.filter, this.buildState(
this.props,
Object.assign({}, this.state, {set_sets: sets, set_data: d, loading: false})
)));
});
this.setState(this.filterState(this.state.filter, this.buildState(
this.props,
Object.assign({}, this.state, {set_sets: sets, set_data: d, loading: false})
)));
});
});
@ -1188,7 +1197,7 @@ export default class EmoteMenu extends Module {
const sorter = this.getSorter(),
sort_tiers = t.chat.context.get('chat.emote-menu.sort-tiers-last'),
sort_emotes = (a,b) => {
if ( a.inventory || b.inventory )
if ( a.misc || b.misc )
return sorter(a,b);
if ( ! a.locked && b.locked ) return -1;
@ -1209,9 +1218,10 @@ export default class EmoteMenu extends Module {
const emote_sets = props.emote_data && props.emote_data.emoteSets,
emote_map = props.emote_data && props.emote_data.emoteMap,
twitch_favorites = t.emotes.getFavorites('twitch'),
twitch_seen_favorites = new Set,
twitch_seen = new Set,
//twitch_seen_favorites = new Set,
inventory = t.emotes.twitch_inventory_sets || new Set,
grouped_sets = {},
set_ids = new Set;
@ -1221,58 +1231,58 @@ export default class EmoteMenu extends Module {
continue;
const set_id = parseInt(emote_set.id, 10),
is_inventory = inventory.has(set_id),
set_data = data[set_id] || {},
more_data = t.emotes.getTwitchSetChannel(set_id, null, false),
image = set_data.image,
image_set = set_data.image_set;
owner = emote_set.owner,
is_points = owner?.login === 'channel_points',
chan = is_points ? null : owner,
set_data = data[set_id];
console.log('set', set_id, owner);
if ( chan )
t.emotes.setTwitchSetChannel(set_id, {
s_id: set_id,
c_id: chan.id,
c_name: chan.login,
c_title: chan.displayName
});
set_ids.add(set_id);
let chan = set_data && set_data.user;
if ( ! chan && more_data && more_data.c_id )
chan = {
id: more_data.c_id,
login: more_data.c_name,
display_name: more_data.c_name,
bad: true
};
let key = `twitch-set-${set_id}`,
sort_key = 0,
icon = 'twitch',
title = chan && chan.display_name;
title = chan && (chan.displayName || chan.login);
if ( title )
key = `twitch-${chan.id}`;
key = `twitch-${chan?.id}`;
else {
if ( is_inventory ) {
title = t.i18n.t('emote-menu.inventory', 'Inventory');
key = 'twitch-inventory';
icon = 'inventory';
sort_key = 50;
else if ( ! chan ) {
if ( is_points || POINTS_SETS.includes(set_id) ) {
title = t.i18n.t('emote-menu.points', 'Unlocked with Points');
key = 'twitch-points';
icon = 'channel-points';
sort_key = 45;
} else if ( set_data && set_data.type === 'turbo' ) {
title = t.i18n.t('emote-menu.prime', 'Prime');
} else if ( GLOBAL_SETS.includes(set_id) ) {
title = t.i18n.t('emote-menu.global', 'Global Emotes');
key = 'twitch-global';
sort_key = 100;
} else if ( PRIME_SETS.includes(set_id) ) {
title = t.i18n.t('emote_menu.prime', 'Prime');
key = 'twitch-prime';
icon = 'crown';
sort_key = 75;
} else if ( more_data ) {
title = more_data.c_name;
} else {
title = t.i18n.t('emote-menu.misc', 'Miscellaneous');
key = 'twitch-misc';
icon = 'inventory';
sort_key = 50;
}
if ( title === '--global--' ) {
title = t.i18n.t('emote-menu.global', 'Global Emotes');
sort_key = 100;
} else if ( title === '--twitch-turbo--' || title === 'turbo' || title === '--turbo-faces--' || title === '--prime--' || title === '--prime-faces--' ) {
title = t.i18n.t('emote-menu.prime', 'Prime');
icon = 'crown';
sort_key = 75;
}
} else
title = t.i18n.t('emote-menu.unknown-set', 'Set #{set_id}', {set_id})
}
} else
title = t.i18n.t('emote-menu.unknown-set', 'Set #{set_id}', {set_id})
let section, emotes;
@ -1280,32 +1290,33 @@ export default class EmoteMenu extends Module {
section = grouped_sets[key];
emotes = section.emotes;
if ( chan && ! chan.bad && section.bad ) {
section.title = title;
section.image = image;
section.image_set = image_set;
section.icon = icon;
section.sort_key = sort_key;
if ( set_data && section.bad ) {
section.bad = false;
section.renews = set_data.renews;
section.ends = set_data.ends;
section.prime = set_data.prime;
section.gift = set_data.gift;
}
} else {
emotes = [];
section = grouped_sets[key] = {
sort_key,
bad: chan ? chan.bad : true,
key,
image,
image_set,
image: chan?.profileImageURL,
image_large: true,
icon,
title,
source: t.i18n.t('emote-menu.twitch', 'Twitch'),
emotes,
renews: set_data.renews,
ends: set_data.ends,
prime: set_data.prime,
gift: set_data.gift && set_data.gift.isGift
renews: set_data?.renews,
ends: set_data?.ends,
prime: set_data?.prime,
gift: set_data?.gift
}
if ( ! set_data )
section.bad = true;
}
for(const emote of emote_set.emotes) {
@ -1339,15 +1350,17 @@ export default class EmoteMenu extends Module {
src,
srcSet,
overridden: overridden ? parseInt(mapped.id,10) : null,
inventory: is_inventory,
misc: ! chan,
favorite: is_fav
};
t.emotes.setTwitchEmoteSet(id, set_id);
emotes.push(em);
if ( is_fav && ! twitch_seen_favorites.has(id) ) {
if ( is_fav && ! twitch_seen.has(id) )
favorites.push(em);
twitch_seen_favorites.add(id);
}
twitch_seen.add(id);
}
if ( emotes.length ) {
@ -1422,6 +1435,7 @@ export default class EmoteMenu extends Module {
const id = parseInt(emote.id, 10),
base = `${TWITCH_EMOTE_BASE}${id}`,
name = KNOWN_CODES[emote.token] || emote.token,
seen = twitch_seen.has(id),
is_fav = twitch_favorites.includes(id);
const em = {
@ -1429,7 +1443,7 @@ export default class EmoteMenu extends Module {
id,
set_id,
name,
locked,
locked: locked && ! seen,
src: `${base}/1.0`,
srcSet: `${base}/1.0 1x, ${base}/2.0 2x`,
favorite: is_fav
@ -1437,10 +1451,10 @@ export default class EmoteMenu extends Module {
emotes.push(em);
if ( ! locked && is_fav && ! twitch_seen_favorites.has(id) ) {
if ( ! locked && is_fav && ! seen )
favorites.push(em);
twitch_seen_favorites.add(id);
}
twitch_seen.add(id);
if ( lock_set )
lock_set.add(id);
@ -1769,7 +1783,7 @@ export default class EmoteMenu extends Module {
</div>
</button>
</div>}
<div class="tw-flex-grow-1" />
{/*<div class="tw-flex-grow-1" />
{!loading && (<div class="emote-picker-tab-item tw-relative">
<button
class="ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--inverted tw-interactive"
@ -1781,7 +1795,7 @@ export default class EmoteMenu extends Module {
<figure class="ffz-i-arrows-cw" />
</div>
</button>
</div>)}
</div>)*/}
</div>
</div>
</div>
@ -1841,7 +1855,16 @@ export default class EmoteMenu extends Module {
if ( ! set_id )
continue;
const owner = product.owner || {},
out[set_id] = {
ends: maybe_date(node.endsAt),
renews: maybe_date(node.renewsAt),
prime: node.purchasedWithPrime,
set_id: parseInt(set_id, 10),
type: product.type,
gift: node.gift?.isGift
};
/*const owner = product.owner || {},
badges = owner.broadcastBadges;
let image, image_set;
@ -1852,7 +1875,7 @@ export default class EmoteMenu extends Module {
if ( image.endsWith('/1') ) {
const base = image.slice(0, -2);
image_set = `${base}/1 1x, ${base}/2 2x, ${base}/4 4x`;
image_set = `${base}/1 1x, ${base}/2 2x, ${base}/3 4x`;
}
break;
@ -1872,7 +1895,7 @@ export default class EmoteMenu extends Module {
login: owner.login,
display_name: owner.displayName
}
}
}*/
}
this._data_sets = sets;

View file

@ -397,7 +397,7 @@ other {# messages were deleted by a moderator.}
}
/*if ( event.ctrlKey )
t.viewer_cards.openCard(r, target_user, event);
t.viewer_cards.openCard(r, target_user, msg, event);
else*/
this.props.onUsernameClick(target_user.login, 'chat_message', msg.id, target.getBoundingClientRect().bottom);
}

View file

@ -259,6 +259,15 @@ export default class Scroller extends Module {
}
inst.scrollToBottom = function() {
if ( inst._ffz_scroll_request )
return;
inst._ffz_scroll_request = requestAnimationFrame(inst._scrollToBottom);
}
inst._scrollToBottom = function() {
inst._ffz_scroll_request = null;
// WIP: Trying to fix the scroll position changing so that we can
// smooth scroll from the previous position.
if ( inst.ffz_smooth_scroll && ! inst._ffz_one_fast_scroll && inst._ffz_snapshot ) {

View file

@ -17,21 +17,7 @@ query FFZ_EmoteMenu_SubStatus($first: Int, $after: Cursor, $criteria: Subscripti
renewsAt
product {
id
name
displayName
emoteSetID
type
owner {
id
login
displayName
broadcastBadges {
id
setID
version
imageURL(size: NORMAL)
}
}
}
}
}

View file

@ -13,11 +13,11 @@ const STYLE_VALIDATOR = document.createElement('span');
const CLASSES = {
'top-discover': '.navigation-link[data-a-target="discover-link"]',
'side-nav': '.side-nav',
'side-rec-channels': '.side-nav .recommended-channels,.side-nav .ffz--popular-channels',
'side-rec-channels': '.side-nav .recommended-channels',
'side-rec-friends': '.side-nav .recommended-friends',
'side-friends': '.side-nav .online-friends',
'side-closed-friends': '.side-nav--collapsed .online-friends',
'side-closed-rec-channels': '.side-nav--collapsed .recommended-channels,.side-nav--collapsed .ffz--popular-channels',
'side-closed-rec-channels': '.side-nav--collapsed .recommended-channels',
'side-offline-channels': '.side-nav-card__link[href*="/videos/"],.side-nav-card[href*="/videos/"]',
'side-rerun-channels': '.side-nav .ffz--side-nav-card-rerun',

View file

@ -25,15 +25,10 @@ export default class Layout extends Module {
n => n.computeStyles && n.navigationLinkSize
);
this.RightColumn = this.fine.define(
/*this.RightColumn = this.fine.define(
'tw-rightcolumn',
n => n.hideOnBreakpoint && n.handleToggleVisibility
);
this.PopularChannels = this.fine.define(
'nav-popular',
n => n.getPopularChannels && n.props && has(n.props, 'locale')
);
);*/
this.SideBarChannels = this.fine.define(
'nav-cards',
@ -176,14 +171,6 @@ export default class Layout extends Module {
this.css_tweaks.setVariable('portrait-extra-width', `${this.settings.get('layout.portrait-extra-width')}rem`);
this.css_tweaks.setVariable('portrait-extra-height', `${this.settings.get('layout.portrait-extra-height')}rem`);
this.PopularChannels.ready((cls, instances) => {
for(const inst of instances)
this.updatePopular(inst);
});
this.PopularChannels.on('mount', this.updatePopular, this);
this.PopularChannels.on('update', this.updatePopular, this);
this.SideBarChannels.ready((cls, instances) => {
for(const inst of instances)
this.updateCardClass(inst);
@ -192,7 +179,7 @@ export default class Layout extends Module {
this.SideBarChannels.on('mount', this.updateCardClass, this);
this.SideBarChannels.on('update', this.updateCardClass, this);
const t = this;
/*const t = this;
this.RightColumn.ready((cls, instances) => {
cls.prototype.ffzHideOnBreakpoint = function() {
try {
@ -235,7 +222,7 @@ export default class Layout extends Module {
window.addEventListener('resize', inst.hideOnBreakpoint);
inst.hideOnBreakpoint();
}
});
});*/
}
get is_minimal() {
@ -258,12 +245,6 @@ export default class Layout extends Module {
} catch(err) { /* no-op */ }
}
updatePopular(inst) {
const node = this.fine.getChildNode(inst);
if ( node )
node.classList.add('ffz--popular-channels');
}
updatePortraitMode() {
for(const inst of this.RightColumn.instances)
inst.hideOnBreakpoint();

View file

@ -101,6 +101,14 @@
margin: 0 0.05rem !important
}
span:before {
margin: 0 0.2rem 0 0 !important;
}
span:empty:before {
margin: 0 !important;
}
span {
display: inline-block;
min-width: 1.6rem;
@ -270,6 +278,15 @@
}
}
&:not(.reduced-padding) {
.ffz--menu-badge__large {
width: 2.8rem;
height: 2.8rem;
margin: -.5rem .5rem -.5rem -.5rem;
}
}
&.reduced-padding {
.tw-pd-1 {
padding: 0.5rem !important;

View file

@ -1,12 +1,14 @@
.ffz-mod-card {
width: 340px;
.ffz-viewer-card {
width: 34rem;
z-index: 9001;
> header {
cursor: move;
&:focus {
outline: none;
box-shadow: var(--shadow-button-focus);
}
background-size: cover;
background-position: top;
> div:first-child {
cursor: move;
}
.ffz-tooltip {
@ -15,18 +17,14 @@
}
}
.ffz--background-dimmer {
background-color: rgba(0, 0, 0, 0.8);
}
.ffz--info-lines > * {
line-height: 1.2;
}
.mod-cards__tabs-container {
.viewer-card__tabs-container {
height: 3rem;
> .mod-cards__tab {
> .viewer-card__tab {
position: relative;
top: -.1rem;
cursor: pointer;
@ -39,4 +37,9 @@
}
}
}
}
.viewer-card__background {
background-position: top;
background-size: cover
}

View file

@ -87,5 +87,8 @@ export default [
"t-pip-inactive",
"docs",
"t-reset-clicked",
"t-reset"
"t-reset",
"whispers",
"cake",
"channel-points"
];