mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-11 00:20:54 +00:00
4.20.42
* 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:
parent
53b22d8a09
commit
b88e6f0f75
17 changed files with 343 additions and 31 deletions
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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">
|
||||
|
|
157
src/modules/main_menu/components/game-list-editor.vue
Normal file
157
src/modules/main_menu/components/game-list-editor.vue
Normal 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>
|
|
@ -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,18 +937,18 @@ export default class MainMenu extends Module {
|
|||
|
||||
close: e => ! this.dialog.exclusive && this.dialog.toggleVisible(e),
|
||||
|
||||
popout: e => {
|
||||
if ( this.dialog.exclusive )
|
||||
return;
|
||||
|
||||
this.dialog.toggleVisible(e);
|
||||
if ( ! this.openPopout() )
|
||||
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,
|
||||
};
|
||||
|
||||
out.popout = e => {
|
||||
if ( this.dialog.exclusive )
|
||||
return;
|
||||
|
||||
this.dialog.toggleVisible(e);
|
||||
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
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
}
|
108
src/modules/main_menu/provider-mixin.js
Normal file
108
src/modules/main_menu/provider-mixin.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue