mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-07 03:28:31 +00:00
4.20.2
* Added: Option to open the emote menu with Ctrl+E, similar to how Discord's works. * Added: Warning to the Debugging > Experiments list that they are not intended for end-users. * Changed: Rewrote tab-completion of emotes to fully respect hidden emotes from the emote menu and to cache more useful data. * Changed: The Experiments list now hides inactive experiments by default. * Fixed: Updated the Host button's menu to restore functionality after Twitch removed the old API. * Fixed: Some avatar images remaining rounded despite the square avatars setting.
This commit is contained in:
parent
fa5333d462
commit
339b6fdfbb
15 changed files with 490 additions and 212 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.20.1",
|
"version": "4.20.2",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -60,6 +60,9 @@ export default class ExperimentManager extends Module {
|
||||||
return values;
|
return values;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
is_locked: () => this.getControlsLocked(),
|
||||||
|
unlock: () => this.unlockControls(),
|
||||||
|
|
||||||
unique_id: () => this.unique_id,
|
unique_id: () => this.unique_id,
|
||||||
|
|
||||||
ffz_data: () => deep_copy(this.experiments),
|
ffz_data: () => deep_copy(this.experiments),
|
||||||
|
@ -88,6 +91,21 @@ export default class ExperimentManager extends Module {
|
||||||
this.cache = new Map;
|
this.cache = new Map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getControlsLocked() {
|
||||||
|
if ( DEBUG )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const ts = this.settings.provider.get('exp-lock', 0);
|
||||||
|
if ( isNaN(ts) || ! isFinite(ts) )
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return Date.now() - ts >= 86400000;
|
||||||
|
}
|
||||||
|
|
||||||
|
unlockControls() {
|
||||||
|
this.settings.provider.set('exp-lock', Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
await this.loadExperiments();
|
await this.loadExperiments();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
"name": "New API Stress Testing",
|
"name": "New API Stress Testing",
|
||||||
"description": "Send duplicate requests to the new API server for load testing.",
|
"description": "Send duplicate requests to the new API server for load testing.",
|
||||||
"groups": [
|
"groups": [
|
||||||
{"value": true, "weight": 25},
|
{"value": true, "weight": 0},
|
||||||
{"value": false, "weight": 75}
|
{"value": false, "weight": 100}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,155 +4,211 @@
|
||||||
{{ t('setting.experiments.about', 'This feature allows you to override experiment values. Please note that, for most experiments, you may have to refresh the page for your changes to take effect.') }}
|
{{ t('setting.experiments.about', 'This feature allows you to override experiment values. Please note that, for most experiments, you may have to refresh the page for your changes to take effect.') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
<section v-if="experiments_locked">
|
||||||
<div class="tw-flex-grow-1">
|
<div class="tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-b-2">
|
||||||
{{ t('setting.experiments.unique-id', 'Unique ID: {id}', {id: unique_id}) }}
|
<h3 class="ffz-i-attention">
|
||||||
|
{{ t('setting.dev-warning', "It's dangerous to go at all.") }}
|
||||||
|
</h3>
|
||||||
|
<markdown :source="t('setting.dev-warning.explain', 'Be careful, this is an advanced feature intended for developer use only. Normal users should steer clear. Adjusting your experiments can have unexpected impacts on your Twitch experience. FrankerFaceZ is not responsible for any issues you encounter as a result of tampering with experiments, and we will not provide support.\n\nIf you\'re sure about this, please type `sv_cheats 1` into the box below and hit enter.')" />
|
||||||
</div>
|
</div>
|
||||||
<select
|
|
||||||
ref="sort_select"
|
|
||||||
class="tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
|
||||||
@change="onSort"
|
|
||||||
>
|
|
||||||
<option :selected="sort_by === 0">
|
|
||||||
{{ t('setting.experiments.sort-name', 'Sort By: Name') }}
|
|
||||||
</option>
|
|
||||||
<option :selected="sort_by === 1">
|
|
||||||
{{ t('setting.experiments.sort-rarity', 'Sort By: Rarity') }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="tw-mg-b-1">
|
<div class="tw-flex tw-align-items-center">
|
||||||
{{ t('setting.experiments.ffz', 'FrankerFaceZ Experiments') }}
|
<input
|
||||||
</h3>
|
ref="code"
|
||||||
|
type="text"
|
||||||
<div class="ffz--experiment-list">
|
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05"
|
||||||
<section
|
autocapitalize="off"
|
||||||
v-for="({key, exp}) of visible_ffz"
|
autocorrect="off"
|
||||||
:key="key"
|
@keydown.enter="enterCode"
|
||||||
:data-key="key"
|
|
||||||
>
|
|
||||||
<div class="tw-elevation-1 tw-c-background-base tw-border tw-pd-y-05 tw-pd-x-1 tw-mg-y-05 tw-flex tw-flex-nowrap">
|
|
||||||
<div class="tw-flex-grow-1">
|
|
||||||
<h4>{{ exp.name }}</h4>
|
|
||||||
<div v-if="exp.description" class="description">
|
|
||||||
{{ exp.description }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tw-flex tw-flex-shrink-0 tw-align-items-start">
|
|
||||||
<select
|
|
||||||
:data-key="key"
|
|
||||||
class="tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
|
||||||
@change="onChange($event)"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-for="(i, idx) in exp.groups"
|
|
||||||
:key="idx"
|
|
||||||
:selected="i.value === exp.value"
|
|
||||||
>
|
|
||||||
{{ t('setting.experiments.entry', '{value,tostring} (weight: {weight,tostring})', i) }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button
|
|
||||||
:disabled="exp.default"
|
|
||||||
:class="{'tw-button--disabled': exp.default}"
|
|
||||||
class="tw-mg-t-05 tw-button tw-button--text tw-tooltip-wrapper"
|
|
||||||
@click="reset(key)"
|
|
||||||
>
|
|
||||||
<span class="tw-button__text ffz-i-cancel" />
|
|
||||||
<span class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
|
||||||
{{ t('setting.reset', 'Reset to Default') }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<div v-if="! Object.keys(ffz_data).length">
|
|
||||||
{{ t('setting.experiments.none', 'There are no current experiments.') }}
|
|
||||||
</div>
|
|
||||||
<div v-else-if="! visible_ffz.length">
|
|
||||||
{{ t('setting.experiments.none-filter', 'There are no matching experiments.') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h3 class="tw-mg-t-5 tw-mg-b-1">
|
|
||||||
{{ t('setting.experiments.twitch', 'Twitch Experiments') }}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<div class="ffz--experiment-list">
|
|
||||||
<section
|
|
||||||
v-for="({key, exp}) of visible_twitch"
|
|
||||||
:key="key"
|
|
||||||
:data-key="key"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
:class="{live: exp.in_use}"
|
|
||||||
class="ffz--experiment-row tw-elevation-1 tw-c-background-base tw-border tw-pd-y-05 tw-pd-x-1 tw-mg-y-05 tw-flex"
|
|
||||||
>
|
>
|
||||||
<div class="tw-flex tw-flex-shrink-0 tw-align-items-center tw-border-r tw-mg-r-1 tw-pd-r-1">
|
</div>
|
||||||
<div v-if="exp.in_use" class="ffz--profile__icon ffz-i-ok tw-tooltip-wrapper">
|
</section>
|
||||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-left">
|
|
||||||
{{ t('setting.experiments.active', 'This experiment is active.') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="ffz--profile__icon ffz-i-cancel tw-tooltip-wrapper">
|
|
||||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-left">
|
|
||||||
{{ t('setting.experiments.inactive', 'This experiment is not active.') }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tw-flex-grow-1">
|
<section v-else>
|
||||||
<h4>{{ exp.name }}</h4>
|
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||||
<div class="description">
|
<div class="tw-flex-grow-1">
|
||||||
{{ exp.remainder }}
|
{{ t('setting.experiments.unique-id', 'Unique ID: {id}', {id: unique_id}) }}
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tw-flex tw-flex-shrink-0 tw-align-items-start">
|
|
||||||
<select
|
|
||||||
:data-key="key"
|
|
||||||
class="tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
|
||||||
@change="onTwitchChange($event)"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
v-if="exp.in_use === false"
|
|
||||||
:selected="exp.default"
|
|
||||||
>
|
|
||||||
{{ t('setting.experiments.unset', 'unset') }}
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
v-for="(i, idx) in exp.groups"
|
|
||||||
:key="idx"
|
|
||||||
:selected="i.value === exp.value"
|
|
||||||
>
|
|
||||||
{{ t('setting.experiments.entry', '{value,tostring} (weight: {weight,tostring})', i) }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<button
|
|
||||||
:disabled="exp.default"
|
|
||||||
:class="{'tw-button--disabled': exp.default}"
|
|
||||||
class="tw-mg-t-05 tw-button tw-button--text tw-tooltip-wrapper"
|
|
||||||
@click="resetTwitch(key)"
|
|
||||||
>
|
|
||||||
<span class="tw-button__text ffz-i-cancel" />
|
|
||||||
<span class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
|
||||||
{{ t('setting.reset', 'Reset to Default') }}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<select
|
||||||
<div v-if="! Object.keys(twitch_data).length">
|
ref="sort_select"
|
||||||
{{ t('setting.experiments.none', 'There are no current experiments.') }}
|
class="tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||||
|
@change="onSort"
|
||||||
|
>
|
||||||
|
<option :selected="sort_by === 0">
|
||||||
|
{{ t('setting.experiments.sort-name', 'Sort By: Name') }}
|
||||||
|
</option>
|
||||||
|
<option :selected="sort_by === 1">
|
||||||
|
{{ t('setting.experiments.sort-rarity', 'Sort By: Rarity') }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="! visible_twitch.length">
|
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||||
{{ t('setting.experiments.none-filter', 'There are no matching experiments.') }}
|
<div class="tw-flex-grow-1" />
|
||||||
|
<div class="tw-checkbox tw-relative">
|
||||||
|
<input
|
||||||
|
id="unused"
|
||||||
|
ref="unused"
|
||||||
|
v-model="unused"
|
||||||
|
type="checkbox"
|
||||||
|
class="tw-checkbox__input"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label for="unused" class="tw-checkbox__label">
|
||||||
|
<span class="tw-mg-l-1">
|
||||||
|
{{ t('setting.experiments.show-unused', 'Display unused experiments.') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
<h3 class="tw-mg-b-1">
|
||||||
|
<span>
|
||||||
|
{{ t('setting.experiments.ffz', 'FrankerFaceZ Experiments') }}
|
||||||
|
</span>
|
||||||
|
<span v-if="filter" class="tw-mg-l-1 tw-font-size-base tw-regular tw-c-text-alt-2">
|
||||||
|
{{ t('setting.experiments.visible', '(Showing {visible,number} of {total,number})', {
|
||||||
|
visible: visible_ffz.length,
|
||||||
|
total: sorted_ffz.length
|
||||||
|
}) }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="ffz--experiment-list">
|
||||||
|
<section
|
||||||
|
v-for="({key, exp}) of visible_ffz"
|
||||||
|
:key="key"
|
||||||
|
:data-key="key"
|
||||||
|
>
|
||||||
|
<div class="tw-elevation-1 tw-c-background-base tw-border tw-pd-y-05 tw-pd-x-1 tw-mg-y-05 tw-flex tw-flex-nowrap">
|
||||||
|
<div class="tw-flex-grow-1">
|
||||||
|
<h4>{{ exp.name }}</h4>
|
||||||
|
<div v-if="exp.description" class="description">
|
||||||
|
{{ exp.description }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-flex-shrink-0 tw-align-items-start">
|
||||||
|
<select
|
||||||
|
:data-key="key"
|
||||||
|
class="tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||||
|
@change="onChange($event)"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="(i, idx) in exp.groups"
|
||||||
|
:key="idx"
|
||||||
|
:selected="i.value === exp.value"
|
||||||
|
>
|
||||||
|
{{ t('setting.experiments.entry', '{value,tostring} (weight: {weight,tostring})', i) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
:disabled="exp.default"
|
||||||
|
:class="{'tw-button--disabled': exp.default}"
|
||||||
|
class="tw-mg-t-05 tw-button tw-button--text tw-tooltip-wrapper"
|
||||||
|
@click="reset(key)"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-cancel" />
|
||||||
|
<span class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||||
|
{{ t('setting.reset', 'Reset to Default') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div v-if="! Object.keys(ffz_data).length">
|
||||||
|
{{ t('setting.experiments.none', 'There are no current experiments.') }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="! visible_ffz.length">
|
||||||
|
{{ t('setting.experiments.none-filter', 'There are no matching experiments.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 class="tw-mg-t-5 tw-mg-b-1">
|
||||||
|
<span>
|
||||||
|
{{ t('setting.experiments.twitch', 'Twitch Experiments') }}
|
||||||
|
</span>
|
||||||
|
<span v-if="filter" class="tw-mg-l-1 tw-font-size-base tw-regular tw-c-text-alt-2">
|
||||||
|
{{ t('setting.experiments.visible', '(Showing {visible,number} of {total,number})', {
|
||||||
|
visible: visible_twitch.length,
|
||||||
|
total: sorted_twitch.length
|
||||||
|
}) }}
|
||||||
|
</span>
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div class="ffz--experiment-list">
|
||||||
|
<section
|
||||||
|
v-for="({key, exp}) of visible_twitch"
|
||||||
|
:key="key"
|
||||||
|
:data-key="key"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:class="{live: exp.in_use}"
|
||||||
|
class="ffz--experiment-row tw-elevation-1 tw-c-background-base tw-border tw-pd-y-05 tw-pd-x-1 tw-mg-y-05 tw-flex"
|
||||||
|
>
|
||||||
|
<div v-if="unused" class="tw-flex tw-flex-shrink-0 tw-align-items-center tw-border-r tw-mg-r-1 tw-pd-r-1">
|
||||||
|
<div v-if="exp.in_use" class="ffz--profile__icon ffz-i-ok tw-tooltip-wrapper">
|
||||||
|
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-left">
|
||||||
|
{{ t('setting.experiments.active', 'This experiment is active.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="ffz--profile__icon ffz-i-cancel tw-tooltip-wrapper">
|
||||||
|
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-left">
|
||||||
|
{{ t('setting.experiments.inactive', 'This experiment is not active.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex-grow-1">
|
||||||
|
<h4>{{ exp.name }}</h4>
|
||||||
|
<div class="description">
|
||||||
|
{{ exp.remainder }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-flex tw-flex-shrink-0 tw-align-items-start">
|
||||||
|
<select
|
||||||
|
:data-key="key"
|
||||||
|
class="tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||||
|
@change="onTwitchChange($event)"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-if="exp.in_use === false"
|
||||||
|
:selected="exp.default"
|
||||||
|
>
|
||||||
|
{{ t('setting.experiments.unset', 'unset') }}
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
v-for="(i, idx) in exp.groups"
|
||||||
|
:key="idx"
|
||||||
|
:selected="i.value === exp.value"
|
||||||
|
>
|
||||||
|
{{ t('setting.experiments.entry', '{value,tostring} (weight: {weight,tostring})', i) }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button
|
||||||
|
:disabled="exp.default"
|
||||||
|
:class="{'tw-button--disabled': exp.default}"
|
||||||
|
class="tw-mg-t-05 tw-button tw-button--text tw-tooltip-wrapper"
|
||||||
|
@click="resetTwitch(key)"
|
||||||
|
>
|
||||||
|
<span class="tw-button__text ffz-i-cancel" />
|
||||||
|
<span class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||||
|
{{ t('setting.reset', 'Reset to Default') }}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div v-if="! Object.keys(twitch_data).length">
|
||||||
|
{{ t('setting.experiments.none', 'There are no current experiments.') }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="! visible_twitch.length">
|
||||||
|
{{ t('setting.experiments.none-filter', 'There are no matching experiments.') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -173,7 +229,9 @@ export default {
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
experiments_locked: this.item.is_locked(),
|
||||||
sort_by: 1,
|
sort_by: 1,
|
||||||
|
unused: false,
|
||||||
unique_id: this.item.unique_id(),
|
unique_id: this.item.unique_id(),
|
||||||
ffz_data: this.item.ffz_data(),
|
ffz_data: this.item.ffz_data(),
|
||||||
twitch_data: this.item.twitch_data()
|
twitch_data: this.item.twitch_data()
|
||||||
|
@ -242,6 +300,14 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
enterCode() {
|
||||||
|
if ( this.$refs.code.value !== 'sv_cheats 1' )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.experiments_locked = false;
|
||||||
|
this.item.unlock();
|
||||||
|
},
|
||||||
|
|
||||||
calculateRarity(exp) {
|
calculateRarity(exp) {
|
||||||
let rarity;
|
let rarity;
|
||||||
for(const group of exp.groups)
|
for(const group of exp.groups)
|
||||||
|
@ -254,7 +320,15 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
sorted(data) {
|
sorted(data) {
|
||||||
const out = Object.entries(data).map(x => ({key: x[0], exp: x[1]}));
|
const out = [];
|
||||||
|
for(const [k,v] of Object.entries(data)) {
|
||||||
|
if ( ! this.unused && v.in_use === false )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
out.push({key: k, exp: v});
|
||||||
|
}
|
||||||
|
|
||||||
|
//const out = Object.entries(data).map(x => ({key: x[0], exp: x[1]}));
|
||||||
|
|
||||||
out.sort((a,b) => {
|
out.sort((a,b) => {
|
||||||
const a_use = a.exp.in_use,
|
const a_use = a.exp.in_use,
|
||||||
|
|
|
@ -27,6 +27,12 @@ export default class Channel extends Module {
|
||||||
this.inject('metadata');
|
this.inject('metadata');
|
||||||
this.inject('socket');
|
this.inject('socket');
|
||||||
|
|
||||||
|
/*this.SideNav = this.elemental.define(
|
||||||
|
'side-nav', '.side-bar-contents .side-nav-section:first-child',
|
||||||
|
null,
|
||||||
|
{childNodes: true, subtree: true}, 1
|
||||||
|
);*/
|
||||||
|
|
||||||
this.ChannelRoot = this.elemental.define(
|
this.ChannelRoot = this.elemental.define(
|
||||||
'channel-root', '.channel-root',
|
'channel-root', '.channel-root',
|
||||||
USER_PAGES,
|
USER_PAGES,
|
||||||
|
@ -43,6 +49,9 @@ export default class Channel extends Module {
|
||||||
onEnable() {
|
onEnable() {
|
||||||
this.updateChannelColor();
|
this.updateChannelColor();
|
||||||
|
|
||||||
|
//this.SideNav.on('mount', this.updateHidden, this);
|
||||||
|
//this.SideNav.on('mutate', this.updateHidden, this);
|
||||||
|
|
||||||
this.ChannelRoot.on('mount', this.updateRoot, this);
|
this.ChannelRoot.on('mount', this.updateRoot, this);
|
||||||
this.ChannelRoot.on('mutate', this.updateRoot, this);
|
this.ChannelRoot.on('mutate', this.updateRoot, this);
|
||||||
this.ChannelRoot.on('unmount', this.removeRoot, this);
|
this.ChannelRoot.on('unmount', this.removeRoot, this);
|
||||||
|
@ -54,6 +63,19 @@ export default class Channel extends Module {
|
||||||
this.InfoBar.each(el => this.updateBar(el));
|
this.InfoBar.each(el => this.updateBar(el));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*updateHidden(el) { // eslint-disable-line class-methods-use-this
|
||||||
|
if ( ! el._ffz_raf )
|
||||||
|
el._ffz_raf = requestAnimationFrame(() => {
|
||||||
|
el._ffz_raf = null;
|
||||||
|
const nodes = el.querySelectorAll('.side-nav-card__avatar--offline');
|
||||||
|
for(const node of nodes) {
|
||||||
|
const par = node.closest('.tw-transition');
|
||||||
|
if ( par && el.contains(par) )
|
||||||
|
par.classList.add('tw-hide');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
|
||||||
updateSubscription(login) {
|
updateSubscription(login) {
|
||||||
if ( this._subbed_login === login )
|
if ( this._subbed_login === login )
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -126,6 +126,16 @@ export default class EmoteMenu extends Module {
|
||||||
|
|
||||||
this.SUB_STATUS = SUB_STATUS;
|
this.SUB_STATUS = SUB_STATUS;
|
||||||
|
|
||||||
|
this.settings.add('chat.emote-menu.shortcut', {
|
||||||
|
default: false,
|
||||||
|
ui: {
|
||||||
|
path: 'Chat > Emote Menu >> General',
|
||||||
|
title: 'Use Ctrl+E to open the Emote Menu.',
|
||||||
|
description: 'When enabled and you press Ctrl+E with the chat input focused, the emote menu will open.',
|
||||||
|
component: 'setting-check-box'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.settings.add('chat.emote-menu.enabled', {
|
this.settings.add('chat.emote-menu.enabled', {
|
||||||
default: true,
|
default: true,
|
||||||
ui: {
|
ui: {
|
||||||
|
@ -568,6 +578,7 @@ export default class EmoteMenu extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
storage.set('emote-menu.hidden-sets', hidden);
|
storage.set('emote-menu.hidden-sets', hidden);
|
||||||
|
t.emit('chat.emotes:change-set-hidden', key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
|
|
||||||
import Module from 'utilities/module';
|
import Module from 'utilities/module';
|
||||||
import { findReactFragment } from 'utilities/dom';
|
import { findReactFragment } from 'utilities/dom';
|
||||||
|
import { TWITCH_POINTS_SETS, TWITCH_GLOBAL_SETS, TWITCH_PRIME_SETS, KNOWN_CODES, REPLACEMENTS, REPLACEMENT_BASE, TWITCH_EMOTE_BASE } from 'utilities/constants';
|
||||||
|
|
||||||
import Twilight from 'site';
|
import Twilight from 'site';
|
||||||
|
|
||||||
export default class Input extends Module {
|
export default class Input extends Module {
|
||||||
|
@ -223,9 +225,23 @@ export default class Input extends Module {
|
||||||
this.EmoteSuggestions.on('mount', this.overrideEmoteMatcher, this);
|
this.EmoteSuggestions.on('mount', this.overrideEmoteMatcher, this);
|
||||||
this.MentionSuggestions.on('mount', this.overrideMentionMatcher, this);
|
this.MentionSuggestions.on('mount', this.overrideMentionMatcher, this);
|
||||||
|
|
||||||
|
this.on('chat.emotes:change-hidden', this.uncacheTabCompletion, this);
|
||||||
|
this.on('chat.emotes:change-set-hidden', this.uncacheTabCompletion, this);
|
||||||
|
this.on('chat.emotes:change-favorite', this.uncacheTabCompletion, this);
|
||||||
|
this.on('chat.emotes:update-default-sets', this.uncacheTabCompletion, this);
|
||||||
|
this.on('chat.emotes:update-user-sets', this.uncacheTabCompletion, this);
|
||||||
|
this.on('chat.emotes:update-room-sets', this.uncacheTabCompletion, this);
|
||||||
|
|
||||||
this.on('site.css_tweaks:update-chat-css', this.resizeInput, this);
|
this.on('site.css_tweaks:update-chat-css', this.resizeInput, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uncacheTabCompletion() {
|
||||||
|
for(const inst of this.EmoteSuggestions.instances) {
|
||||||
|
inst.ffz_ffz_cache = null;
|
||||||
|
inst.ffz_twitch_cache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateInput() {
|
updateInput() {
|
||||||
for(const inst of this.ChatInput.instances) {
|
for(const inst of this.ChatInput.instances) {
|
||||||
if ( inst ) {
|
if ( inst ) {
|
||||||
|
@ -295,6 +311,12 @@ export default class Input extends Module {
|
||||||
|
|
||||||
inst.onKeyDown = function(event) {
|
inst.onKeyDown = function(event) {
|
||||||
try {
|
try {
|
||||||
|
if ( inst.onEmotePickerToggle && t.chat.context.get('chat.emote-menu.shortcut') && event.key === 'e' && event.ctrlKey && ! event.altKey && ! event.shiftKey ) {
|
||||||
|
inst.onEmotePickerToggle();
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ( inst.autocompleteInputRef && t.chat.context.get('chat.mru.enabled') && ! event.shiftKey && ! event.ctrlKey && ! event.altKey ) {
|
if ( inst.autocompleteInputRef && t.chat.context.get('chat.mru.enabled') && ! event.shiftKey && ! event.ctrlKey && ! event.altKey ) {
|
||||||
const code = event.charCode || event.keyCode;
|
const code = event.charCode || event.keyCode;
|
||||||
|
|
||||||
|
@ -471,54 +493,114 @@ export default class Input extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
buildTwitchCache(emotes) {
|
||||||
getTwitchEmoteSuggestions(input, inst) {
|
if ( ! Array.isArray(emotes) )
|
||||||
const hydratedEmotes = inst.hydrateEmotes(inst.props.emotes);
|
return {emotes: [], length: 0};
|
||||||
if (!Array.isArray(hydratedEmotes)) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const usageResults = [],
|
const out = [],
|
||||||
startingResults = [],
|
hidden_sets = this.settings.provider.get('emote-menu.hidden-sets'),
|
||||||
otherResults = [],
|
has_hidden = Array.isArray(hidden_sets) && hidden_sets.length > 0,
|
||||||
favorites = this.emotes.getFavorites('twitch'),
|
hidden_emotes = this.emotes.getHidden('twitch'),
|
||||||
hidden = this.emotes.getHidden('twitch'),
|
favorites = this.emotes.getFavorites('twitch');
|
||||||
search = input.startsWith(':') ? input.slice(1) : input;
|
|
||||||
|
|
||||||
for (const set of hydratedEmotes) {
|
for(const set of emotes) {
|
||||||
if (set && Array.isArray(set.emotes)) {
|
if ( has_hidden ) {
|
||||||
for (const emote of set.emotes) {
|
const int_id = parseInt(set.id, 10),
|
||||||
if (inst.doesEmoteMatchTerm(emote, search) && ! hidden.includes(emote.id)) {
|
owner = set.owner,
|
||||||
const favorite = favorites.includes(emote.id);
|
is_points = TWITCH_POINTS_SETS.includes(int_id) || owner?.login === 'channel_points',
|
||||||
const element = {
|
channel = is_points ? null : owner;
|
||||||
current: input,
|
|
||||||
replacement: emote.token,
|
|
||||||
element: inst.renderEmoteSuggestion({
|
|
||||||
...emote,
|
|
||||||
favorite
|
|
||||||
}),
|
|
||||||
favorite
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.EmoteUsageCount[emote.token]) {
|
let key = `twitch-set-${set.id}`;
|
||||||
usageResults.push(element);
|
|
||||||
}
|
if ( channel?.login )
|
||||||
else if (emote.token.toLowerCase().startsWith(search)) {
|
key = `twitch-${channel.id}`;
|
||||||
startingResults.push(element);
|
else if ( is_points )
|
||||||
}
|
key = 'twitch-points';
|
||||||
else {
|
else if ( TWITCH_GLOBAL_SETS.includes(int_id) )
|
||||||
otherResults.push(element);
|
key = 'twitch-global';
|
||||||
}
|
else if ( TWITCH_PRIME_SETS.includes(int_id) )
|
||||||
}
|
key = 'twitch-prime';
|
||||||
|
else
|
||||||
|
key = 'twitch-misc';
|
||||||
|
|
||||||
|
if ( hidden_sets.includes(key) )
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const emote of set.emotes) {
|
||||||
|
if ( ! emote || ! emote.id || hidden_emotes.includes(emote.id) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const id = emote.id,
|
||||||
|
replacement = REPLACEMENTS[id];
|
||||||
|
|
||||||
|
let src, srcSet;
|
||||||
|
|
||||||
|
if ( replacement && this.chat.context.get('chat.fix-bad-emotes') ) {
|
||||||
|
src = `${REPLACEMENT_BASE}${replacement}`;
|
||||||
|
srcSet = `${src} 1x`;
|
||||||
|
} else {
|
||||||
|
const base = `${TWITCH_EMOTE_BASE}${id}`;
|
||||||
|
src = `${base}/1.0`;
|
||||||
|
srcSet = `${src} 1x, ${base}/2.0 2x`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
id,
|
||||||
|
setID: set.id,
|
||||||
|
token: KNOWN_CODES[emote.token] || emote.token,
|
||||||
|
srcSet,
|
||||||
|
favorite: favorites.includes(id)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usageResults.sort((a, b) => this.EmoteUsageCount[b.replacement] - this.EmoteUsageCount[a.replacement]);
|
return {
|
||||||
startingResults.sort((a, b) => a.replacement.localeCompare(b.replacement));
|
emotes: out,
|
||||||
otherResults.sort((a, b) => a.replacement.localeCompare(b.replacement));
|
length: emotes.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return usageResults.concat(startingResults).concat(otherResults);
|
|
||||||
|
getTwitchEmoteSuggestions(input, inst) {
|
||||||
|
if ( inst.ffz_twitch_cache?.length !== inst.props.emotes?.length )
|
||||||
|
inst.ffz_twitch_cache = this.buildTwitchCache(inst.props.emotes);
|
||||||
|
|
||||||
|
const emotes = inst.ffz_twitch_cache.emotes;
|
||||||
|
|
||||||
|
if ( ! emotes.length )
|
||||||
|
return [];
|
||||||
|
|
||||||
|
const results_usage = [],
|
||||||
|
results_starting = [],
|
||||||
|
results_other = [],
|
||||||
|
|
||||||
|
search = input.startsWith(':') ? input.slice(1) : input;
|
||||||
|
|
||||||
|
for(const emote of emotes) {
|
||||||
|
if ( inst.doesEmoteMatchTerm(emote, search) ) {
|
||||||
|
const element = {
|
||||||
|
current: input,
|
||||||
|
replacement: emote.token,
|
||||||
|
element: inst.renderEmoteSuggestion(emote),
|
||||||
|
favorite: emote.favorite,
|
||||||
|
count: this.EmoteUsageCount[emote.token] || 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( element.count > 0 )
|
||||||
|
results_usage.push(element);
|
||||||
|
else if ( emote.token.toLowerCase().startsWith(search) )
|
||||||
|
results_starting.push(element);
|
||||||
|
else
|
||||||
|
results_other.push(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
results_usage.sort((a,b) => b.count - a.count);
|
||||||
|
results_starting.sort((a,b) => a.replacement.localeCompare(b.replacement));
|
||||||
|
results_other.sort((a,b) => a.replacement.localeCompare(b.replacement));
|
||||||
|
|
||||||
|
return results_usage.concat(results_starting).concat(results_other);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -564,6 +646,48 @@ export default class Input extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
buildFFZCache(user_id, user_login, channel_id, channel_login) {
|
||||||
|
const sets = this.emotes.getSets(user_id, user_login, channel_id, channel_login);
|
||||||
|
if ( ! sets || ! sets.length )
|
||||||
|
return {emotes: [], length: 0, user_id, user_login, channel_id, channel_login};
|
||||||
|
|
||||||
|
const out = [],
|
||||||
|
hidden_sets = this.settings.provider.get('emote-menu.hidden-sets'),
|
||||||
|
has_hidden = Array.isArray(hidden_sets) && hidden_sets.length > 0;
|
||||||
|
|
||||||
|
for(const set of sets) {
|
||||||
|
if ( ! set || ! set.emotes )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const source = set.source || 'ffz',
|
||||||
|
key = `${set.merge_source || source}-${set.merge_id || set.id}`;
|
||||||
|
|
||||||
|
if ( has_hidden && hidden_sets.includes(key) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const hidden_emotes = this.emotes.getHidden(source),
|
||||||
|
favorites = this.emotes.getFavorites(source);
|
||||||
|
|
||||||
|
for(const emote of Object.values(set.emotes)) {
|
||||||
|
if ( ! emote || ! emote.id || hidden_emotes.includes(emote.id) )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
id: `${source}-${emote.id}`,
|
||||||
|
token: emote.name,
|
||||||
|
srcSet: emote.srcSet,
|
||||||
|
favorite: favorites.includes(emote.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
emotes: out,
|
||||||
|
length: sets.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
getEmoteSuggestions(input, inst) {
|
getEmoteSuggestions(input, inst) {
|
||||||
const user = inst._ffz_user,
|
const user = inst._ffz_user,
|
||||||
channel_id = inst._ffz_channel_id,
|
channel_id = inst._ffz_channel_id,
|
||||||
|
@ -578,17 +702,38 @@ export default class Input extends Module {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let cache = inst.ffz_ffz_cache;
|
||||||
|
if ( ! cache || cache.user_id !== user?.id || cache.user_login !== user?.login || cache.channel_id !== channel_id || cache.channel_login !== channel_login )
|
||||||
|
cache = inst.ffz_ffz_cache = this.buildFFZCache(user?.id, user?.login, channel_id, channel_login);
|
||||||
|
|
||||||
|
const emotes = cache.emotes;
|
||||||
|
if ( ! emotes.length )
|
||||||
|
return [];
|
||||||
|
|
||||||
const search = input.startsWith(':') ? input.slice(1) : input,
|
const search = input.startsWith(':') ? input.slice(1) : input,
|
||||||
results = [],
|
results = [],
|
||||||
sets = this.emotes.getSets(
|
|
||||||
user && user.id,
|
|
||||||
user && user.login,
|
|
||||||
channel_id,
|
|
||||||
channel_login
|
|
||||||
),
|
|
||||||
added_emotes = new Set();
|
added_emotes = new Set();
|
||||||
|
|
||||||
for(const set of sets) {
|
for(const emote of emotes) {
|
||||||
|
if ( inst.doesEmoteMatchTerm(emote, search) && ! added_emotes.has(emote.name) ) {
|
||||||
|
results.push({
|
||||||
|
current: input,
|
||||||
|
replacement: emote.token,
|
||||||
|
element: inst.renderEmoteSuggestion(emote),
|
||||||
|
favorite: emote.favorite,
|
||||||
|
count: 0 // TODO: Count stuff?
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
|
||||||
|
/*for(const set of sets) {
|
||||||
|
if ( ! set || ! set.emotes )
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const
|
||||||
|
|
||||||
if ( set && set.emotes )
|
if ( set && set.emotes )
|
||||||
for(const emote of Object.values(set.emotes))
|
for(const emote of Object.values(set.emotes))
|
||||||
if ( inst.doesEmoteMatchTerm(emote, search) && !added_emotes.has(emote.name) && ! this.emotes.isHidden(set.source || 'ffz', emote.id) ) {
|
if ( inst.doesEmoteMatchTerm(emote, search) && !added_emotes.has(emote.name) && ! this.emotes.isHidden(set.source || 'ffz', emote.id) ) {
|
||||||
|
@ -608,7 +753,7 @@ export default class Input extends Module {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;*/
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteMessage(room, message) {
|
pasteMessage(room, message) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
--border-radius-rounded: 0 !important;
|
--border-radius-rounded: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-avatar-card__halo,
|
||||||
.player-streaminfo__picture img[src] {
|
.player-streaminfo__picture img[src] {
|
||||||
border-radius: 0 !important;
|
border-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,8 +84,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||||
{{ t('metadata.host.setting.auto-hosting.description', 'Toggle all forms of auto hosting: teammates, host list, and similar channels.') }}<br>
|
{{ t('metadata.host.setting.auto-hosting.description', 'Automatically host channels from your host list when you\'re offline.') }}
|
||||||
<a href="https://blog.twitch.tv/grow-your-community-with-auto-hosting-e80c1460f6e1" target="_blank" rel="noopener">{{ t('metadata.host.setting.auto-hosting.link', 'Learn More') }}</a>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="ffz--widget ffz--checkbox">
|
<div class="ffz--widget ffz--checkbox">
|
||||||
|
@ -105,16 +104,14 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||||
{{ t('metadata.host.setting.team-hosting.description',
|
{{ t('metadata.host.setting.team-hosting.description', "Include team channels in your host list.") }}
|
||||||
"Automatically host random channels from your team when you're not live. " +
|
|
||||||
'Team channels will be hosted before any channels in your host list.') }}
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="ffz--widget ffz--checkbox">
|
<div class="ffz--widget ffz--checkbox">
|
||||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||||
<input
|
<input
|
||||||
id="autoHostSettings:deprioritizeVodcast"
|
id="autoHostSettings:deprioritizeVodcast"
|
||||||
:checked="!autoHostSettings.deprioritizeVodcast"
|
:checked="autoHostSettings.deprioritizeVodcast"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="tw-checkbox__input"
|
class="tw-checkbox__input"
|
||||||
data-setting="deprioritizeVodcast"
|
data-setting="deprioritizeVodcast"
|
||||||
|
@ -122,20 +119,19 @@
|
||||||
>
|
>
|
||||||
<label for="autoHostSettings:deprioritizeVodcast" class="tw-checkbox__label">
|
<label for="autoHostSettings:deprioritizeVodcast" class="tw-checkbox__label">
|
||||||
<span class="tw-mg-l-1">
|
<span class="tw-mg-l-1">
|
||||||
{{ t('metadata.host.setting.vodcast-hosting.title', 'Vodcast Hosting') }}
|
{{ t('metadata.host.setting.vodcast-hosting.title', 'Host pre-recorded videos') }}
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||||
{{ t('metadata.host.setting.vodcast-hosting.description', 'Include Vodcasts in auto host.') }}
|
{{ t('metadata.host.setting.vodcast-hosting.description', 'Include channels streaming pre-recorded video, like Premieres or Reruns') }}
|
||||||
<a href="https://blog.twitch.tv/vodcast-brings-the-twitch-community-experience-to-uploads-54098498715" target="_blank" rel="noopener">{{ t('metadata.host.setting.vodcast-hosting.link', 'Learn about Vodcasts') }}</a>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="ffz--widget ffz--checkbox">
|
<div class="ffz--widget ffz--checkbox">
|
||||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||||
<input
|
<input
|
||||||
id="autoHostSettings:strategy"
|
id="autoHostSettings:strategy"
|
||||||
:checked="autoHostSettings.strategy === 'random'"
|
:checked="autoHostSettings.strategy === 'RANDOM'"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
class="tw-checkbox__input"
|
class="tw-checkbox__input"
|
||||||
data-setting="strategy"
|
data-setting="strategy"
|
||||||
|
@ -149,8 +145,7 @@
|
||||||
</div>
|
</div>
|
||||||
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
<section class="tw-c-text-alt-2 ffz-checkbox-description">
|
||||||
{{ t('metadata.host.setting.strategy.description',
|
{{ t('metadata.host.setting.strategy.description',
|
||||||
'If enabled, auto-hosts will be picked at random. ' +
|
'When enabled, host channels are chosen randomly from the list.') }}
|
||||||
"Otherwise they're picked in order.") }}
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -235,10 +235,9 @@ export default class HostButton extends Module {
|
||||||
else if ( setting === 'teamHost' )
|
else if ( setting === 'teamHost' )
|
||||||
setting = 'willAutohostTeam';
|
setting = 'willAutohostTeam';
|
||||||
else if ( setting === 'strategy' )
|
else if ( setting === 'strategy' )
|
||||||
state = state ? 'random' : 'ordered';
|
state = state ? 'RANDOM' : 'ORDERED';
|
||||||
else if ( setting === 'deprioritizeVodcast' ) {
|
else if ( setting === 'deprioritizeVodcast' ) {
|
||||||
setting = 'willPrioritizeAutohost';
|
setting = 'willPrioritizeAutohost';
|
||||||
state = ! state;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateAutoHostSetting(setting, state);
|
this.updateAutoHostSetting(setting, state);
|
||||||
|
@ -255,7 +254,7 @@ export default class HostButton extends Module {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const result = await this.twitch_data.queryApollo(
|
const result = await this.twitch_data.queryApollo(
|
||||||
await import(/* webpackChunkName: 'queries' */ './autohost_list.gql'),
|
await import(/* webpackChunkName: 'host-options' */ './autohost_list.gql'),
|
||||||
{
|
{
|
||||||
id: user.id
|
id: user.id
|
||||||
},
|
},
|
||||||
|
@ -273,7 +272,7 @@ export default class HostButton extends Module {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const result = await this.twitch_data.queryApollo(
|
const result = await this.twitch_data.queryApollo(
|
||||||
await import(/* webpackChunkName: 'queries' */ './autohost_settings.gql'),
|
await import(/* webpackChunkName: 'host-options' */ './autohost_settings.gql'),
|
||||||
{
|
{
|
||||||
id: user.id
|
id: user.id
|
||||||
},
|
},
|
||||||
|
@ -341,7 +340,7 @@ export default class HostButton extends Module {
|
||||||
const autoHosts = this.getAutoHostIDs(newHosts);
|
const autoHosts = this.getAutoHostIDs(newHosts);
|
||||||
|
|
||||||
const result = await this.twitch_data.mutate({
|
const result = await this.twitch_data.mutate({
|
||||||
mutation: await import(/* webpackChunkName: 'queries' */ './autohost_list_mutate.gql'),
|
mutation: await import(/* webpackChunkName: 'host-options' */ './autohost_list_mutate.gql'),
|
||||||
variables: {
|
variables: {
|
||||||
userID: user.id,
|
userID: user.id,
|
||||||
channelIDs: autoHosts
|
channelIDs: autoHosts
|
||||||
|
@ -361,7 +360,7 @@ export default class HostButton extends Module {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const result = await this.twitch_data.mutate({
|
const result = await this.twitch_data.mutate({
|
||||||
mutation: await import(/* webpackChunkName: 'queries' */ './autohost_settings_mutate.gql'),
|
mutation: await import(/* webpackChunkName: 'host-options' */ './autohost_settings_mutate.gql'),
|
||||||
variables: {
|
variables: {
|
||||||
userID: user.id,
|
userID: user.id,
|
||||||
[setting]: newValue
|
[setting]: newValue
|
|
@ -279,6 +279,19 @@ textarea.tw-input {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ffz--experiments code {
|
||||||
|
user-select: none;
|
||||||
|
padding: 2px 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background-color: rgba(0,0,0,0.2);
|
||||||
|
font-family: monospace;
|
||||||
|
white-space: pre;
|
||||||
|
|
||||||
|
.tw-root--theme-dark & {
|
||||||
|
background-color:rgba(255,255,255,0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ffz--changelog,
|
.ffz--changelog,
|
||||||
.ffz--widget {
|
.ffz--widget {
|
||||||
code {
|
code {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue