1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-08-01 15:38:31 +00:00
* Added: Settings page for editing the list of categories that are Blocked or have Hidden Thumbnails.
* Changed: When popping out the FFZ Control Center, it now remembers which page it was on.
* Fixed: Emoji-related features not functioning correctly if the emoji style is set to an invalid value. (Closes #923)
* Fixed: Messages not being highlighted when a viewer card is open. (Closes #920)
* Fixed: Disable minimal navigation for Watch Parties. (Closes #916)
* Fixed: Block and Hide Thumbnails controls not appearing on appropriate directory pages.
* Fixed: Hide Thumbnails not working for a category when on that category's directory page.
* Fixed: Move up theater metadata to avoid it overlapping with the seek preview when viewing videos.
* Fixed: Various instances of `occurred` being spelled incorrectly.
* Fixed: Setting incorrectly named `Show Twitch Prime offers.` has been renamed to `Show Prime Gaming Loot.` to reflect Amazon's branding changes.
* API Added: `ProviderMixin` for creating Vue components for settings that are set directly to the settings provider. See the new `games-list-editor` for an example.
* API Added: Select box and combo box settings now have an optional `no_i18n` property to avoid i18n keys being automatically generated for their entries.
This commit is contained in:
SirStendec 2020-10-14 14:55:10 -04:00
parent 53b22d8a09
commit b88e6f0f75
17 changed files with 343 additions and 31 deletions

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.20.41",
"version": "4.20.42",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0",
"scripts": {

View file

@ -78,6 +78,11 @@ export default class Emoji extends Module {
this.settings.add('chat.emoji.style', {
default: 'twitter',
process(ctx, val) {
if ( val != 0 && ! IMAGE_PATHS[val] )
return 'twitter';
return val;
},
ui: {
path: 'Chat > Appearance >> Emoji',
title: 'Emoji Style',

View file

@ -1665,12 +1665,12 @@ export default class Chat extends Module {
if ( data.error )
data = {
v: 5,
title: this.i18n.t('card.error', 'An error occured.'),
title: this.i18n.t('card.error', 'An error occurred.'),
description: data.error,
short: {
type: 'header',
image: {type: 'image', url: ERROR_IMAGE},
title: {type: 'i18n', key: 'card.error', phrase: 'An error occured.'},
title: {type: 'i18n', key: 'card.error', phrase: 'An error occurred.'},
subtitle: data.error
}
}

View file

@ -100,7 +100,7 @@ export default {
if ( ! response.ok ) {
this.uploading = false;
this.url = 'An error occured.';
this.url = 'An error occurred.';
}
this.url = await response.text();

View file

@ -91,7 +91,7 @@
<div class="tw-align-center tw-pd-1">
<div v-if="error">
{{ t('home.changelog.error', 'An error occured loading changes from GitHub.') }}
{{ t('home.changelog.error', 'An error occurred loading changes from GitHub.') }}
</div>
<h1 v-else-if="loading" class="tw-mg-5 ffz-i-zreknarf loading" />
<div v-else-if="! more">

View file

@ -0,0 +1,157 @@
<template lang="html">
<section class="ffz--widget ffz--game-list">
<div class="tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row tw-full-width">
<div class="tw-flex-grow-1 tw-mg-r-05">
<autocomplete
v-slot="slot"
v-model="adding"
:input-id="'category$' + id"
:items="fetchCategories"
:suggest-on-focus="true"
:escape-to-clear="false"
class="tw-flex-grow-1"
>
<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>
<div class="tw-flex-shrink-0">
<button class="tw-button" @click="add">
<span class="tw-button__text">
{{ t('setting.terms.add-term', 'Add') }}
</span>
</button>
</div>
</div>
<div v-if="! value || ! value.length" class="tw-mg-t-05 tw-c-text-alt-2 tw-font-size-4 tw-align-center tw-c-text-alt-2 tw-pd-05">
{{ t('setting.no-items', 'no items') }}
</div>
<ul v-else class="ffz--term-list tw-mg-t-05">
<li
v-for="i in value"
:key="i"
class="ffz--term ffz--game-term tw-align-items-center tw-flex tw-flex-nowrap tw-flex-row tw-full-width"
>
<div class="tw-flex-grow-1 tw-mg-r-05">
<a
v-if="can_link"
:href="`/directory/game/${i}`"
class="tw-link"
@click.prevent="handleLink(i)"
>
{{ i }}
</a>
<span v-else>
{{ i }}
</span>
</div>
<div class="tw-flex-shrink-0">
<button class="tw-button tw-button--text tw-tooltip-wrapper" @click="remove(i)">
<span class="tw-button__text ffz-i-trash" />
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('setting.delete', 'Delete') }}
</div>
</button>
</div>
</li>
</ul>
</section>
</template>
<script>
import ProviderMixin from '../provider-mixin';
import {deep_copy} from 'utilities/object';
let last_id = 0;
export default {
mixins: [ProviderMixin],
props: ['item', 'context'],
data() {
return {
id: last_id++,
adding: '',
can_link: false
}
},
created() {
const ffz = this.context.getFFZ();
this.loader = ffz.resolve('site.twitch_data');
this.router = ffz.resolve('site.router');
this.can_link = ! ffz.resolve('main_menu').exclusive;
},
methods: {
add() {
if ( ! this.adding?.length )
return;
const values = Array.from(this.value);
if ( values.includes(this.adding) )
return;
values.push(this.adding);
this.set(values);
},
remove(item) {
const values = Array.from(this.value),
idx = values.indexOf(item);
if ( idx === -1 )
return;
if ( values.length === 1 )
this.clear();
else {
values.splice(idx, 1);
this.set(values);
}
},
handleLink(i) {
this.router.navigate('dir-game-index', {gameName: i});
},
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(...args) {
console.log('selected', args);
}
}
}
</script>

View file

@ -10,7 +10,8 @@ import {get, has, deep_copy} from 'utilities/object';
import Dialog from 'utilities/dialog';
import Mixin from './setting-mixin';
import SettingMixin from './setting-mixin';
import ProviderMixin from './provider-mixin';
import {parse_path} from 'src/settings';
@ -32,7 +33,8 @@ export default class MainMenu extends Module {
this.load_requires = ['vue'];
this.Mixin = Mixin;
this.Mixin = this.SettingMixin = SettingMixin;
this.ProviderMixin = ProviderMixin;
//this.should_enable = true;
@ -134,9 +136,9 @@ export default class MainMenu extends Module {
this.scheduleUpdate();
}
openPopout() {
openPopout(item) {
const win = window.open(
'https://twitch.tv/popout/frankerfacez/chat?ffz-settings',
`https://twitch.tv/popout/frankerfacez/chat?ffz-settings${item ? `=${encodeURIComponent(item)}` : ''}`,
'_blank',
'resizable=yes,scrollbars=yes,width=850,height=600'
);
@ -633,6 +635,7 @@ export default class MainMenu extends Module {
const t = this,
Vue = this.vue.Vue,
settings = this.settings,
provider = settings.provider,
context = settings.main_context,
[profiles, profile_keys] = this.getProfiles();
@ -665,6 +668,16 @@ export default class MainMenu extends Module {
getFFZ: () => t.resolve('core'),
provider: {
unwrap: () => provider,
get: (...args) => provider.get(...args),
set: (...args) => provider.set(...args),
delete: (...args) => provider.delete(...args),
has: (...args) => provider.has(...args),
on: (...args) => provider.on(...args),
off: (...args) => provider.off(...args)
},
context: {
_users: 0,
@ -924,17 +937,17 @@ export default class MainMenu extends Module {
close: e => ! this.dialog.exclusive && this.dialog.toggleVisible(e),
popout: e => {
version: window.FrankerFaceZ.version_info,
};
out.popout = e => {
if ( this.dialog.exclusive )
return;
this.dialog.toggleVisible(e);
if ( ! this.openPopout() )
if ( ! this.openPopout(out.currentItem?.full_key) )
alert(this.i18n.t('popup.error', 'We tried opening a pop-up window and could not. Make sure to allow pop-ups from Twitch.')); // eslint-disable-line no-alert
},
version: window.FrankerFaceZ.version_info,
};
}
return out;
}

View file

@ -0,0 +1,108 @@
'use strict';
import {deep_copy} from 'utilities/object';
export default {
data() {
return {
value: undefined,
has_value: false,
_unseen: false
}
},
created() {
const provider = this.context.provider,
setting = this.item.setting;
provider.on('changed', this._providerChange, this);
this.has_value = provider.has(setting);
if ( this.has_value )
this.value = deep_copy(provider.get(setting));
else
this.value = this.default_value;
if ( this.item.unseen ) {
this._unseen = true;
this.item.unseen = 0;
}
},
destroyed() {
const provider = this.context.provider;
provider.off('changed', this._providerChange, this);
this.value = undefined;
this.has_value = false;
},
computed: {
data() {
const data = this.item.data;
if ( typeof data === 'function' )
return data(this.value);
return data;
},
unseen() {
return this._unseen || this.item.unseen > 0;
},
default_value() {
if ( typeof this.item.default === 'function' )
return this.item.default(this.context);
return deep_copy(this.item.default);
},
isDefault() {
return ! this.has_value
}
},
methods: {
_providerChange(key, val, deleted) {
if ( key !== this.item.setting )
return;
if ( deleted ) {
this.has_value = false;
this.value = this.default_value;
} else {
this.has_value = true;
this.value = deep_copy(val);
}
},
set(value) {
if ( this.item.process )
value = this.item.process(value);
const provider = this.context.provider,
setting = this.item.setting;
provider.set(setting, value);
this.has_value = true;
this.value = deep_copy(value);
if ( this.item.onUIChange )
this.item.onUIChange(this.value);
},
clear() {
const provider = this.context.provider,
setting = this.item.setting;
provider.delete(setting);
this.value = this.default_value;
this.has_value = false;
if ( this.item.onUIChange )
this.item.onUIChange(this.value);
}
}
}

View file

@ -565,7 +565,8 @@ export default class SettingsManager extends Module {
if ( ! ui.key && ui.title )
ui.key = ui.title.toSnakeCase();
if ( (ui.component === 'setting-select-box' || ui.component === 'setting-combo-box') && Array.isArray(ui.data) ) {
if ( (ui.component === 'setting-select-box' || ui.component === 'setting-combo-box') && Array.isArray(ui.data) && ! ui.no_i18n
&& key !== 'ffzap.core.highlight_sound' ) { // TODO: Remove workaround.
const i18n_base = `${ui.i18n_key || `setting.entry.${key}`}.values`;
for(const value of ui.data) {
if ( value.i18n_key === undefined && value.value !== undefined )

View file

@ -196,7 +196,7 @@ export default class RichContent extends Module {
renderBasic() {
let title, description;
if ( this.state.error ) {
title = t.i18n.t('card.error', 'An error occured.');
title = t.i18n.t('card.error', 'An error occurred.');
description = this.state.error;
} else if ( this.state.loaded && this.state.has_tokenizer ) {

View file

@ -78,7 +78,7 @@ export default class ViewerCards extends Module {
else
color = 'rgba(128,170,255,0.2)';
this.css_tweaks.set('viewer-card-highlight', `body .chat-list .chat-line__message:not(.chat-line--inline):nth-child(1n+0)[data-user="${login}"] {
this.css_tweaks.set('viewer-card-highlight', `body .chat-room .chat-line__message:not(.chat-line--inline):nth-child(1n+0)[data-user="${login}"] {
background-color: ${color} !important;
}`);
} else

View file

@ -241,9 +241,12 @@ export default class CSSTweaks extends Module {
});
this.settings.add('layout.minimal-navigation', {
requires: ['layout.theatre-navigation'],
requires: ['layout.theatre-navigation', 'context.isWatchParty'],
default: false,
process(ctx, val) {
if ( ctx.get('context.isWatchParty') )
return false;
return ctx.get('layout.theatre-navigation') ?
true : val;
},
@ -288,7 +291,7 @@ export default class CSSTweaks extends Module {
default: true,
ui: {
path: 'Appearance > Layout >> Top Navigation',
title: 'Show Twitch Prime offers.',
title: 'Show Prime Gaming Loot.',
component: 'setting-check-box'
},
changed: val => this.toggleHide('prime-offers', !val)

View file

@ -2,7 +2,7 @@
.channel-info-content > div:first-child,
.channel-info-bar {
position: fixed;
bottom: 10rem;
bottom: 17rem;
left: 5rem;
right: calc(var(--ffz-chat-width) + 40rem);
z-index: 3500;

View file

@ -21,9 +21,23 @@ export default class Game extends SiteModule {
this.GameHeader = this.fine.define(
'game-header',
n => n.props && n.props.data && n.getBannerImage && n.getFollowButton,
n => n.props && n.props.data && n.getBannerImage && n.getDirectoryCountAndTags,
['dir-game-index', 'dir-community', 'dir-game-videos', 'dir-game-clips', 'dir-game-details']
);
this.settings.addUI('directory.game.blocked-games', {
path: 'Directory > Categories @{"description": "Please note that due to limitations in Twitch\'s website, names here must be formatted exactly as displayed in your client. For best results, you can block or unblock categories directly from directory pages."} >> Blocked',
component: 'game-list-editor',
default: [],
onUIChange: () => this.parent.updateCards()
});
this.settings.addUI('directory.game.hidden-thumbnails', {
path: 'Directory > Categories >> Hidden Thumbnails',
component: 'game-list-editor',
default: [],
onUIChange: () => this.parent.updateCards()
});
}
onEnable() {
@ -36,6 +50,15 @@ export default class Game extends SiteModule {
})
});
this.settings.provider.on('changed', key => {
if ( key === 'directory.game.blocked-games' || key === 'directory.game.hidden-thumbnails' ) {
this.parent.updateCards();
for(const inst of this.GameHeader.instances)
this.updateGameHeader(inst);
}
});
this.GameHeader.ready((cls, instances) => {
for(const inst of instances)
this.updateGameHeader(inst);

View file

@ -57,9 +57,10 @@ export default class Directory extends SiteModule {
default: 2,
ui: {
path: 'Directory > Channels >> Blocked and Hidden Categories',
path: 'Directory > Categories >> Hidden Thumbnail Style @{"sort": 100}',
title: 'Hidden Style',
component: 'setting-select-box',
sort: 100,
data: [
{value: 0, title: 'Replace Image'},
@ -79,9 +80,10 @@ export default class Directory extends SiteModule {
default: false,
ui: {
path: 'Directory > Channels >> Blocked and Hidden Categories',
path: 'Directory > Categories >> Hidden Thumbnail Style',
title: 'Reveal hidden entries on mouse hover.',
component: 'setting-check-box'
component: 'setting-check-box',
sort: 101
},
changed: val => this.css_tweaks.toggle('dir-reveal', val)
@ -329,7 +331,7 @@ export default class Directory extends SiteModule {
if ( ! props?.channelLogin )
return;
const game = props.gameTitle || props.trackingProps?.categoryName;
const game = props.gameTitle || props.trackingProps?.categoryName || props.trackingProps?.category;
el.classList.toggle('ffz-hide-thumbnail', this.settings.provider.get('directory.game.hidden-thumbnails', []).includes(game));
el.dataset.ffzType = props.streamType;

View file

@ -30,7 +30,7 @@
<div class="tw-flex-grow-1 tw-pd-x-2" />
<div v-if="user.error">
{{ t('featured-follow.error', 'An error occured.') }}
{{ t('featured-follow.error', 'An error occurred.') }}
</div>
<template v-else>
<button

View file

@ -111,7 +111,7 @@ export default class WebMunch extends Module {
for(const fn of t._resolve_require)
fn(require);
} catch(err) {
t.log.error('An error occured running require callbacks.', err);
t.log.error('An error occurred running require callbacks.', err);
}
t._resolve_require = null;