mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-02 07:58:31 +00:00
4.71.0
* 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:
parent
10d35468a9
commit
a02c14d84c
10 changed files with 189 additions and 20 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.70.0",
|
"version": "4.71.0",
|
||||||
"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",
|
||||||
|
|
|
@ -1218,8 +1218,6 @@ export default class Actions extends Module {
|
||||||
if ( target._ffz_tooltip )
|
if ( target._ffz_tooltip )
|
||||||
target._ffz_tooltip.hide();
|
target._ffz_tooltip.hide();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return data.definition.click.call(this, event, data);
|
return data.definition.click.call(this, event, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
44
src/modules/chat/actions/components/edit-profile.vue
Normal file
44
src/modules/chat/actions/components/edit-profile.vue
Normal 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>
|
|
@ -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
|
// Gift Subscription
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
@ -675,4 +773,4 @@ export const gift_sub = {
|
||||||
Woop woop.
|
Woop woop.
|
||||||
</div>);
|
</div>);
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
import { DEBUG } from 'utilities/constants';
|
import { DEBUG } from 'utilities/constants';
|
||||||
import Module, { GenericModule, buildAddonProxy } from 'utilities/module';
|
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 {PathNode, parse as parse_path} from 'utilities/path-parser';
|
||||||
|
|
||||||
import SettingsProfile from './profile';
|
import SettingsProfile from './profile';
|
||||||
|
@ -132,6 +132,7 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
|
||||||
/** @internal */
|
/** @internal */
|
||||||
__profiles: SettingsProfile[];
|
__profiles: SettingsProfile[];
|
||||||
private __profile_ids: Record<number, SettingsProfile | null>;
|
private __profile_ids: Record<number, SettingsProfile | null>;
|
||||||
|
private __profile_uuids: Record<string, SettingsProfile | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not profiles have been disabled for this session
|
* 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.__contexts = [];
|
||||||
this.__profiles = [];
|
this.__profiles = [];
|
||||||
this.__profile_ids = {};
|
this.__profile_ids = {};
|
||||||
|
this.__profile_uuids = {};
|
||||||
|
|
||||||
this.ui_structures = new Map;
|
this.ui_structures = new Map;
|
||||||
this.definitions = new Map;
|
this.definitions = new Map;
|
||||||
|
@ -858,10 +860,10 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get an existing {@link SettingsProfile} instance.
|
* 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 {
|
profile(id: number | string): SettingsProfile | null {
|
||||||
return this.__profile_ids[id] ?? 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) {
|
loadProfiles(suppress_events: boolean = false) {
|
||||||
const old_profile_ids = this.__profile_ids,
|
const old_profile_ids = this.__profile_ids,
|
||||||
|
old_profile_uuids = this.__profile_uuids,
|
||||||
old_profiles = this.__profiles,
|
old_profiles = this.__profiles,
|
||||||
|
|
||||||
profile_ids: Record<number, SettingsProfile> = this.__profile_ids = {},
|
profile_ids: Record<number, SettingsProfile> = this.__profile_ids = {},
|
||||||
profiles: SettingsProfile[] = this.__profiles = [],
|
profiles: SettingsProfile[] = this.__profiles = [],
|
||||||
|
profile_uuids: Record<string, SettingsProfile> = this.__profile_uuids = {},
|
||||||
|
|
||||||
// Create a set of actual IDs with a map from the profiles
|
// Create a set of actual IDs with a map from the profiles
|
||||||
// list rather than just getting the keys from the ID map
|
// 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,
|
let reordered = false,
|
||||||
changed = false;
|
changed = false;
|
||||||
|
|
||||||
|
@ -918,23 +941,13 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
|
||||||
if ( old_slot_id !== slot_id )
|
if ( old_slot_id !== slot_id )
|
||||||
reordered = true;
|
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) ) {
|
if ( old_profile && deep_equals(old_profile.data, profile_data, true) ) {
|
||||||
// Did the order change?
|
// Did the order change?
|
||||||
if ( old_slot_id !== slot_id )
|
if ( old_slot_id !== slot_id )
|
||||||
changed = true;
|
changed = true;
|
||||||
|
|
||||||
profiles.push(profile_ids[id] = old_profile);
|
profiles.push(profile_ids[id] = old_profile);
|
||||||
|
profile_uuids[old_profile.uuid] = old_profile;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -947,6 +960,7 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
|
||||||
new_ids.add(id);
|
new_ids.add(id);
|
||||||
|
|
||||||
profiles.push(new_profile);
|
profiles.push(new_profile);
|
||||||
|
profile_uuids[new_profile.uuid] = new_profile;
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -994,11 +1008,13 @@ export default class SettingsManager extends Module<'settings', SettingsEvents>
|
||||||
}
|
}
|
||||||
|
|
||||||
options.id = id;
|
options.id = id;
|
||||||
|
options.uuid = generateUUID();
|
||||||
|
|
||||||
if ( ! options.name )
|
if ( ! options.name )
|
||||||
options.name = `Unnamed Profile ${this.__profiles.length + 1}`;
|
options.name = `Unnamed Profile ${this.__profiles.length + 1}`;
|
||||||
|
|
||||||
const profile = this.__profile_ids[id] = new SettingsProfile(this, options);
|
const profile = this.__profile_ids[id] = new SettingsProfile(this, options);
|
||||||
|
this.__profile_uuids[options.uuid] = profile;
|
||||||
this.__profiles.unshift(profile);
|
this.__profiles.unshift(profile);
|
||||||
|
|
||||||
profile.on('toggled', this._onProfileToggled, this);
|
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.off('toggled', this._onProfileToggled, this);
|
||||||
profile.clear();
|
profile.clear();
|
||||||
this.__profile_ids[id] = null;
|
this.__profile_ids[id] = null;
|
||||||
|
this.__profile_uuids[profile.uuid] = null;
|
||||||
|
|
||||||
const idx = this.__profiles.indexOf(profile);
|
const idx = this.__profiles.indexOf(profile);
|
||||||
if ( idx !== -1 )
|
if ( idx !== -1 )
|
||||||
|
|
|
@ -34,6 +34,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
|
||||||
|
|
||||||
static Default: Partial<SettingsProfileMetadata> = {
|
static Default: Partial<SettingsProfileMetadata> = {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
uuid: 'ffz_profile_default',
|
||||||
name: 'Default Profile',
|
name: 'Default Profile',
|
||||||
i18n_key: 'setting.profiles.default',
|
i18n_key: 'setting.profiles.default',
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
|
||||||
|
|
||||||
static Moderation: Partial<SettingsProfileMetadata> = {
|
static Moderation: Partial<SettingsProfileMetadata> = {
|
||||||
id: 1,
|
id: 1,
|
||||||
|
uuid: 'ffz_profile_moderation',
|
||||||
name: 'Moderation',
|
name: 'Moderation',
|
||||||
i18n_key: 'setting.profiles.moderation',
|
i18n_key: 'setting.profiles.moderation',
|
||||||
|
|
||||||
|
@ -78,6 +80,11 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
|
||||||
*/
|
*/
|
||||||
id: number = -1;
|
id: number = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The unique ID for this profile. UUIDs should always be unique.
|
||||||
|
*/
|
||||||
|
uuid: string = null as any;
|
||||||
|
|
||||||
// Metadata
|
// Metadata
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -172,6 +179,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
|
||||||
return {
|
return {
|
||||||
id: this.id,
|
id: this.id,
|
||||||
//parent: this.parent,
|
//parent: this.parent,
|
||||||
|
uuid: this.uuid,
|
||||||
|
|
||||||
name: this.name,
|
name: this.name,
|
||||||
i18n_key: this.i18n_key,
|
i18n_key: this.i18n_key,
|
||||||
|
@ -260,6 +268,7 @@ export default class SettingsProfile extends EventEmitter<ProfileEvents> {
|
||||||
// We don't want to override general settings.
|
// We don't want to override general settings.
|
||||||
delete data.profile.ephemeral;
|
delete data.profile.ephemeral;
|
||||||
delete data.profile.id;
|
delete data.profile.id;
|
||||||
|
delete data.profile.uuid;
|
||||||
delete data.profile.name;
|
delete data.profile.name;
|
||||||
delete data.profile.i18n_key;
|
delete data.profile.i18n_key;
|
||||||
delete data.profile.hotkey;
|
delete data.profile.hotkey;
|
||||||
|
|
|
@ -280,6 +280,7 @@ export type ExportedBlobMetadata = {
|
||||||
|
|
||||||
export type SettingsProfileMetadata = {
|
export type SettingsProfileMetadata = {
|
||||||
id: number;
|
id: number;
|
||||||
|
uuid: string;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
i18n_key?: string | null;
|
i18n_key?: string | null;
|
||||||
|
|
|
@ -833,6 +833,7 @@ export default class Input extends Module {
|
||||||
out.push({
|
out.push({
|
||||||
current: input,
|
current: input,
|
||||||
replacement: inst.determineReplacement(cmd),
|
replacement: inst.determineReplacement(cmd),
|
||||||
|
description: cmd.description,
|
||||||
element: inst.renderCommandSuggestion(cmd, i),
|
element: inst.renderCommandSuggestion(cmd, i),
|
||||||
group: cmd.ffz_group ?
|
group: cmd.ffz_group ?
|
||||||
(Array.isArray(cmd.ffz_group) ? t.i18n.t(...cmd.ffz_group) : cmd.ffz_group)
|
(Array.isArray(cmd.ffz_group) ? t.i18n.t(...cmd.ffz_group) : cmd.ffz_group)
|
||||||
|
|
|
@ -108,6 +108,7 @@ export default class ChatLine extends Module {
|
||||||
|
|
||||||
if ( data.tokenize ) {
|
if ( data.tokenize ) {
|
||||||
const tokens = data.ffz_tokens = data.ffz_tokens || this.chat.tokenizeMessage({
|
const tokens = data.ffz_tokens = data.ffz_tokens || this.chat.tokenizeMessage({
|
||||||
|
badges: {},
|
||||||
message: text,
|
message: text,
|
||||||
id: msg.id,
|
id: msg.id,
|
||||||
user: msg.user,
|
user: msg.user,
|
||||||
|
|
|
@ -64,7 +64,7 @@ export function isValidShortcut(key: string) {
|
||||||
*/
|
*/
|
||||||
export const generateUUID = crypto.randomUUID
|
export const generateUUID = crypto.randomUUID
|
||||||
? () => crypto.randomUUID()
|
? () => crypto.randomUUID()
|
||||||
: function generateUUID(input?: any) {
|
: function generateUUID(input?: any): string {
|
||||||
return input // if the placeholder was passed, return
|
return input // if the placeholder was passed, return
|
||||||
? ( // a random number from 0 to 15
|
? ( // a random number from 0 to 15
|
||||||
input ^ // unless b is 8,
|
input ^ // unless b is 8,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue