mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-10-15 07:21:58 +00:00
4.14.0
* Added: Profiles can now be toggled on and off, rather than relying on rules to control them. * Added: Right-clicking the FFZ menu button in the top right of the page will open a Profiles dialog, letting you quickly toggle a profile on or off. * Added: Profiles can now be enabled or disabled based upon the time of day. * Added: Polish and Serbian pluralization rules. * Fixed: Ensure tool-tips work on the new dashboard. * Fixed: Ensure the FFZ Control Center works on the new dashboard.
This commit is contained in:
parent
02efd61f00
commit
62bb6440f3
30 changed files with 503 additions and 52 deletions
47
src/settings/components/time.vue
Normal file
47
src/settings/components/time.vue
Normal file
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<section class="tw-flex-grow-1 tw-align-self-start tw-flex tw-align-items-center">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<div class="tw-mg-r-1">
|
||||
{{ t(type.i18n, type.title) }}
|
||||
</div>
|
||||
|
||||
<label :for="'start-time$' + id" class="tw-mg-l-1">
|
||||
{{ t('settings.filter.time.start', 'Start:') }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
:id="'start-time$' + id"
|
||||
v-model="value.data[0]"
|
||||
type="time"
|
||||
class="ffz-min-width-unset tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-mg-x-1 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
>
|
||||
|
||||
<label :for="'end-time$' + id" class="tw-mg-l-1">
|
||||
{{ t('settings.filter.time.end', 'End:') }}
|
||||
</label>
|
||||
|
||||
<input
|
||||
:id="'end-time$' + id"
|
||||
v-model="value.data[1]"
|
||||
type="time"
|
||||
class="ffz-min-width-unset tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-mg-x-1 tw-input"
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
let last_id = 0;
|
||||
|
||||
export default {
|
||||
props: ['value', 'type', 'filters', 'context'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
id: last_id++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
|
@ -118,8 +118,9 @@ export default class SettingsContext extends EventEmitter {
|
|||
selectProfiles() {
|
||||
const new_profiles = [],
|
||||
order = this.order = [];
|
||||
|
||||
for(const profile of this.manager.__profiles)
|
||||
if ( profile.matches(this.__context) ) {
|
||||
if ( profile.toggled && profile.matches(this.__context) ) {
|
||||
new_profiles.push(profile);
|
||||
order.push(profile.id);
|
||||
}
|
||||
|
@ -389,6 +390,16 @@ export default class SettingsContext extends EventEmitter {
|
|||
}
|
||||
|
||||
|
||||
hasProfile(profile) {
|
||||
if ( typeof profile === 'number' )
|
||||
for(const prof of this.__profiles)
|
||||
if ( prof.id === profile )
|
||||
return true;
|
||||
|
||||
return this.__profiles.includes(profile);
|
||||
}
|
||||
|
||||
|
||||
_getRaw(key, type) {
|
||||
if ( ! type )
|
||||
throw new Error(`non-existent type for ${key}`)
|
||||
|
|
|
@ -42,6 +42,74 @@ export const Or = {
|
|||
|
||||
// Context Stuff
|
||||
|
||||
function parseTime(time) {
|
||||
if ( typeof time !== 'string' || ! time.length )
|
||||
return null;
|
||||
|
||||
const idx = time.indexOf(':');
|
||||
if ( idx === -1 )
|
||||
return null;
|
||||
|
||||
let hours, minutes;
|
||||
try {
|
||||
hours = parseInt(time.slice(0, idx), 10);
|
||||
minutes = parseInt(time.slice(idx + 1), 10);
|
||||
|
||||
} catch(err) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return hours * 60 + minutes;
|
||||
}
|
||||
|
||||
export const Time = {
|
||||
_captured: new Set,
|
||||
|
||||
createTest(config) {
|
||||
const start = parseTime(config[0]),
|
||||
end = parseTime(config[1]);
|
||||
|
||||
if ( start == null || end == null )
|
||||
return () => false;
|
||||
|
||||
if ( start <= end )
|
||||
return () => {
|
||||
Time._captured.add(start);
|
||||
Time._captured.add(end + 1);
|
||||
|
||||
const d = new Date,
|
||||
v = d.getHours() * 60 + d.getMinutes();
|
||||
|
||||
return v >= start && v <= end;
|
||||
}
|
||||
|
||||
return () => {
|
||||
Time._captured.add(start + 1);
|
||||
Time._captured.add(end);
|
||||
|
||||
const d = new Date,
|
||||
v = d.getHours() * 60 + d.getMinutes();
|
||||
|
||||
return v <= start || v >= end;
|
||||
}
|
||||
},
|
||||
|
||||
captured: () => {
|
||||
const out = Array.from(Time._captured);
|
||||
Time._captured = new Set;
|
||||
out.sort((a, b) => a - b);
|
||||
return out;
|
||||
},
|
||||
|
||||
title: 'Time of Day',
|
||||
i18n: 'settings.filter.time',
|
||||
|
||||
default: ['08:00', '18:00'],
|
||||
|
||||
editor: () => import(/* webpackChunkName: 'main-menu' */ './components/time.vue')
|
||||
}
|
||||
|
||||
|
||||
export const TheaterMode = {
|
||||
createTest(config) {
|
||||
return ctx => ctx.ui && ctx.ui.theatreModeEnabled === config;
|
||||
|
|
|
@ -46,7 +46,6 @@ export default class SettingsManager extends Module {
|
|||
|
||||
this.migrations = new MigrationManager(this);
|
||||
|
||||
|
||||
// Also create the main context as early as possible.
|
||||
this.main_context = new SettingsContext(this);
|
||||
|
||||
|
@ -98,6 +97,42 @@ export default class SettingsManager extends Module {
|
|||
this.log.info(`Initialization complete after ${duration.toFixed(5)}ms -- Values: ${this.provider.size} -- Profiles: ${this.__profiles.length}`)
|
||||
|
||||
this.scheduleUpdates();
|
||||
this.updateClock();
|
||||
}
|
||||
|
||||
|
||||
updateClock() {
|
||||
const captured = require('./filters').Time.captured();
|
||||
if ( ! captured?.length )
|
||||
return;
|
||||
|
||||
if ( this._time_timer )
|
||||
clearTimeout(this._time_timer);
|
||||
|
||||
const d = new Date,
|
||||
now = d.getHours() * 60 + d.getMinutes();
|
||||
|
||||
let next = this._time_next != null ? this._time_next : null;
|
||||
for(const value of captured) {
|
||||
if ( value <= now )
|
||||
continue;
|
||||
|
||||
if ( next == null || value < next )
|
||||
next = value;
|
||||
}
|
||||
|
||||
// There's no time waiting for today. Skip to the next day.
|
||||
if ( next == null )
|
||||
next = captured[0] + 1440;
|
||||
|
||||
// Determine how long it'll take to reach the next time period.
|
||||
const delta = (next - now) * 60 * 1000 - 59750 + (60000 - Date.now() % 60000);
|
||||
this._time_timer = setTimeout(() => {
|
||||
for(const context of this.__contexts)
|
||||
context.selectProfiles();
|
||||
|
||||
this.updateClock();
|
||||
}, delta);
|
||||
}
|
||||
|
||||
|
||||
|
@ -186,6 +221,8 @@ export default class SettingsManager extends Module {
|
|||
// Look up the profile it belongs to and emit a changed event from
|
||||
// that profile, thus notifying any contexts or UI instances.
|
||||
key = key.substr(2);
|
||||
|
||||
// Is it a value?
|
||||
const idx = key.indexOf(':');
|
||||
if ( idx === -1 )
|
||||
return;
|
||||
|
@ -193,8 +230,12 @@ export default class SettingsManager extends Module {
|
|||
const profile = this.__profile_ids[key.slice(0, idx)],
|
||||
s_key = key.slice(idx + 1);
|
||||
|
||||
if ( profile )
|
||||
profile.emit('changed', s_key, new_value, deleted);
|
||||
if ( profile ) {
|
||||
if ( s_key === ':enabled' )
|
||||
profile.emit('toggled', profile, deleted ? true : new_value);
|
||||
else
|
||||
profile.emit('changed', s_key, new_value, deleted);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -210,6 +251,17 @@ export default class SettingsManager extends Module {
|
|||
// And then re-select the active profiles.
|
||||
for(const context of this.__contexts)
|
||||
context.selectProfiles();
|
||||
|
||||
this.updateClock();
|
||||
}
|
||||
|
||||
|
||||
_onProfileToggled(profile, val) {
|
||||
for(const context of this.__contexts)
|
||||
context.selectProfiles();
|
||||
|
||||
this.updateClock();
|
||||
this.emit(':profile-toggled', profile, val);
|
||||
}
|
||||
|
||||
|
||||
|
@ -250,6 +302,9 @@ export default class SettingsManager extends Module {
|
|||
let reordered = false,
|
||||
changed = false;
|
||||
|
||||
for(const profile of old_profiles)
|
||||
profile.off('toggled', this._onProfileToggled, this);
|
||||
|
||||
for(const profile_data of raw_profiles) {
|
||||
const id = profile_data.id,
|
||||
slot_id = profiles.length,
|
||||
|
@ -293,12 +348,17 @@ export default class SettingsManager extends Module {
|
|||
changed = true;
|
||||
}
|
||||
|
||||
for(const profile of profiles)
|
||||
profile.on('toggled', this._onProfileToggled, this);
|
||||
|
||||
if ( ! changed && ! old_ids.size || suppress_events )
|
||||
return;
|
||||
|
||||
for(const context of this.__contexts)
|
||||
context.selectProfiles();
|
||||
|
||||
this.updateClock();
|
||||
|
||||
for(const id of new_ids)
|
||||
this.emit(':profile-created', profile_ids[id]);
|
||||
|
||||
|
@ -327,9 +387,10 @@ export default class SettingsManager extends Module {
|
|||
options.name = `Unnamed Profile ${i}`;
|
||||
|
||||
const profile = this.__profile_ids[i] = new SettingsProfile(this, options);
|
||||
|
||||
this.__profiles.unshift(profile);
|
||||
|
||||
profile.on('toggled', this._onProfileToggled, this);
|
||||
|
||||
this._saveProfiles();
|
||||
this.emit(':profile-created', profile);
|
||||
return profile;
|
||||
|
@ -351,6 +412,7 @@ export default class SettingsManager extends Module {
|
|||
if ( profile.id === 0 )
|
||||
throw new Error('cannot delete default profile');
|
||||
|
||||
profile.off('toggled', this._onProfileToggled, this);
|
||||
profile.clear();
|
||||
this.__profile_ids[id] = null;
|
||||
|
||||
|
@ -400,6 +462,8 @@ export default class SettingsManager extends Module {
|
|||
this.provider.set('profiles', this.__profiles.map(prof => prof.data));
|
||||
for(const context of this.__contexts)
|
||||
context.selectProfiles();
|
||||
|
||||
this.updateClock();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ export default class SettingsProfile extends EventEmitter {
|
|||
|
||||
this.data = data;
|
||||
this.prefix = `p:${this.id}:`;
|
||||
this.enabled_key = `${this.prefix}:enabled`;
|
||||
}
|
||||
|
||||
get data() {
|
||||
|
@ -38,6 +39,7 @@ export default class SettingsProfile extends EventEmitter {
|
|||
desc_i18n_key: this.desc_i18n_key,
|
||||
|
||||
url: this.url,
|
||||
show_toggle: this.show_toggle,
|
||||
|
||||
context: this.context
|
||||
}
|
||||
|
@ -72,6 +74,7 @@ export default class SettingsProfile extends EventEmitter {
|
|||
version: 2,
|
||||
type: 'profile',
|
||||
profile: this.data,
|
||||
toggled: this.toggled,
|
||||
values: {}
|
||||
};
|
||||
|
||||
|
@ -107,6 +110,23 @@ export default class SettingsProfile extends EventEmitter {
|
|||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Toggled
|
||||
// ========================================================================
|
||||
|
||||
get toggled() {
|
||||
return this.provider.get(this.enabled_key, true);
|
||||
}
|
||||
|
||||
set toggled(val) {
|
||||
if ( val === this.toggleState )
|
||||
return;
|
||||
|
||||
this.provider.set(this.enabled_key, val);
|
||||
this.emit('toggled', this, val);
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Context
|
||||
// ========================================================================
|
||||
|
@ -158,7 +178,7 @@ export default class SettingsProfile extends EventEmitter {
|
|||
len = p.length;
|
||||
|
||||
for(const key of this.provider.keys())
|
||||
if ( key.startsWith(p) )
|
||||
if ( key.startsWith(p) && key !== this.enabled_key )
|
||||
out.push(key.slice(len));
|
||||
|
||||
return out;
|
||||
|
@ -168,7 +188,7 @@ export default class SettingsProfile extends EventEmitter {
|
|||
const p = this.prefix,
|
||||
len = p.length;
|
||||
for(const key of this.provider.keys())
|
||||
if ( key.startsWith(p) ) {
|
||||
if ( key.startsWith(p) && key !== this.enabled_key ) {
|
||||
this.provider.delete(key);
|
||||
this.emit('changed', key.slice(len), undefined, true);
|
||||
}
|
||||
|
@ -179,7 +199,7 @@ export default class SettingsProfile extends EventEmitter {
|
|||
len = p.length;
|
||||
|
||||
for(const key of this.provider.keys())
|
||||
if ( key.startsWith(p) )
|
||||
if ( key.startsWith(p) && key !== this.enabled_key )
|
||||
yield [key.slice(len), this.provider.get(key)];
|
||||
}
|
||||
|
||||
|
@ -188,7 +208,7 @@ export default class SettingsProfile extends EventEmitter {
|
|||
let count = 0;
|
||||
|
||||
for(const key of this.provider.keys())
|
||||
if ( key.startsWith(p) )
|
||||
if ( key.startsWith(p) && key !== this.enabled_key )
|
||||
count++;
|
||||
|
||||
return count;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue