1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +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

@ -635,6 +635,40 @@
"search": [
"reset player unclicked"
]
},
{
"uid": "a3d0a62c5abd3e027103287c6702466f",
"css": "whispers",
"code": 59451,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M391.4 650L500 758.6 608.6 650H750V250H250V650H391.4ZM500 900L350 750C310 749.1 269.9 752 230.1 748 178.4 739 144.4 686.4 150 635.7 150.3 503.8 149.3 371.9 150.5 240 154.4 184 210.1 143.7 264.4 150 429.5 150.4 594.8 149.4 760 150.5 816 154.5 856.3 210.1 850 264.4 849.7 396.3 850.7 528.1 849.5 660 845.5 716 789.9 756.4 735.6 750H650L500 900Z",
"width": 1000
},
"search": [
"whispers"
]
},
{
"uid": "bc61ebcf2f5d8d08b1e9e62167df7617",
"css": "cake",
"code": 61949,
"src": "fontawesome"
},
{
"uid": "0048e3bf3ba4e1d92b331a59bec2d3dd",
"css": "channel-points",
"code": 59452,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M500 300A200 200 0 0 1 700 500H600A100 100 0 0 0 500 400ZM511.7 100.2A400 400 0 0 0 100 500 400 400 0 0 0 900 500 400 400 0 0 0 511.7 100.2ZM508.8 200.1A300 300 0 0 1 800 500 300 300 0 0 1 200 500 300 300 0 0 1 508.8 200.1Z",
"width": 1000
},
"search": [
"channel-points"
]
}
]
}

7
package-lock.json generated
View file

@ -1,6 +1,6 @@
{
"name": "frankerfacez",
"version": "4.14.6",
"version": "4.14.12",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2296,9 +2296,8 @@
}
},
"displacejs": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/displacejs/-/displacejs-1.3.2.tgz",
"integrity": "sha512-voRe/da7mfc/yncf0eGnCLB1vwioPHRS6Xv7xoNBXRUJeyq18usqwaxl9WCl5QWVAzEm/lfHxnRmx5w6UBZMkA=="
"version": "github:sirstendec/displace#38306dedc200e980fefe3a9b978700cc6d1f127b",
"from": "github:sirstendec/displace"
},
"dns-equal": {
"version": "1.0.0",

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.14.12",
"version": "4.15.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0",
"scripts": {
@ -65,7 +65,7 @@
"@ffz/icu-msgparser": "^1.0.1",
"crypto-js": "^3.1.9-1",
"dayjs": "^1.8.15",
"displacejs": "^1.2.4",
"displacejs": "github:sirstendec/displace",
"emoji-regex": "^8.0.0",
"file-saver": "^2.0.1",
"graphql": "^14.4.2",

Binary file not shown.

View file

@ -124,6 +124,10 @@
<glyph glyph-name="t-reset" unicode="&#xe83a;" d="M650 650a100 100 0 0 0 100-100v-500h-100v500h-179l64-65-70-70-186 185 186 185 70-70-64-65z m-385-435l64-65h-179v500h-100v-500a100 100 0 0 1 100-100h179l-64-65 70-70 186 185-186 185z" horiz-adv-x="800" />
<glyph glyph-name="whispers" unicode="&#xe83b;" d="M391 200l109-109 109 109h141v400h-500v-400h141z m109-250l-150 150c-40 1-80-2-120 2-52 9-86 62-80 112 0 132-1 264 1 396 3 56 59 96 113 90 166 0 331 1 496 0 56-4 96-60 90-114 0-132 1-264 0-396-4-56-60-96-114-90h-86l-150-150z" horiz-adv-x="1000" />
<glyph glyph-name="channel-points" unicode="&#xe83c;" d="M500 550a200 200 0 0 0 200-200h-100a100 100 0 0 1-100 100z m12 200a400 400 0 0 1-412-400 400 400 0 0 1 800 0 400 400 0 0 1-388 400z m-3-100a300 300 0 0 0 291-300 300 300 0 0 0-600 0 300 300 0 0 0 309 300z" horiz-adv-x="1000" />
<glyph glyph-name="link-ext" unicode="&#xf08e;" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="twitter" unicode="&#xf099;" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" />
@ -166,6 +170,8 @@
<glyph glyph-name="eyedropper" unicode="&#xf1fb;" d="M948 798q52-53 52-127t-52-126l-126-124 58-58q6-6 6-13t-6-13l-117-117q-6-6-13-6t-13 6l-58 59-337-337q-21-21-50-21h-113l-143-71-36 36 71 143v113q0 29 21 50l337 337-59 58q-6 6-6 13t6 13l117 117q6 6 13 6t13-6l58-58 124 126q52 52 126 52t127-52z m-662-769l321 321-107 107-321-321v-107h107z" horiz-adv-x="1000" />
<glyph glyph-name="cake" unicode="&#xf1fd;" d="M1000 64v-214h-1000v214q25 0 47 8t33 15 27 21q16 15 28 22t32 6q13 0 24-4t18-9 18-15q16-14 26-21t33-15 48-8q25 0 47 8t33 15 26 21q12 11 18 15t18 9 24 4q20 0 32-6t28-22q16-13 27-21t32-15 48-8 47 8 33 15 26 21q17 15 29 22t32 6q19 0 31-6t28-22q16-13 27-21t33-15 47-8z m0 179v-107q-13 0-25 4t-17 8-18 15q-16 14-26 21t-33 15-47 8q-26 0-48-8t-33-15-26-21q-12-10-18-15t-18-8-24-4q-20 0-32 6t-28 21q-17 14-27 21t-32 15-48 8q-25 0-47-8t-33-15-27-21q-11-10-18-15t-17-8-24-4q-20 0-32 6t-29 21q-15 14-26 21t-33 15-47 8q-26 0-48-8t-32-15-27-21q-16-15-28-21t-32-6v107q0 45 31 76t76 31h36v250h143v-250h143v250h142v-250h143v250h143v-250h36q45 0 76-31t31-76z m-714 482q0-43-20-66t-52-23q-29 0-50 21t-21 50q0 16 5 29t13 19 18 15 17 18 13 25 5 37q22 0 47-41t25-84z m285 0q0-43-20-66t-51-23q-30 0-50 21t-21 50q0 16 5 29t13 19 17 15 18 18 13 25 5 37q21 0 46-41t25-84z m286 0q0-43-20-66t-51-23q-30 0-51 21t-21 50q0 16 6 29t13 19 17 15 17 18 13 25 6 37q21 0 46-41t25-84z" horiz-adv-x="1000" />
<glyph glyph-name="user-secret" unicode="&#xf21b;" d="M321-7l54 250-54 71-71 36z m143 0l72 357-72-36-53-71z m90 564q-1 2-3 3-5 4-53 4-39 0-93-10-4-1-12-1t-12 1q-54 10-93 10-48 0-54-4-1-1-2-3 1-11 2-16 2-1 5-3t4-6q1-2 4-11t4-12 4-9 5-10 5-8 7-7 7-6 10-4 12-2 13-1q20 0 33 7t18 16 8 20 7 16 10 7h6q6 0 10-7t6-16 9-20 18-16 33-7q7 0 13 1t12 2 9 4 8 6 7 7 5 8 5 10 4 9 4 12 4 11q1 4 4 6t4 3q2 5 3 16z m232-491q0-68-41-106t-108-39h-488q-67 0-108 39t-41 106q0 34 3 66t10 70 21 69 36 58 52 41l-51 123h120q-12 36-12 71 0 7 1 18-109 23-109 54 0 32 118 55 9 35 28 75t40 63q18 21 42 21 17 0 47-17t47-18 47 18 47 17q24 0 42-21 20-23 39-63t29-75q117-23 117-55 0-31-108-54 4-45-11-89h119l-45-126q35-18 60-54t36-80 16-84 5-83z" horiz-adv-x="857.1" />
<glyph glyph-name="window-maximize" unicode="&#xf2d0;" d="M143 64h714v429h-714v-429z m857 625v-678q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v678q0 37 26 63t63 27h822q37 0 63-27t26-63z" horiz-adv-x="1000" />

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Binary file not shown.

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"
];

View file

@ -22,6 +22,10 @@
max-width: 30rem;
}
.ffz--inline-actions {
vertical-align: middle;
}
.ffz-badge {
display: inline-block;

View file

@ -58,6 +58,8 @@
.ffz-i-t-pip-inactive:before { content: '\e838'; } /* '' */
.ffz-i-t-reset-clicked:before { content: '\e839'; } /* '' */
.ffz-i-t-reset:before { content: '\e83a'; } /* '' */
.ffz-i-whispers:before { content: '\e83b'; } /* '' */
.ffz-i-channel-points:before { content: '\e83c'; } /* '' */
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
.ffz-i-twitter:before { content: '\f099'; } /* '' */
.ffz-i-github:before { content: '\f09b'; } /* '' */
@ -79,6 +81,7 @@
.ffz-i-bell-off:before { content: '\f1f7'; } /* '' */
.ffz-i-trash:before { content: '\f1f8'; } /* '' */
.ffz-i-eyedropper:before { content: '\f1fb'; } /* '' */
.ffz-i-cake:before { content: '\f1fd'; } /* '' */
.ffz-i-user-secret:before { content: '\f21b'; } /* '' */
.ffz-i-window-maximize:before { content: '\f2d0'; } /* '' */
.ffz-i-window-minimize:before { content: '\f2d1'; } /* '' */

File diff suppressed because one or more lines are too long

View file

@ -58,6 +58,8 @@
.ffz-i-t-pip-inactive { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe838;&nbsp;'); }
.ffz-i-t-reset-clicked { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe839;&nbsp;'); }
.ffz-i-t-reset { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83a;&nbsp;'); }
.ffz-i-whispers { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83b;&nbsp;'); }
.ffz-i-channel-points { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83c;&nbsp;'); }
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf099;&nbsp;'); }
.ffz-i-github { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09b;&nbsp;'); }
@ -79,6 +81,7 @@
.ffz-i-bell-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1f7;&nbsp;'); }
.ffz-i-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1f8;&nbsp;'); }
.ffz-i-eyedropper { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1fb;&nbsp;'); }
.ffz-i-cake { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1fd;&nbsp;'); }
.ffz-i-user-secret { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf21b;&nbsp;'); }
.ffz-i-window-maximize { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf2d0;&nbsp;'); }
.ffz-i-window-minimize { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf2d1;&nbsp;'); }

View file

@ -69,6 +69,8 @@
.ffz-i-t-pip-inactive { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe838;&nbsp;'); }
.ffz-i-t-reset-clicked { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe839;&nbsp;'); }
.ffz-i-t-reset { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83a;&nbsp;'); }
.ffz-i-whispers { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83b;&nbsp;'); }
.ffz-i-channel-points { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe83c;&nbsp;'); }
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf099;&nbsp;'); }
.ffz-i-github { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09b;&nbsp;'); }
@ -90,6 +92,7 @@
.ffz-i-bell-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1f7;&nbsp;'); }
.ffz-i-trash { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1f8;&nbsp;'); }
.ffz-i-eyedropper { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1fb;&nbsp;'); }
.ffz-i-cake { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1fd;&nbsp;'); }
.ffz-i-user-secret { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf21b;&nbsp;'); }
.ffz-i-window-maximize { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf2d0;&nbsp;'); }
.ffz-i-window-minimize { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf2d1;&nbsp;'); }

View file

@ -1,11 +1,11 @@
@font-face {
font-family: 'ffz-fontello';
src: url('../font/ffz-fontello.eot?50383568');
src: url('../font/ffz-fontello.eot?50383568#iefix') format('embedded-opentype'),
url('../font/ffz-fontello.woff2?50383568') format('woff2'),
url('../font/ffz-fontello.woff?50383568') format('woff'),
url('../font/ffz-fontello.ttf?50383568') format('truetype'),
url('../font/ffz-fontello.svg?50383568#ffz-fontello') format('svg');
src: url('../font/ffz-fontello.eot?90039340');
src: url('../font/ffz-fontello.eot?90039340#iefix') format('embedded-opentype'),
url('../font/ffz-fontello.woff2?90039340') format('woff2'),
url('../font/ffz-fontello.woff?90039340') format('woff'),
url('../font/ffz-fontello.ttf?90039340') format('truetype'),
url('../font/ffz-fontello.svg?90039340#ffz-fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'ffz-fontello';
src: url('../font/ffz-fontello.svg?50383568#ffz-fontello') format('svg');
src: url('../font/ffz-fontello.svg?90039340#ffz-fontello') format('svg');
}
}
*/
@ -114,6 +114,8 @@
.ffz-i-t-pip-inactive:before { content: '\e838'; } /* '' */
.ffz-i-t-reset-clicked:before { content: '\e839'; } /* '' */
.ffz-i-t-reset:before { content: '\e83a'; } /* '' */
.ffz-i-whispers:before { content: '\e83b'; } /* '' */
.ffz-i-channel-points:before { content: '\e83c'; } /* '' */
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
.ffz-i-twitter:before { content: '\f099'; } /* '' */
.ffz-i-github:before { content: '\f09b'; } /* '' */
@ -135,6 +137,7 @@
.ffz-i-bell-off:before { content: '\f1f7'; } /* '' */
.ffz-i-trash:before { content: '\f1f8'; } /* '' */
.ffz-i-eyedropper:before { content: '\f1fb'; } /* '' */
.ffz-i-cake:before { content: '\f1fd'; } /* '' */
.ffz-i-user-secret:before { content: '\f21b'; } /* '' */
.ffz-i-window-maximize:before { content: '\f2d0'; } /* '' */
.ffz-i-window-minimize:before { content: '\f2d1'; } /* '' */