1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* Added: Chat action to toggle a settings profile, for convenience.
* Fixed: Missing message data in rich notices causing messages to not appear correctly when certain add-ons were enabled. Closes #1475
* API Changed: SettingsProfiles now have UUIDs that are stable and not reused, allowing things like chat actions to uniquely identify profiles.
This commit is contained in:
SirStendec 2024-03-21 15:44:11 -04:00
parent 10d35468a9
commit a02c14d84c
10 changed files with 189 additions and 20 deletions

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.70.0",
"version": "4.71.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"private": true,
"license": "Apache-2.0",

View file

@ -1218,8 +1218,6 @@ export default class Actions extends Module {
if ( target._ffz_tooltip )
target._ffz_tooltip.hide();
return data.definition.click.call(this, event, data);
}

View file

@ -0,0 +1,44 @@
<template lang="html">
<div>
<div class="tw-flex tw-align-items-center">
<label for="edit_profile">
{{ t('setting.actions.profile', 'Profile') }}
</label>
<select
id="edit_profile"
v-model.trim="value.uuid"
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
@input="$emit('input', value)"
>
<option
v-for="profile in profiles"
:key="profile.value"
:value="profile.value"
>{{ profile.i18n_key ? t(profile.i18n_key, profile.name) : profile.name }}</option>
</select>
</div>
</div>
</template>
<script>
export default {
props: ['value', 'defaults'],
data() {
const ffz = window.FrankerFaceZ.get(),
settings = ffz?.resolve('settings'),
profiles = settings?.__profiles;
return {
profiles: profiles ? profiles.map(prof => ({
value: prof.uuid,
name: prof.name,
i18n_key: prof.i18n_key
})) : []
}
}
}
</script>

View file

@ -650,6 +650,104 @@ export const whisper = {
}
// ============================================================================
// Toggle Profile
// ============================================================================
export const toggle_profile = {
presets: [{
appearance: {
type: 'dynamic'
}
}],
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/edit-profile.vue'),
required_context: [],
supports_dynamic: true,
can_self: true,
title: 'Toggle Settings Profile',
description(data) {
const ffz = window.FrankerFaceZ.get(),
settings = ffz?.resolve('settings'),
profile = settings?.profile(data.options?.uuid);
if ( ! profile )
return null;
return profile.i18n_key ? this.t(profile.i18n_key, profile.name) : profile.name;
},
description_i18n: null,
dynamicAppearance(ap, data) {
const profile = this.settings.profile(data.options?.uuid);
if ( ! profile )
return {
type: 'icon',
icon: 'ffz-i-attention',
color: ap.color
};
return {
type: 'icon',
icon: profile.toggled ? 'ffz-i-ok' : 'ffz-i-cancel',
color: ap.color
}
},
tooltip(data) {
const profile = this.settings.profile(data.options?.uuid);
let name;
if ( ! profile )
name = 'invalid';
else
name = profile.i18n_key ? this.i18n.t(profile.i18n_key, profile.name) : profile.name;
return this.i18n.t('chat.actions.toggle_profile.tooltip', 'Toggle Profile: {name}', {
name
});
},
click(event, data) {
const profile = this.settings.profile(data.options?.uuid);
if ( ! profile )
return;
// TODO: Make dynamic appearance things update automatically so
// we don't need this hack.
// Find any setting using this.
const found = new Set;
for(const ctx of ['inline', 'hover', 'user-context', 'room']) {
const key = `chat.actions.${ctx}`,
stuff = this.parent.context.get(key);
if ( Array.isArray(stuff) )
for(const item of stuff) {
if ( item.action === 'toggle_profile' ) {
if ( item.options?.uuid === profile.uuid ) {
found.add(ctx);
this.parent.context.once(`changed:${key}`, () => found.delete(ctx));
break;
}
}
}
}
// Toggle the profile.
profile.toggled = ! profile.toggled;
// Now wait, and update any setting that didn't update.
requestAnimationFrame(() => {
for(const ctx of found) {
this.parent.context.update(`chat.actions.${ctx}`);
}
});
}
}
// ============================================================================
// Gift Subscription
// ============================================================================
@ -675,4 +773,4 @@ export const gift_sub = {
Woop woop.
</div>);
}
}*/
}*/

View file

