mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-09-17 02:16:54 +00:00
4.78.1
* Fixed: Certain page elements not being correctly hidden when FFZ first loads. * Fixed: Backup and Restore not allowing you to restore a backup with blobs if the current settings provider does not support blobs. It now prompts you to confirm the restoration. * Fixed: The settings provider switcher now more accurately displays if providers contain existing data. * Fixed: Issue where the FFZ Control Center fails to load in a pop-out on Firefox. * Fixed: FFZ believing it is in developer mode when loaded from an extension in some cases. * Changed: The setting to hide Stories now hides them in the directory as well. * Changed: The settings provider switcher now displays a warning if transferring your settings would erase data in the destination provider. * Changed: The settings provider switcher now displays a tag on providers that are Cross-Origin Compatible.
This commit is contained in:
parent
b62e2f7530
commit
3551d5c650
8 changed files with 247 additions and 55 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.78.0",
|
"version": "4.78.1",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
|
@ -4,7 +4,32 @@
|
||||||
{{ t('setting.backup-restore.about', 'This tool allows you to backup and restore your FrankerFaceZ settings, including all settings from the Control Center along with other data such as favorited emotes and blocked games.') }}
|
{{ t('setting.backup-restore.about', 'This tool allows you to backup and restore your FrankerFaceZ settings, including all settings from the Control Center along with other data such as favorited emotes and blocked games.') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="checking_allow_no_blobs"
|
||||||
|
class="tw-c-background-accent-alt-2 tw-c-text-overlay tw-pd-1 tw-mg-b-1"
|
||||||
|
>
|
||||||
|
<h3 class="ffz-i-attention ffz-font-size-3">
|
||||||
|
{{ t('setting.backup-restore.blob-notice', 'This backup contains binary data not supported by the current storage provider.') }}
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
{{ t('setting.backup-restore.blob-notice.desc', 'The binary data will be discarded and certain settings, notably ones with custom sound files, may not work correctly. Do you wish to continue? Alternatively, you will need to change your settings provider to one supporting binary data.') }}
|
||||||
|
</div>
|
||||||
<div class="tw-flex tw-align-items-center tw-justify-content-center tw-mg-b-1">
|
<div class="tw-flex tw-align-items-center tw-justify-content-center tw-mg-b-1">
|
||||||
|
<button
|
||||||
|
class="tw-button tw-mg-x-1"
|
||||||
|
@click="continueBlob"
|
||||||
|
>
|
||||||
|
<span class="tw-button__icon tw-button__icon--left">
|
||||||
|
<figure class="ffz-i-download" />
|
||||||
|
</span>
|
||||||
|
<span class="tw-button__text">
|
||||||
|
{{ t('setting.backup-restore.blob-notice.continue', 'Continue Restoration') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="! error" class="tw-flex tw-align-items-center tw-justify-content-center tw-mg-b-1">
|
||||||
<button
|
<button
|
||||||
class="tw-button tw-mg-x-1"
|
class="tw-button tw-mg-x-1"
|
||||||
@click="backup"
|
@click="backup"
|
||||||
|
@ -57,6 +82,8 @@ export default {
|
||||||
return {
|
return {
|
||||||
error_desc: null,
|
error_desc: null,
|
||||||
error: false,
|
error: false,
|
||||||
|
checking_allow_no_blobs: false,
|
||||||
|
confirmed_no_blobs: false,
|
||||||
message: null
|
message: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -153,6 +180,14 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
continueBlob() {
|
||||||
|
this.checking_allow_no_blobs = false;
|
||||||
|
this.confirmed_no_blobs = true;
|
||||||
|
const file = this.file;
|
||||||
|
this.file = null;
|
||||||
|
return this.restoreZip(file);
|
||||||
|
},
|
||||||
|
|
||||||
async restoreZip(file) {
|
async restoreZip(file) {
|
||||||
const JSZip = (await import(/* webpackChunkName: "zip" */ 'jszip')).default;
|
const JSZip = (await import(/* webpackChunkName: "zip" */ 'jszip')).default;
|
||||||
let input, blobs, data;
|
let input, blobs, data;
|
||||||
|
@ -194,12 +229,19 @@ export default {
|
||||||
const provider = settings.provider;
|
const provider = settings.provider;
|
||||||
await provider.awaitReady();
|
await provider.awaitReady();
|
||||||
|
|
||||||
if ( Object.keys(blobs).length && ! provider.supportsBlobs ) {
|
let b = 0;
|
||||||
this.error_desc = this.t('setting.backup-restore.blob-error', 'This backup contains binary data not supported by the current storage provider. Please change your storage provider in Data Management > Storage >> Provider.');
|
|
||||||
this.error = true;
|
// Blobs
|
||||||
|
if ( Object.keys(blobs).length ) {
|
||||||
|
if ( ! provider.supportsBlobs && ! this.confirmed_no_blobs ) {
|
||||||
|
this.checking_allow_no_blobs = true;
|
||||||
|
this.file = file;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.confirmed_no_blobs = false;
|
||||||
|
|
||||||
|
if ( provider.supportsBlobs ) {
|
||||||
// Attempt to load all the blobs, to make sure they're all valid.
|
// Attempt to load all the blobs, to make sure they're all valid.
|
||||||
const loaded_blobs = {};
|
const loaded_blobs = {};
|
||||||
|
|
||||||
|
@ -224,13 +266,17 @@ export default {
|
||||||
|
|
||||||
// We've loaded all data, let's get this installed.
|
// We've loaded all data, let's get this installed.
|
||||||
// Blobs first.
|
// Blobs first.
|
||||||
let b = 0;
|
|
||||||
await provider.clearBlobs();
|
await provider.clearBlobs();
|
||||||
|
|
||||||
for(const [key, blob] of Object.entries(loaded_blobs)) {
|
for(const [key, blob] of Object.entries(loaded_blobs)) {
|
||||||
await provider.setBlob(key, blob); // eslint-disable-line no-await-in-loop
|
await provider.setBlob(key, blob); // eslint-disable-line no-await-in-loop
|
||||||
b++;
|
b++;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ( provider.supportsBlobs )
|
||||||
|
// Make sure blobs are cleared either way.
|
||||||
|
await provider.clearBlobs();
|
||||||
|
|
||||||
// Settings second.
|
// Settings second.
|
||||||
provider.clear();
|
provider.clear();
|
||||||
|
|
|
@ -41,14 +41,29 @@
|
||||||
<span class="tw-strong">
|
<span class="tw-strong">
|
||||||
{{ t(val.i18n_key, val.title) }}
|
{{ t(val.i18n_key, val.title) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="val.key === current" class="tw-mg-l-1 tw-c-text-alt">
|
<span v-if="val.key === current" class="tw-mg-l-1 tw-c-text-alt tw-relative ffz-il-tooltip__container">
|
||||||
{{ t('setting.provider.selected', '(Current)') }}
|
{{ t('setting.provider.selected', '(Current)') }}
|
||||||
|
<div class="ffz-il-tooltip ffz-il-tooltip--align-center ffz-il-tooltip--up">
|
||||||
|
{{ t('setting.provider.selected.about', 'This is the currently active settings provider.') }}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="val.has_data" class="tw-mg-l-1 tw-c-text-alt">
|
<span v-if="val.has_data" class="tw-mg-l-1 tw-c-text-alt tw-relative ffz-il-tooltip__container">
|
||||||
{{ t('setting.provider.has-data', '(Has Data)') }}
|
{{ t('setting.provider.has-data', '(Has Data)') }}
|
||||||
|
<div class="ffz-il-tooltip ffz-il-tooltip--align-center ffz-il-tooltip--up">
|
||||||
|
{{ t('setting.provider.has-data.about', 'This provider has data saved in it.') }}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span v-if="val.has_blobs" class="tw-mg-l-1 tw-c-text-alt">
|
<span v-if="val.has_crossorigin" class="tw-mg-l-1 tw-c-text-alt tw-relative ffz-il-tooltip__container">
|
||||||
|
{{ t('setting.provider.cross-origin', '(Cross-Origin Compatible)') }}
|
||||||
|
<div class="ffz-il-tooltip ffz-il-tooltip--align-center ffz-il-tooltip--up">
|
||||||
|
{{ t('setting.provider.cross-origin.about', 'This means that your settings will work across different domains, subdomains, and embeds.') }}
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
<span v-if="val.has_blobs" class="tw-mg-l-1 tw-c-text-alt tw-relative ffz-il-tooltip__container">
|
||||||
{{ t('setting.provider.has-blobs', '(Supports Binary Data)') }}
|
{{ t('setting.provider.has-blobs', '(Supports Binary Data)') }}
|
||||||
|
<div class="ffz-il-tooltip ffz-il-tooltip--align-center ffz-il-tooltip--up">
|
||||||
|
{{ t('setting.provider.has-blobs.about', 'This provider supports binary data such as custom sound files.') }}
|
||||||
|
</div>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<section v-if="val.description" class="tw-c-text-alt-2">
|
<section v-if="val.description" class="tw-c-text-alt-2">
|
||||||
|
@ -69,6 +84,17 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<section v-if="canTransfer" class="tw-c-text-alt-2 tw-pd-b-05" style="padding-left:2.5rem">
|
<section v-if="canTransfer" class="tw-c-text-alt-2 tw-pd-b-05" style="padding-left:2.5rem">
|
||||||
|
<div
|
||||||
|
v-if="selected !== current && prov_keys[selected].has_data"
|
||||||
|
class="ffz--notice tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-y-1"
|
||||||
|
>
|
||||||
|
<h3 class="ffz-i-attention ffz-font-size-3">
|
||||||
|
{{ t('setting.provider.warn.title', 'Be careful!') }}
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
<markdown :source="t('setting.provider.transfer.warning', 'The provider you selected contains data, and that data will be lost if you transfer the data from your current provider to it. Make sure you have a backup of your settings in case anything goes wrong.')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<markdown :source="t('setting.provider.transfer.desc', '**Note:** It is recommended to leave this enabled unless you know what you\'re doing.')" />
|
<markdown :source="t('setting.provider.transfer.desc', '**Note:** It is recommended to leave this enabled unless you know what you\'re doing.')" />
|
||||||
</section>
|
</section>
|
||||||
<div v-else class="tw-flex tw-align-items-center" style="padding-left:2.5rem">
|
<div v-else class="tw-flex tw-align-items-center" style="padding-left:2.5rem">
|
||||||
|
@ -110,6 +136,7 @@ export default {
|
||||||
const ffz = this.context.getFFZ(),
|
const ffz = this.context.getFFZ(),
|
||||||
settings = ffz.resolve('settings'),
|
settings = ffz.resolve('settings'),
|
||||||
providers = [],
|
providers = [],
|
||||||
|
prov_keys = {},
|
||||||
transfers = {};
|
transfers = {};
|
||||||
|
|
||||||
for(const [key, val] of Object.entries(settings.getProviders())) {
|
for(const [key, val] of Object.entries(settings.getProviders())) {
|
||||||
|
@ -117,7 +144,8 @@ export default {
|
||||||
key,
|
key,
|
||||||
priority: val.priority || 0,
|
priority: val.priority || 0,
|
||||||
has_data: null,
|
has_data: null,
|
||||||
has_blobs: val.supportsBlobs,
|
has_blobs: val.canSupportBlobs(settings),
|
||||||
|
has_crossorigin: val.crossOrigin(settings),
|
||||||
has_trans: val.allowTransfer,
|
has_trans: val.allowTransfer,
|
||||||
i18n_key: `setting.provider.${key}.title`,
|
i18n_key: `setting.provider.${key}.title`,
|
||||||
title: val.title || key,
|
title: val.title || key,
|
||||||
|
@ -125,10 +153,11 @@ export default {
|
||||||
description: val.description
|
description: val.description
|
||||||
};
|
};
|
||||||
|
|
||||||
|
prov_keys[key] = prov;
|
||||||
transfers[key] = val.allowTransfer;
|
transfers[key] = val.allowTransfer;
|
||||||
|
|
||||||
if ( val.supported() )
|
if ( val.supported(settings) )
|
||||||
Promise.resolve(val.hasContent()).then(v => {
|
Promise.resolve(val.hasContent(settings)).then(v => {
|
||||||
prov.has_data = v;
|
prov.has_data = v;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -143,6 +172,7 @@ export default {
|
||||||
backup: false,
|
backup: false,
|
||||||
not_www: window.location.host !== 'www.twitch.tv',
|
not_www: window.location.host !== 'www.twitch.tv',
|
||||||
providers,
|
providers,
|
||||||
|
prov_keys,
|
||||||
transfers,
|
transfers,
|
||||||
current,
|
current,
|
||||||
selected: current
|
selected: current
|
||||||
|
|
|
@ -19,7 +19,7 @@ import * as CLEARABLES from './clearables';
|
||||||
|
|
||||||
import type { SettingsProfileMetadata, ContextData, ExportedFullDump, SettingsClearable, SettingDefinition, SettingProcessor, SettingUiDefinition, SettingValidator, SettingType, ExportedBlobMetadata, SettingsKeys, AllSettingsKeys, ConcreteLocalStorageData } from './types';
|
import type { SettingsProfileMetadata, ContextData, ExportedFullDump, SettingsClearable, SettingDefinition, SettingProcessor, SettingUiDefinition, SettingValidator, SettingType, ExportedBlobMetadata, SettingsKeys, AllSettingsKeys, ConcreteLocalStorageData } from './types';
|
||||||
import type { FilterType } from 'utilities/filtering';
|
import type { FilterType } from 'utilities/filtering';
|
||||||
import { AdvancedSettingsProvider, IndexedDBProvider, LocalStorageProvider, Providers, SettingsProvider } from './providers';
|
import { AdvancedSettingsProvider, IGNORE_CONTENT_KEYS, LocalStorageProvider, Providers, SettingsProvider } from './providers';
|
||||||
import type { AddonInfo, SettingsTypeMap } from 'utilities/types';
|
import type { AddonInfo, SettingsTypeMap } from 'utilities/types';
|
||||||
import { FFZEvent } from '../utilities/events';
|
import { FFZEvent } from '../utilities/events';
|
||||||
|
|
||||||
|
@ -170,12 +170,17 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
|
||||||
// Load any dynamic providers that have been registered.
|
// Load any dynamic providers that have been registered.
|
||||||
// Now that we're here, no further providers can be registered, so seal them.
|
// Now that we're here, no further providers can be registered, so seal them.
|
||||||
window.ffz_providers = window.ffz_providers || [];
|
window.ffz_providers = window.ffz_providers || [];
|
||||||
|
try {
|
||||||
Object.seal(window.ffz_providers);
|
Object.seal(window.ffz_providers);
|
||||||
|
} catch(err) {
|
||||||
|
this.log.warn('Unable to seal window.ffz_providers:', err);
|
||||||
|
}
|
||||||
if ( window.ffz_providers.length > 0 ) {
|
if ( window.ffz_providers.length > 0 ) {
|
||||||
const evt = {
|
const evt = {
|
||||||
settings: this,
|
settings: this,
|
||||||
Provider: SettingsProvider,
|
Provider: SettingsProvider,
|
||||||
AdvancedProvider: AdvancedSettingsProvider,
|
AdvancedProvider: AdvancedSettingsProvider,
|
||||||
|
IGNORE_CONTENT_KEYS: IGNORE_CONTENT_KEYS,
|
||||||
registerProvider: (key: string, provider: typeof SettingsProvider) => {
|
registerProvider: (key: string, provider: typeof SettingsProvider) => {
|
||||||
if ( ! this.providers[key] && provider.supported(this) )
|
if ( ! this.providers[key] && provider.supported(this) )
|
||||||
this.providers[key] = provider;
|
this.providers[key] = provider;
|
||||||
|
@ -786,8 +791,21 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
|
||||||
((a[1] as any).priority ?? 0)
|
((a[1] as any).priority ?? 0)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Remove unsupported providers.
|
||||||
|
for(let i = providers.length - 1; i >= 0; i--) {
|
||||||
|
if ( ! providers[i][1].supported(this) )
|
||||||
|
providers.splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's a provider that has content, then use it.
|
||||||
for(const [key, provider] of providers) {
|
for(const [key, provider] of providers) {
|
||||||
if ( provider.supported(this) && await provider.hasContent(this) ) // eslint-disable-line no-await-in-loop
|
if ( await provider.hasContent(this) ) // eslint-disable-line no-await-in-loop
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the first provider that allows itself to be the default.
|
||||||
|
for(const [key, provider] of providers) {
|
||||||
|
if ( provider.allowAsDefault(this) )
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,30 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import { isValidBlob, deserializeBlob, serializeBlob, BlobLike, SerializedBlobLike } from 'utilities/blobs';
|
import { isValidBlob, deserializeBlob, serializeBlob, BlobLike, SerializedBlobLike } from 'utilities/blobs';
|
||||||
|
import { EXTENSION } from 'utilities/constants';
|
||||||
|
import {EventEmitter} from 'utilities/events';
|
||||||
|
import {TicketLock, has, once} from 'utilities/object';
|
||||||
|
import type { OptionalArray, OptionalPromise, ProviderTypeMap } from '../utilities/types';
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Settings Providers
|
// Settings Providers
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
import {EventEmitter} from 'utilities/events';
|
|
||||||
import {TicketLock, has, once} from 'utilities/object';
|
|
||||||
import type SettingsManager from '.';
|
import type SettingsManager from '.';
|
||||||
import type { OptionalArray, OptionalPromise, ProviderTypeMap } from '../utilities/types';
|
|
||||||
import { EXTENSION } from '../utilities/constants';
|
|
||||||
|
|
||||||
const DB_VERSION = 1,
|
const DB_VERSION = 1,
|
||||||
NOT_WWW_TWITCH = window.location.host !== 'www.twitch.tv',
|
NOT_WWW_TWITCH = window.location.host !== 'www.twitch.tv',
|
||||||
NOT_WWW_YT = window.location.host !== 'www.youtube.com';
|
NOT_WWW_YT = window.location.host !== 'www.youtube.com';
|
||||||
|
|
||||||
|
|
||||||
|
export const IGNORE_CONTENT_KEYS = [
|
||||||
|
'client-id',
|
||||||
|
'cfg-seen',
|
||||||
|
'cfg-collapsed'
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Types
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
@ -51,6 +58,9 @@ export abstract class SettingsProvider extends EventEmitter<ProviderEvents> {
|
||||||
static title: string;
|
static title: string;
|
||||||
static description: string;
|
static description: string;
|
||||||
|
|
||||||
|
static crossOrigin(manager: SettingsManager) { return false; }
|
||||||
|
static canSupportBlobs(manager: SettingsManager) { return false; }
|
||||||
|
|
||||||
static hasContent: (manager: SettingsManager) => OptionalPromise<boolean>;
|
static hasContent: (manager: SettingsManager) => OptionalPromise<boolean>;
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,6 +84,10 @@ export abstract class SettingsProvider extends EventEmitter<ProviderEvents> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static allowAsDefault(manager: SettingsManager) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static allowTransfer = true;
|
static allowTransfer = true;
|
||||||
static shouldUpdate = true;
|
static shouldUpdate = true;
|
||||||
|
|
||||||
|
@ -129,6 +143,8 @@ export abstract class SettingsProvider extends EventEmitter<ProviderEvents> {
|
||||||
|
|
||||||
export abstract class AdvancedSettingsProvider extends SettingsProvider {
|
export abstract class AdvancedSettingsProvider extends SettingsProvider {
|
||||||
|
|
||||||
|
static canSupportBlobs() { return true; }
|
||||||
|
|
||||||
get supportsBlobs() { return true; }
|
get supportsBlobs() { return true; }
|
||||||
|
|
||||||
isValidBlob(blob: any): blob is BlobLike {
|
isValidBlob(blob: any): blob is BlobLike {
|
||||||
|
@ -421,7 +437,7 @@ export class LocalStorageProvider extends SettingsProvider {
|
||||||
const prefix = 'FFZ:setting:';
|
const prefix = 'FFZ:setting:';
|
||||||
|
|
||||||
for(const key in localStorage)
|
for(const key in localStorage)
|
||||||
if ( key.startsWith(prefix) && has(localStorage, key) )
|
if ( key.startsWith(prefix) && ! IGNORE_CONTENT_KEYS.includes(key.slice(prefix.length)) && has(localStorage, key) )
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -695,7 +711,11 @@ export class IndexedDBProvider extends AdvancedSettingsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
r2.onsuccess = () => {
|
r2.onsuccess = () => {
|
||||||
const success = Array.isArray(r2.result) && r2.result.length > 0;
|
let success = false;
|
||||||
|
if ( Array.isArray(r2.result) && r2.result.length > 0 ) {
|
||||||
|
success = r2.result.filter(key => !IGNORE_CONTENT_KEYS.includes(key as string)).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
return resolve(success);
|
return resolve(success);
|
||||||
}
|
}
|
||||||
|
@ -1446,6 +1466,7 @@ export class CrossOriginStorageBridge extends RemoteSettingsProvider {
|
||||||
static hasContent() {
|
static hasContent() {
|
||||||
return CrossOriginStorageBridge.supported();
|
return CrossOriginStorageBridge.supported();
|
||||||
}
|
}
|
||||||
|
static allowAsDefault() { return false; }
|
||||||
|
|
||||||
static priority = 100;
|
static priority = 100;
|
||||||
static title = 'Cross-Origin Storage Bridge';
|
static title = 'Cross-Origin Storage Bridge';
|
||||||
|
|
|
@ -403,9 +403,9 @@ export default class ChatHook extends Module {
|
||||||
ui: {
|
ui: {
|
||||||
path: 'Chat > Filtering > Block >> Callout Types @{"description":"This filter allows you to remove callouts of specific types from Twitch chat. Callouts are special messages that can be pinned to the bottom of chat and often have associated actions, like claiming a drop or sharing your resubscription."}',
|
path: 'Chat > Filtering > Block >> Callout Types @{"description":"This filter allows you to remove callouts of specific types from Twitch chat. Callouts are special messages that can be pinned to the bottom of chat and often have associated actions, like claiming a drop or sharing your resubscription."}',
|
||||||
component: 'blocked-types',
|
component: 'blocked-types',
|
||||||
getExtraTerms: () => Object.keys(this.callout_types).filter(key => ! UNBLOCKABLE_CALLOUTS.includes(key)),
|
getExtraTerms: () => Object.keys(this.callout_types ?? CALLOUT_TYPES).filter(key => ! UNBLOCKABLE_CALLOUTS.includes(key)),
|
||||||
data: () => Object
|
data: () => Object
|
||||||
.keys(this.callout_types)
|
.keys(this.callout_types ?? CALLOUT_TYPES)
|
||||||
.filter(key => ! UNBLOCKABLE_CALLOUTS.includes(key))
|
.filter(key => ! UNBLOCKABLE_CALLOUTS.includes(key))
|
||||||
.sort()
|
.sort()
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ declare module 'utilities/types' {
|
||||||
'chat.hype.show-pinned': boolean;
|
'chat.hype.show-pinned': boolean;
|
||||||
'layout.turbo-cta': boolean;
|
'layout.turbo-cta': boolean;
|
||||||
'layout.subtember': boolean;
|
'layout.subtember': boolean;
|
||||||
|
'layout.side-nav.hide-stories': boolean;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +44,13 @@ type ErrorBoundaryNode = ReactStateNode<{
|
||||||
onErrorBoundaryTestEmit: any
|
onErrorBoundaryTestEmit: any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SettingToggleNode = ReactStateNode<{
|
||||||
|
name: string;
|
||||||
|
children: any;
|
||||||
|
}> & {
|
||||||
|
render: AnyFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default class Loadable extends Module {
|
export default class Loadable extends Module {
|
||||||
|
|
||||||
|
@ -53,10 +61,12 @@ export default class Loadable extends Module {
|
||||||
|
|
||||||
// State
|
// State
|
||||||
overrides: Map<string, boolean>;
|
overrides: Map<string, boolean>;
|
||||||
|
setting_overrides: Map<string, boolean>;
|
||||||
|
|
||||||
// Fine
|
// Fine
|
||||||
ErrorBoundaryComponent: FineWrapper<ErrorBoundaryNode>;
|
ErrorBoundaryComponent: FineWrapper<ErrorBoundaryNode>;
|
||||||
LoadableComponent: FineWrapper<LoadableNode>;
|
LoadableComponent: FineWrapper<LoadableNode>;
|
||||||
|
SettingsToggleComponent: FineWrapper<SettingToggleNode>;
|
||||||
|
|
||||||
constructor(name?: string, parent?: GenericModule) {
|
constructor(name?: string, parent?: GenericModule) {
|
||||||
super(name, parent);
|
super(name, parent);
|
||||||
|
@ -83,8 +93,17 @@ export default class Loadable extends Module {
|
||||||
(n as ErrorBoundaryNode).onErrorBoundaryTestEmit
|
(n as ErrorBoundaryNode).onErrorBoundaryTestEmit
|
||||||
);
|
);
|
||||||
|
|
||||||
this.overrides = new Map();
|
this.SettingsToggleComponent = this.fine.define(
|
||||||
|
'settings-toggle-component',
|
||||||
|
n =>
|
||||||
|
(n as SettingToggleNode).props?.name &&
|
||||||
|
(n as SettingToggleNode).props?.children &&
|
||||||
|
(n as SettingToggleNode).render &&
|
||||||
|
(n as SettingToggleNode).render.toString().includes('defaultThreshold')
|
||||||
|
);
|
||||||
|
|
||||||
|
this.overrides = new Map();
|
||||||
|
this.setting_overrides = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
onEnable() {
|
onEnable() {
|
||||||
|
@ -100,6 +119,10 @@ export default class Loadable extends Module {
|
||||||
this.toggle('TokenizedCommerceBanner', val);
|
this.toggle('TokenizedCommerceBanner', val);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.settings.getChanges('layout.side-nav.hide-stories', val => {
|
||||||
|
this.toggleSetting('stories_web', !val);
|
||||||
|
})
|
||||||
|
|
||||||
this.ErrorBoundaryComponent.ready((cls, instances) => {
|
this.ErrorBoundaryComponent.ready((cls, instances) => {
|
||||||
this.log.debug('Found Error Boundary component wrapper.');
|
this.log.debug('Found Error Boundary component wrapper.');
|
||||||
|
|
||||||
|
@ -122,6 +145,7 @@ export default class Loadable extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ErrorBoundaryComponent.updateInstances();
|
this.ErrorBoundaryComponent.updateInstances();
|
||||||
|
this.ErrorBoundaryComponent.forceUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.LoadableComponent.ready((cls, instances) => {
|
this.LoadableComponent.ready((cls, instances) => {
|
||||||
|
@ -167,6 +191,32 @@ export default class Loadable extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.LoadableComponent.updateInstances();
|
this.LoadableComponent.updateInstances();
|
||||||
|
this.LoadableComponent.forceUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.SettingsToggleComponent.ready((cls, instances) => {
|
||||||
|
this.log.debug('Found Settings Toggle component wrapper.');
|
||||||
|
|
||||||
|
const t = this,
|
||||||
|
proto = cls.prototype as SettingToggleNode,
|
||||||
|
old_render = proto.render;
|
||||||
|
|
||||||
|
(proto as any)._ffz_wrapped_render = old_render;
|
||||||
|
proto.render = function() {
|
||||||
|
try {
|
||||||
|
const type = this.props.name;
|
||||||
|
if ( t.setting_overrides.has(type) && ! t.shouldRenderSetting(type) )
|
||||||
|
return null;
|
||||||
|
} catch(err) {
|
||||||
|
/* no-op */
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return old_render.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.SettingsToggleComponent.updateInstances();
|
||||||
|
this.SettingsToggleComponent.forceUpdate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,6 +234,20 @@ export default class Loadable extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleSetting(cmp: string, state: boolean | null = null) {
|
||||||
|
const existing = this.setting_overrides.get(cmp) ?? true;
|
||||||
|
|
||||||
|
if ( state == null )
|
||||||
|
state = ! existing;
|
||||||
|
else
|
||||||
|
state = !! state;
|
||||||
|
|
||||||
|
if ( state !== existing ) {
|
||||||
|
this.setting_overrides.set(cmp, state);
|
||||||
|
this.updateSetting(cmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
update(cmp: string) {
|
update(cmp: string) {
|
||||||
for(const inst of this.LoadableComponent.instances) {
|
for(const inst of this.LoadableComponent.instances) {
|
||||||
const type = inst.props?.component;
|
const type = inst.props?.component;
|
||||||
|
@ -200,8 +264,21 @@ export default class Loadable extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSetting(cmp: string) {
|
||||||
|
for(const inst of this.SettingsToggleComponent.instances) {
|
||||||
|
const name = inst.props?.name;
|
||||||
|
if ( name && name === cmp ) {
|
||||||
|
inst.forceUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
shouldRender(cmp: string) {
|
shouldRender(cmp: string) {
|
||||||
return this.overrides.get(cmp) ?? true;
|
return this.overrides.get(cmp) ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
shouldRenderSetting(cmp: string) {
|
||||||
|
return this.setting_overrides.get(cmp) ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@ declare global {
|
||||||
let __extension__: string | undefined;
|
let __extension__: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether or not FrankerFaceZ was loaded from a development server. */
|
|
||||||
export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev');
|
|
||||||
|
|
||||||
/** Whether or not FrankerFaceZ was loaded as a packed web extension. */
|
/** Whether or not FrankerFaceZ was loaded as a packed web extension. */
|
||||||
export const EXTENSION = !!__extension__;
|
export const EXTENSION = !!__extension__;
|
||||||
|
|
||||||
|
/** Whether or not FrankerFaceZ was loaded from a development server. */
|
||||||
|
export const DEBUG = localStorage.ffzDebugMode === 'true' && document.body.classList.contains('ffz-dev') && !EXTENSION;
|
||||||
|
|
||||||
/** The base URL of the FrankerFaceZ CDN. */
|
/** The base URL of the FrankerFaceZ CDN. */
|
||||||
export const SERVER = DEBUG ? 'https://localhost:8000' : 'https://cdn2.frankerfacez.com';
|
export const SERVER = DEBUG ? 'https://localhost:8000' : 'https://cdn2.frankerfacez.com';
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue