From b88e6f0f75b93e59c17d258f2fd3b751d2700f3a Mon Sep 17 00:00:00 2001 From: SirStendec Date: Wed, 14 Oct 2020 14:55:10 -0400 Subject: [PATCH] 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. --- package.json | 2 +- src/modules/chat/emoji.js | 5 + src/modules/chat/index.js | 4 +- .../main_menu/components/async-text.vue | 2 +- .../main_menu/components/changelog.vue | 2 +- .../main_menu/components/game-list-editor.vue | 157 ++++++++++++++++++ src/modules/main_menu/index.js | 39 +++-- src/modules/main_menu/provider-mixin.js | 108 ++++++++++++ src/settings/index.js | 3 +- .../modules/chat/rich_content.jsx | 2 +- .../modules/chat/viewer_card.jsx | 2 +- .../modules/css_tweaks/index.js | 7 +- .../css_tweaks/styles/theatre-metadata.scss | 2 +- .../modules/directory/game.jsx | 25 ++- .../modules/directory/index.jsx | 10 +- .../modules/featured-follow.vue | 2 +- src/utilities/compat/webmunch.js | 2 +- 17 files changed, 343 insertions(+), 31 deletions(-) create mode 100644 src/modules/main_menu/components/game-list-editor.vue create mode 100644 src/modules/main_menu/provider-mixin.js diff --git a/package.json b/package.json index 2a1f92e3..bd7ea275 100755 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/modules/chat/emoji.js b/src/modules/chat/emoji.js index 6f4b8515..89888ec1 100644 --- a/src/modules/chat/emoji.js +++ b/src/modules/chat/emoji.js @@ -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', diff --git a/src/modules/chat/index.js b/src/modules/chat/index.js index 9be248eb..a3e81298 100644 --- a/src/modules/chat/index.js +++ b/src/modules/chat/index.js @@ -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 } } diff --git a/src/modules/main_menu/components/async-text.vue b/src/modules/main_menu/components/async-text.vue index e9afaa81..94ede8fc 100644 --- a/src/modules/main_menu/components/async-text.vue +++ b/src/modules/main_menu/components/async-text.vue @@ -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(); diff --git a/src/modules/main_menu/components/changelog.vue b/src/modules/main_menu/components/changelog.vue index 1c171a4d..08014d6d 100644 --- a/src/modules/main_menu/components/changelog.vue +++ b/src/modules/main_menu/components/changelog.vue @@ -91,7 +91,7 @@
- {{ t('home.changelog.error', 'An error occured loading changes from GitHub.') }} + {{ t('home.changelog.error', 'An error occurred loading changes from GitHub.') }}

diff --git a/src/modules/main_menu/components/game-list-editor.vue b/src/modules/main_menu/components/game-list-editor.vue new file mode 100644 index 00000000..1742b75a --- /dev/null +++ b/src/modules/main_menu/components/game-list-editor.vue @@ -0,0 +1,157 @@ + + + \ No newline at end of file diff --git a/src/modules/main_menu/index.js b/src/modules/main_menu/index.js index 87050eee..6988a9cf 100644 --- a/src/modules/main_menu/index.js +++ b/src/modules/main_menu/index.js @@ -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; } } \ No newline at end of file diff --git a/src/modules/main_menu/provider-mixin.js b/src/modules/main_menu/provider-mixin.js new file mode 100644 index 00000000..3b6e0107 --- /dev/null +++ b/src/modules/main_menu/provider-mixin.js @@ -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); + } + } +} \ No newline at end of file diff --git a/src/settings/index.js b/src/settings/index.js index f257f3d3..3302afa6 100644 --- a/src/settings/index.js +++ b/src/settings/index.js @@ -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 ) diff --git a/src/sites/twitch-twilight/modules/chat/rich_content.jsx b/src/sites/twitch-twilight/modules/chat/rich_content.jsx index 363083ac..b50f0148 100644 --- a/src/sites/twitch-twilight/modules/chat/rich_content.jsx +++ b/src/sites/twitch-twilight/modules/chat/rich_content.jsx @@ -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 ) { diff --git a/src/sites/twitch-twilight/modules/chat/viewer_card.jsx b/src/sites/twitch-twilight/modules/chat/viewer_card.jsx index c632fb54..840fe6e7 100644 --- a/src/sites/twitch-twilight/modules/chat/viewer_card.jsx +++ b/src/sites/twitch-twilight/modules/chat/viewer_card.jsx @@ -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 diff --git a/src/sites/twitch-twilight/modules/css_tweaks/index.js b/src/sites/twitch-twilight/modules/css_tweaks/index.js index 31f7c9cd..c4b426bf 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/index.js +++ b/src/sites/twitch-twilight/modules/css_tweaks/index.js @@ -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) diff --git a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss index 90ae1b9f..1b8a7213 100644 --- a/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss +++ b/src/sites/twitch-twilight/modules/css_tweaks/styles/theatre-metadata.scss @@ -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; diff --git a/src/sites/twitch-twilight/modules/directory/game.jsx b/src/sites/twitch-twilight/modules/directory/game.jsx index b9dbfd42..c93ed242 100644 --- a/src/sites/twitch-twilight/modules/directory/game.jsx +++ b/src/sites/twitch-twilight/modules/directory/game.jsx @@ -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); diff --git a/src/sites/twitch-twilight/modules/directory/index.jsx b/src/sites/twitch-twilight/modules/directory/index.jsx index 00965c54..c3ec8f15 100644 --- a/src/sites/twitch-twilight/modules/directory/index.jsx +++ b/src/sites/twitch-twilight/modules/directory/index.jsx @@ -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; diff --git a/src/sites/twitch-twilight/modules/featured-follow.vue b/src/sites/twitch-twilight/modules/featured-follow.vue index 880b862b..61036167 100644 --- a/src/sites/twitch-twilight/modules/featured-follow.vue +++ b/src/sites/twitch-twilight/modules/featured-follow.vue @@ -30,7 +30,7 @@
- {{ t('featured-follow.error', 'An error occured.') }} + {{ t('featured-follow.error', 'An error occurred.') }}