@ -6,7 +6,7 @@
import { DEBUG } from 'utilities/constants';
import Module, { GenericModule, buildAddonProxy } from 'utilities/module';
import {deep_equals, has, debounce, deep_copy} from 'utilities/object';
import {deep_equals, has, debounce, deep_copy, generateUUID} from 'utilities/object';
import {PathNode, parse as parse_path} from 'utilities/path-parser';
import SettingsProfile from './profile';
@ -132,6 +132,7 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
/** @internal */
__profiles: SettingsProfile[];
private __profile_ids: Record<number, SettingsProfile | null>;
private __profile_uuids: Record<string, SettingsProfile | null>;
/**
* Whether or not profiles have been disabled for this session
@ -175,6 +176,7 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
this.__contexts = [];
this.__profiles = [];
this.__profile_ids = {};
this.__profile_uuids = {};
this.ui_structures = new Map;
this.definitions = new Map;
@ -858,10 +860,10 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
/**
* Get an existing {@link SettingsProfile} instance.
* @param {number} id - The id of the profile.
* @param id - The id or uuid of the profile.
*/
profile(id: number): SettingsProfile | null {
return this.__profile_ids[id] ?? null;
profile(id: number | string): SettingsProfile | null {
return this.__profile_uuids[id] ?? this.__profile_ids[id as number] ?? null;
}
@ -871,10 +873,12 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
*/
loadProfiles(suppress_events: boolean = false) {
const old_profile_ids = this.__profile_ids,
old_profile_uuids = this.__profile_uuids,
old_profiles = this.__profiles,
profile_ids: Record<number, SettingsProfile> = this.__profile_ids = {},
profiles: SettingsProfile[] = this.__profiles = [],
profile_uuids: Record<string, SettingsProfile> = this.__profile_uuids = {},
// Create a set of actual IDs with a map from the profiles
// list rather than just getting the keys from the ID map
@ -899,6 +903,25 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
];
}
// Update: Add UUIDs to all profiles.
let need_save = false;
for(const profile of raw_profiles) {
if ( ! profile.uuid ) {
need_save = true;
if ( profile.i18n_key === SettingsProfile.Default.i18n_key )
profile.uuid = SettingsProfile.Default.uuid as string;
else if ( profile.i18n_key === SettingsProfile.Moderation.i18n_key )
profile.uuid = SettingsProfile.Moderation.uuid as string;
else
profile.uuid = generateUUID();
}
}
if ( need_save )
this.provider?.set('profiles', raw_profiles);
let reordered = false,
changed = false;
@ -918,23 +941,13 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
if ( old_slot_id !== slot_id )
reordered = true;
// Monkey patch to the new profile format...
// Update: Probably safe to remove this, at this point.
/*
if ( profile_data.context && ! Array.isArray(profile_data.context) ) {
if ( profile_data.context.moderator )
profile_data.context = SettingsProfile.Moderation.context;
else
profile_data.context = null;
}
*/
if ( old_profile && deep_equals(old_profile.data, profile_data, true) ) {
// Did the order change?
if ( old_slot_id !== slot_id )
changed = true;
profiles.push(profile_ids[id] = old_profile);
profile_uuids[old_profile.uuid] = old_profile;
continue;
}
@ -947,6 +960,7 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
new_ids.add(id);
profiles.push(new_profile);
profile_uuids[new_profile.uuid] = new_profile;
changed = true;
}
@ -994,11 +1008,13 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
}
options.id = id;
options.uuid = generateUUID();
if ( ! options.name )
options.name = `Unnamed Profile ${this.__profiles.length + 1}`;
const profile = this.__profile_ids[id] = new SettingsProfile(this, options);
this.__profile_uuids[options.uuid] = profile;
this.__profiles.unshift(profile);
profile.on('toggled', this._onProfileToggled, this);
@ -1040,6 +1056,7 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
profile.off('toggled', this._onProfileToggled, this);
profile.clear();
this.__profile_ids[id] = null;
this.__profile_uuids[profile.uuid] = null;
const idx = this.__profiles.indexOf(profile);
if ( idx !== -1 )

View file

@ -34,6 +34,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
static Default: Partial<SettingsProfileMetadata> = {
id: 0,
uuid: 'ffz_profile_default',
name: 'Default Profile',
i18n_key: 'setting.profiles.default',
@ -43,6 +44,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
static Moderation: Partial<SettingsProfileMetadata> = {
id: 1,
uuid: 'ffz_profile_moderation',
name: 'Moderation',
i18n_key: 'setting.profiles.moderation',
@ -78,6 +80,11 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
*/
id: number = -1;
/**
* The unique ID for this profile. UUIDs should always be unique.
*/
uuid: string = null as any;
// Metadata
/**
@ -172,6 +179,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
return {
id: this.id,
//parent: this.parent,
uuid: this.uuid,
name: this.name,
i18n_key: this.i18n_key,
@ -260,6 +268,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
// We don't want to override general settings.
delete data.profile.ephemeral;
delete data.profile.id;
delete data.profile.uuid;
delete data.profile.name;
delete data.profile.i18n_key;
delete data.profile.hotkey;

View file

@ -280,6 +280,7 @@ export type ExportedBlobMetadata = {
export type SettingsProfileMetadata = {
id: number;
uuid: string;
name: string;
i18n_key?: string | null;

View file

@ -833,6 +833,7 @@ export default class Input extends Module {
out.push({
current: input,
replacement: inst.determineReplacement(cmd),
description: cmd.description,
element: inst.renderCommandSuggestion(cmd, i),
group: cmd.ffz_group ?
(Array.isArray(cmd.ffz_group) ? t.i18n.t(...cmd.ffz_group) : cmd.ffz_group)

View file

@ -108,6 +108,7 @@ export default class ChatLine extends Module {
if ( data.tokenize ) {
const tokens = data.ffz_tokens = data.ffz_tokens || this.chat.tokenizeMessage({
badges: {},
message: text,
id: msg.id,
user: msg.user,

View file

@ -64,7 +64,7 @@ export function isValidShortcut(key: string) {
*/
export const generateUUID = crypto.randomUUID
? () => crypto.randomUUID()
: function generateUUID(input?: any) {
: function generateUUID(input?: any): string {
return input // if the placeholder was passed, return
? ( // a random number from 0 to 15
input ^ // unless b is 8,