mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-11 00:20:54 +00:00
4.20.54
* Added: New UI for clearing FrankerFaceZ settings (Data Management > Storage). * Fixed: Do not display duplicate bot badges in tool-tips when a bot has both a global bot badge and a channel-specific bot badge. * Fixed: Do not add click URLs for FFZ badges to add-on badges. * Fixed: Remove `debugger;` from automatic error reporting method. * Fixed: IndexedDBProvider not synchronizing settings correctly. * Fixed: Change a CSS class name used when changing emote visibility to avoid bad UX due to misbehaving third-party extensions. * Fixed: Color calculations for chat messages, including highlight colors. (Closes #947) * Changed: Use rounded images from the CDN for FFZ badges in tool-tips. * Changed: Use Twitch IDs rather than usernames for assigning channel-specific badges to users. * API Added: `settings.addClearable(key, definition)` for adding new UI to `Data Management > Storage >> Clear`.
This commit is contained in:
parent
7adee6556c
commit
ef4ff0c13a
15 changed files with 394 additions and 27 deletions
|
@ -493,8 +493,6 @@ export default class Badges extends Module {
|
|||
}
|
||||
}
|
||||
|
||||
this.log.info('badge-click', event.target);
|
||||
|
||||
if ( url ) {
|
||||
const link = createElement('a', {
|
||||
target: '_blank',
|
||||
|
@ -586,8 +584,15 @@ export default class Badges extends Module {
|
|||
};
|
||||
}
|
||||
|
||||
const handled_ids = new Set;
|
||||
|
||||
for(const badge of badges)
|
||||
if ( badge && badge.id != null ) {
|
||||
if ( handled_ids.has(badge.id) )
|
||||
continue;
|
||||
|
||||
handled_ids.add(badge.id);
|
||||
|
||||
const full_badge = this.badges[badge.id] || {},
|
||||
is_hidden = hidden_badges[badge.id];
|
||||
|
||||
|
@ -609,6 +614,12 @@ export default class Badges extends Module {
|
|||
title: badge.title || full_badge.title
|
||||
};
|
||||
|
||||
// Hacky nonsense.
|
||||
if ( ! full_badge.addon ) {
|
||||
bd.image = `//cdn.frankerfacez.com/badge/${badge.id}/4/rounded`;
|
||||
bd.color = null;
|
||||
}
|
||||
|
||||
let style;
|
||||
|
||||
if ( old_badge ) {
|
||||
|
@ -821,7 +832,7 @@ export default class Badges extends Module {
|
|||
data.replaces = true;
|
||||
}
|
||||
|
||||
if ( data.name === 'developer' || data.name === 'supporter' )
|
||||
if ( ! data.addon && (data.name === 'developer' || data.name === 'supporter') )
|
||||
data.click_url = 'https://www.frankerfacez.com/donate';
|
||||
|
||||
if ( generate_css )
|
||||
|
|
|
@ -276,6 +276,13 @@ export default class Room {
|
|||
return false;
|
||||
}
|
||||
|
||||
const old_badges = this.data?.user_badge_ids;
|
||||
if ( old_badges )
|
||||
for(const badge_id in old_badges )
|
||||
if ( has(old_badges, badge_id) )
|
||||
for(const user of old_badges[badge_id])
|
||||
this.getUser(user, undefined).removeBadge('ffz', badge_id);
|
||||
|
||||
const d = data.room,
|
||||
id = `${d.twitch_id}`;
|
||||
|
||||
|
@ -306,14 +313,12 @@ export default class Room {
|
|||
if ( has(data.sets, set_id) )
|
||||
this.manager.emotes.loadSetData(set_id, data.sets[set_id]);
|
||||
|
||||
|
||||
const badges = d.user_badges;
|
||||
const badges = d.user_badge_ids;
|
||||
if ( badges )
|
||||
for(const badge_id in badges)
|
||||
if ( has(badges, badge_id) )
|
||||
for(const user of badges[badge_id])
|
||||
this.getUser(undefined, user).addBadge('ffz', badge_id);
|
||||
|
||||
this.getUser(user, undefined).addBadge('ffz', badge_id);
|
||||
|
||||
if ( d.css )
|
||||
this.style.set('css', d.css);
|
||||
|
|
216
src/modules/main_menu/components/clear-settings.vue
Normal file
216
src/modules/main_menu/components/clear-settings.vue
Normal file
|
@ -0,0 +1,216 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--clear-settings tw-pd-t-05">
|
||||
<div class="tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-b-2">
|
||||
<h3 class="ffz-i-attention">
|
||||
{{ t('setting.clear.warning', 'Be careful! This is permanent.') }}
|
||||
</h3>
|
||||
<markdown :source="t('setting.clear.warning-explain', 'Deleting your data with this tool cannot be reversed. Make sure you have a backup!')" />
|
||||
</div>
|
||||
|
||||
<div v-if="! started">
|
||||
<div class="tw-mg-b-1">
|
||||
{{ t('setting.clear.step-1', 'Please select which types of data you wish to clear:') }}
|
||||
</div>
|
||||
|
||||
<div class="tw-mg-l-5">
|
||||
<div
|
||||
v-for="(type, key) in types"
|
||||
:key="key"
|
||||
class="tw-checkbox tw-relative tw-mg-y-05"
|
||||
>
|
||||
<input
|
||||
:id="key"
|
||||
:ref="key"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
>
|
||||
|
||||
<label :for="key" class="tw-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t(`setting.clear.opt.${key}`, type.label || key) }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tw-mg-t-1 tw-border-t tw-pd-t-1 tw-mg-b-1">
|
||||
<markdown :source="t('setting.clear.step-2', 'Are you really sure? Please enter `{code}` in the text box below to confirm.', {code})" />
|
||||
</div>
|
||||
|
||||
<div class="tw-mg-l-5">
|
||||
<input
|
||||
v-model="entered"
|
||||
type="text"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-input tw-pd-x-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="tw-mg-t-1 tw-border-t tw-pd-t-1 tw-mg-b-1">
|
||||
<div class="tw-mg-l-5">
|
||||
<button
|
||||
class="tw-button"
|
||||
:class="{'tw-button--disabled': ! enabled}"
|
||||
@click="clear"
|
||||
>
|
||||
<span class="tw-button__icon tw-button__icon--left">
|
||||
<figure class="ffz-i-trash" />
|
||||
</span>
|
||||
<span class="tw-button__text">
|
||||
{{ t('setting.clear.start', 'Clear My Data') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="started && running">
|
||||
{{ t('setting.clear.running', 'Clearing settings. Please wait...') }}
|
||||
</div>
|
||||
<div v-if="started && ! running">
|
||||
{{ t('setting.clear.done', 'Your settings have been cleared. Please refresh any applicable Twitch pages to ensure no cached data remains.') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {generateHex} from 'utilities/object';
|
||||
import { maybe_call } from 'src/utilities/object';
|
||||
|
||||
|
||||
export default {
|
||||
props: ['item', 'context'],
|
||||
|
||||
data() {
|
||||
const ffz = this.context.getFFZ(),
|
||||
settings = ffz.resolve('settings');
|
||||
|
||||
return {
|
||||
types: settings.getClearables(),
|
||||
entered: '',
|
||||
code: generateHex(8),
|
||||
|
||||
started: false,
|
||||
running: false,
|
||||
|
||||
message: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
enabled() {
|
||||
return this.code === this.entered
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
async clear() {
|
||||
if ( ! this.enabled )
|
||||
return;
|
||||
|
||||
this.started = true;
|
||||
this.running = true;
|
||||
|
||||
const ffz = this.context.getFFZ(),
|
||||
settings = ffz.resolve('settings'),
|
||||
provider = settings.provider;
|
||||
|
||||
for(const [key, type] of Object.entries(this.types)) {
|
||||
if ( ! this.$refs[key]?.[0]?.checked )
|
||||
continue;
|
||||
|
||||
if ( type.clear )
|
||||
await type.clear.call(this, provider, settings); // eslint-disable-line no-await-in-loop
|
||||
else {
|
||||
let keys = maybe_call(type.keys, this, provider, settings);
|
||||
if ( keys instanceof Promise )
|
||||
keys = await keys; // eslint-disable-line no-await-in-loop
|
||||
|
||||
if ( Array.isArray(keys) )
|
||||
for(const key of keys)
|
||||
provider.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
this.running = false;
|
||||
},
|
||||
|
||||
async backup() {
|
||||
this.error = false;
|
||||
this.message = null;
|
||||
|
||||
let blob;
|
||||
try {
|
||||
const settings = this.item.getFFZ().resolve('settings'),
|
||||
data = await settings.getFullBackup();
|
||||
blob = new Blob([JSON.stringify(data)], {type: 'application/json;charset=utf-8'});
|
||||
} catch(err) {
|
||||
this.error_desc = this.t('setting.backup-restore.dump-error', 'Unable to export settings data to JSON.');
|
||||
this.error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
saveAs(blob, 'ffz-settings.json');
|
||||
} catch(err) {
|
||||
this.error_desc = this.t('setting.backup-restore.save-error', 'Unable to save.');
|
||||
}
|
||||
},
|
||||
|
||||
async restore() {
|
||||
this.error = false;
|
||||
this.message = null;
|
||||
|
||||
let contents;
|
||||
try {
|
||||
contents = await readFile(await openFile('application/json'));
|
||||
} catch(err) {
|
||||
this.error_desc = this.t('setting.backup-restore.read-error', 'Unable to read file.');
|
||||
this.error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(contents);
|
||||
} catch(err) {
|
||||
this.error_desc = this.t('setting.backup-restore.json-error', 'Unable to parse file as JSON.');
|
||||
this.error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! data || data.version !== 2 ) {
|
||||
this.error_desc = this.t('setting.backup-restore.old-file', 'This file is invalid or was created in another version of FrankerFaceZ and cannot be loaded.');
|
||||
this.error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( data.type !== 'full' ) {
|
||||
this.error_desc = this.t('setting.backup-restore.non-full', 'This file is not a full backup and cannot be restored with this tool.');
|
||||
this.error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const settings = this.item.getFFZ().resolve('settings'),
|
||||
provider = settings.provider;
|
||||
|
||||
await provider.awaitReady();
|
||||
|
||||
provider.clear();
|
||||
let i = 0;
|
||||
for(const key of Object.keys(data.values)) {
|
||||
const val = data.values[key];
|
||||
provider.set(key, val);
|
||||
provider.emit('changed', key, val, false);
|
||||
i++;
|
||||
}
|
||||
|
||||
this.message = this.t('setting.backup-restore.restored', '{count,number} items have been restored. Please refresh this page.', {
|
||||
count: i
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -60,6 +60,12 @@ export default class MainMenu extends Module {
|
|||
getFFZ: () => this.resolve('core')
|
||||
});
|
||||
|
||||
this.settings.addUI('clear', {
|
||||
path: 'Data Management > Storage @{"profile_warning": false} >> tabs ~> Clear',
|
||||
component: 'clear-settings',
|
||||
force_seen: true
|
||||
});
|
||||
|
||||
this.settings.addUI('home', {
|
||||
path: 'Home @{"sort": -1000, "profile_warning": false}',
|
||||
component: 'home-page'
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue