mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-06 22:30:57 +00:00
4.14.11
* Added: `Current Category` rule type for profile rules. (Ironically, does not currently apply when on the directory page for a category.) * Fixed: Various chat input related features breaking, including Room Actions and tab-completion of emotes. * Fixed: Alignment of certain chat settings menu elements. * Maintenance: Update our copy of the `CHAT_TYPES` enum with new types from Twitch's client.
This commit is contained in:
parent
bbaf96584c
commit
903b0b234c
9 changed files with 199 additions and 8 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.14.10",
|
"version": "4.14.11",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
138
src/settings/components/category.vue
Normal file
138
src/settings/components/category.vue
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
<template>
|
||||||
|
<section class="tw-flex-grow-1 tw-align-self-start">
|
||||||
|
<div class="tw-flex tw-align-items-center">
|
||||||
|
<label :for="'category$' + id">
|
||||||
|
{{ t(type.i18n, type.title) }}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="ffz--search-avatar tw-mg-x-05 tw-card-img--size-2">
|
||||||
|
<aspect :ratio="1/1.33">
|
||||||
|
<img
|
||||||
|
v-if="current"
|
||||||
|
:alt="current.displayName || current.name"
|
||||||
|
:src="current.boxArtURL"
|
||||||
|
class="tw-avatar__img tw-image"
|
||||||
|
>
|
||||||
|
</aspect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<autocomplete
|
||||||
|
v-slot="slot"
|
||||||
|
:input-id="'category$' + id"
|
||||||
|
:items="fetchCategories"
|
||||||
|
:value="search"
|
||||||
|
:suggest-on-focus="true"
|
||||||
|
:escape-to-clear="false"
|
||||||
|
class="tw-flex-grow-1"
|
||||||
|
@selected="onSelected"
|
||||||
|
>
|
||||||
|
<div class="tw-pd-x-1 tw-pd-y-05">
|
||||||
|
<div class="tw-card tw-relative">
|
||||||
|
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row">
|
||||||
|
<div class="tw-card-img tw-card-img--size-3 tw-flex-shrink-0 tw-overflow-hidden">
|
||||||
|
<aspect :ratio="1/1.33">
|
||||||
|
<img
|
||||||
|
:alt="slot.item.displayName"
|
||||||
|
:src="slot.item.boxArtURL"
|
||||||
|
class="tw-image"
|
||||||
|
>
|
||||||
|
</aspect>
|
||||||
|
</div>
|
||||||
|
<div class="tw-card-body tw-overflow-hidden tw-relative">
|
||||||
|
<p class="tw-pd-x-1">
|
||||||
|
{{ slot.item.displayName }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</autocomplete>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import {debounce, deep_copy} from 'utilities/object';
|
||||||
|
|
||||||
|
let last_id = 0;
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['value', 'type', 'filters', 'context'],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: last_id++,
|
||||||
|
current: null,
|
||||||
|
loaded_id: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
search() {
|
||||||
|
return this.current && this.current.displayName || this.value.data.name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
value: {
|
||||||
|
handler() {
|
||||||
|
this.cacheCategory();
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {
|
||||||
|
const ffz = FrankerFaceZ.get();
|
||||||
|
this.loader = ffz.resolve('site.twitch_data');
|
||||||
|
this.cacheCategory = debounce(this.cacheCategory, 50);
|
||||||
|
},
|
||||||
|
|
||||||
|
beforeDestroy() {
|
||||||
|
this.cacheCategory = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
this.cacheCategory();
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
async cacheCategory() {
|
||||||
|
if ( ! this.loader || this.loaded_id === this.value.data.id )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.current = null;
|
||||||
|
this.loaded_id = this.value.data.id;
|
||||||
|
|
||||||
|
if ( ! this.loaded_id )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const data = await this.loader.getCategory(this.loaded_id);
|
||||||
|
if ( data )
|
||||||
|
this.current = deep_copy(data);
|
||||||
|
else
|
||||||
|
this.current = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchCategories(query) {
|
||||||
|
if ( ! this.loader )
|
||||||
|
return [];
|
||||||
|
|
||||||
|
const data = await this.loader.getMatchingCategories(query);
|
||||||
|
if ( ! data || ! data.items )
|
||||||
|
return [];
|
||||||
|
|
||||||
|
return deep_copy(data.items);
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelected(item) {
|
||||||
|
this.current = item;
|
||||||
|
this.value.data.name = item?.name || null;
|
||||||
|
this.value.data.id = item?.id || null;
|
||||||
|
this.value.data.boxArtURL = item?.boxArtURL || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
|
@ -142,7 +142,6 @@ export const Time = {
|
||||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/time.vue')
|
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/time.vue')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const TheaterMode = {
|
export const TheaterMode = {
|
||||||
createTest(config) {
|
createTest(config) {
|
||||||
return ctx => ctx.ui && ctx.ui.theatreModeEnabled === config;
|
return ctx => ctx.ui && ctx.ui.theatreModeEnabled === config;
|
||||||
|
@ -261,4 +260,26 @@ export const Channel = {
|
||||||
id: null
|
id: null
|
||||||
}),
|
}),
|
||||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/channel.vue')
|
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/channel.vue')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const Category = {
|
||||||
|
createTest(config = {}) {
|
||||||
|
const name = config.name,
|
||||||
|
id = config.id;
|
||||||
|
|
||||||
|
if ( ! id || ! name )
|
||||||
|
return () => false;
|
||||||
|
|
||||||
|
return ctx => ctx.categoryID === id || (ctx.categoryID == null && ctx.category === name);
|
||||||
|
},
|
||||||
|
|
||||||
|
title: 'Current Category',
|
||||||
|
i18n: 'settings.filter.category',
|
||||||
|
|
||||||
|
default: () => ({
|
||||||
|
name: null,
|
||||||
|
id: null
|
||||||
|
}),
|
||||||
|
|
||||||
|
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/category.vue')
|
||||||
|
}
|
|
@ -85,23 +85,36 @@ export default class Channel extends Module {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ChannelPage.on('mount', inst => {
|
this.ChannelPage.on('mount', inst => {
|
||||||
|
const category = get('state.video.game', inst) || get('state.clip.game', inst) || get('state.channel.broadcastSettings.game', inst);
|
||||||
|
|
||||||
this.settings.updateContext({
|
this.settings.updateContext({
|
||||||
channel: get('state.channel.login', inst),
|
channel: get('state.channel.login', inst),
|
||||||
channelID: get('state.channel.id', inst)
|
channelID: get('state.channel.id', inst),
|
||||||
|
channelColor: get('state.primaryColorHex', inst),
|
||||||
|
category: category?.name,
|
||||||
|
categoryID: category?.id
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ChannelPage.on('unmount', () => {
|
this.ChannelPage.on('unmount', () => {
|
||||||
this.settings.updateContext({
|
this.settings.updateContext({
|
||||||
channel: null,
|
channel: null,
|
||||||
channelID: null
|
channelID: null,
|
||||||
|
channelColor: null,
|
||||||
|
category: null,
|
||||||
|
categoryID: null
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.ChannelPage.on('update', inst => {
|
this.ChannelPage.on('update', inst => {
|
||||||
|
const category = get('state.video.game', inst) || get('state.clip.game', inst) || get('state.channel.broadcastSettings.game', inst);
|
||||||
|
|
||||||
this.settings.updateContext({
|
this.settings.updateContext({
|
||||||
channel: get('state.channel.login', inst),
|
channel: get('state.channel.login', inst),
|
||||||
channelID: get('state.channel.id', inst)
|
channelID: get('state.channel.id', inst),
|
||||||
|
channelColor: get('state.primaryColorHex', inst),
|
||||||
|
category: category?.name,
|
||||||
|
categoryID: category?.id
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( this.settings.get('channel.hosting.enable') || has(inst.state, 'hostMode') || has(inst.state, 'hostedChannel') )
|
if ( this.settings.get('channel.hosting.enable') || has(inst.state, 'hostMode') || has(inst.state, 'hostedChannel') )
|
||||||
|
|
|
@ -110,6 +110,8 @@ const CHAT_TYPES = make_enum(
|
||||||
'RewardGift',
|
'RewardGift',
|
||||||
'SubMysteryGift',
|
'SubMysteryGift',
|
||||||
'AnonSubMysteryGift',
|
'AnonSubMysteryGift',
|
||||||
|
'StandardPayForward',
|
||||||
|
'CommunityPayForward',
|
||||||
'FirstCheerMessage',
|
'FirstCheerMessage',
|
||||||
'BitsBadgeTierMessage',
|
'BitsBadgeTierMessage',
|
||||||
'InlinePrivateCallout',
|
'InlinePrivateCallout',
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class Input extends Module {
|
||||||
|
|
||||||
this.ChatInput = this.fine.define(
|
this.ChatInput = this.fine.define(
|
||||||
'chat-input',
|
'chat-input',
|
||||||
n => n && n.setChatInputRef && n.setLocalAutocompleteInputRef,
|
n => n && n.setLocalChatInputRef && n.setLocalAutocompleteInputRef,
|
||||||
Twilight.CHAT_ROUTES
|
Twilight.CHAT_ROUTES
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,7 @@ export default class SettingsMenu extends Module {
|
||||||
if ( ! this.ffzPauseClick )
|
if ( ! this.ffzPauseClick )
|
||||||
this.ffzPauseClick = () => this.setState({ffzPauseMenu: ! this.state.ffzPauseMenu});
|
this.ffzPauseClick = () => this.setState({ffzPauseMenu: ! this.state.ffzPauseMenu});
|
||||||
|
|
||||||
return (<div class="tw-absolute tw-balloon tw-balloon--auto tw-balloon--up tw-block" data-a-target="chat-settings-balloon">
|
return (<div class="tw-absolute tw-balloon tw-balloon--auto tw-balloon--right tw-balloon--up tw-block" data-a-target="chat-settings-balloon" style={{marginRight: '-5.3rem'}}>
|
||||||
<div class="tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-2">
|
<div class="tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-2">
|
||||||
<div class="chat-settings__popover">
|
<div class="chat-settings__popover">
|
||||||
<div class="chat-settings__header tw-align-items-center tw-c-background-base tw-flex tw-pd-x-1 tw-relative">
|
<div class="chat-settings__header tw-align-items-center tw-c-background-base tw-flex tw-pd-x-1 tw-relative">
|
||||||
|
|
8
src/utilities/data/category-fetch.gql
Normal file
8
src/utilities/data/category-fetch.gql
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
query FFZ_GetGame($id: ID, $name: String) {
|
||||||
|
game(name: $name, id: $id) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
displayName
|
||||||
|
boxArtURL(width: 40, height: 56)
|
||||||
|
}
|
||||||
|
}
|
|
@ -141,6 +141,15 @@ export default class TwitchData extends Module {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCategory(id, name) {
|
||||||
|
const data = await this.queryApollo(
|
||||||
|
require('./data/category-fetch.gql'),
|
||||||
|
{ id, name }
|
||||||
|
);
|
||||||
|
|
||||||
|
return get('data.game', data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
// Users
|
// Users
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue