1
0
Fork 0
mirror of https://github.com/FrankerFaceZ/FrankerFaceZ.git synced 2025-06-27 21:05:53 +00:00
* 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:
SirStendec 2019-10-09 16:02:25 -04:00
parent 02efd61f00
commit 62bb6440f3
30 changed files with 503 additions and 52 deletions

View file

@ -561,6 +561,12 @@
"css": "youtube-play",
"code": 61802,
"src": "fontawesome"
},
{
"uid": "861ab06e455e2de3232ebef67d60d708",
"css": "minus",
"code": 59445,
"src": "fontawesome"
}
]
}

View file

@ -1,7 +1,7 @@
{
"name": "frankerfacez",
"author": "Dan Salvato LLC",
"version": "4.13.3",
"version": "4.14.0",
"description": "FrankerFaceZ is a Twitch enhancement suite.",
"license": "Apache-2.0",
"scripts": {

Binary file not shown.

View file

@ -112,6 +112,8 @@
<glyph glyph-name="clip" unicode="&#xe834;" d="M900 608l-14 99a50 50 0 0 1-57 43l-123-18 95-138 99 14z m-251-35l-96 138-148-21 95-138 149 21z m-287-42l-95 138-124-17a50 50 0 0 1-42-57l14-99 247 35z m538-131v-404c0-28-22-46-50-46h-700c-28 0-50 18-50 46v404h250l-100-150h150l100 150h150l-100-150h150l100 150h100z" horiz-adv-x="1000" />
<glyph glyph-name="minus" unicode="&#xe835;" d="M786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q23 0 38-16t16-38z" horiz-adv-x="785.7" />
<glyph glyph-name="link-ext" unicode="&#xf08e;" d="M786 332v-178q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h393q7 0 12-5t5-13v-36q0-8-5-13t-12-5h-393q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v178q0 8 5 13t13 5h36q8 0 13-5t5-13z m214 482v-285q0-15-11-25t-25-11-25 11l-98 98-364-364q-5-6-13-6t-12 6l-64 64q-6 5-6 12t6 13l364 364-98 98q-11 11-11 25t11 25 25 11h285q15 0 25-11t11-25z" horiz-adv-x="1000" />
<glyph glyph-name="twitter" unicode="&#xf099;" d="M904 622q-37-54-90-93 0-8 0-23 0-73-21-145t-64-139-103-117-144-82-181-30q-151 0-276 81 19-2 43-2 126 0 224 77-59 1-105 36t-64 89q19-3 34-3 24 0 48 6-63 13-104 62t-41 115v2q38-21 82-23-37 25-59 64t-22 86q0 49 25 91 68-83 164-133t208-55q-5 21-5 41 0 75 53 127t127 53q79 0 132-57 61 12 115 44-21-64-80-100 52 6 104 28z" horiz-adv-x="928.6" />

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -486,7 +486,7 @@ export class TranslationManager extends Module {
out = 'FFZ Control Center';
else {
let label = match[1];
if ( label === 'Proxy.render' && location[2].includes('.vue') )
if ( (label === 'Proxy.render' || label.startsWith('Proxy.push')) && location[2].includes('.vue') )
label = 'Vue Component';
out = `${label} (${location[2]}:${location[3]})`;

View file

@ -7,16 +7,24 @@
<template v-if="i !== item">&raquo; </template>
</span>
</header>
<section v-if="! context.currentProfile.live && item.profile_warning !== false" class="tw-border-t tw-pd-t-1 tw-pd-b-2">
<section v-if="(! context.currentProfile.live || ! context.currentProfile.toggled) && item.profile_warning !== false" class="tw-border-t tw-pd-t-1 tw-pd-b-2">
<div class="tw-c-background-accent tw-c-text-overlay tw-pd-1">
<h3 class="ffz-i-attention">
{{ t('setting.profiles.inactive', "This profile isn't active.") }}
{{ t('setting.profiles.inactive', "This profile is not active.") }}
</h3>
{{ t('setting.profiles.inactive.description',
"This profile's rules don't match the current context and it therefore isn't currently active, so you " +
"won't see changes you make here reflected on Twitch.")
}}
<span v-if="! context.currentProfile.toggled">
{{ t('setting.profiles.disabled.description',
"This profile has been disabled, so you won't see changes you make here reflected on Twitch.")
}}
</span>
<span v-else>
{{ t('setting.profiles.inactive.description',
"This profile's rules don't match the current context and it therefore isn't currently active, so you " +
"won't see changes you make here reflected on Twitch.")
}}
</span>
</div>
</section>
<section v-if="context.has_update" class="tw-border-t tw-pd-t-1 tw-pd-b-2">

View file

@ -164,16 +164,28 @@
</div>
<div class="tw-flex tw-flex-shrink-0 tw-align-items-center tw-border-l tw-mg-l-1 tw-pd-l-1">
<div v-if="p.live" class="ffz--profile__icon ffz-i-ok tw-relative tw-tooltip-wrapper">
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('setting.profiles.active', 'This profile is active.') }}
<button class="tw-button tw-button--text" @click="toggle(p)">
<div
:class="{
'ffz-i-ok': p.live,
'ffz-i-cancel': ! p.toggled,
'ffz-i-minus': p.toggled && ! p.live
}"
class="ffz--profile__icon tw-relative tw-tooltip-wrapper"
>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
<span v-if="p.live">
{{ t('setting.profiles.active', 'This profile is enabled and active.') }}
</span>
<span v-if="! p.toggled">
{{ t('setting.profiles.disabled', 'This profile is disabled.') }}
</span>
<span v-if="p.toggled && ! p.live">
{{ t('setting.profiles.disabled.rules', 'This profile is enabled, but inactive due to its rules.') }}
</span>
</div>
</div>
</div>
<div v-if="! p.live" class="ffz--profile__icon ffz-i-cancel tw-relative tw-tooltip-wrapper">
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('setting.profiles.inactive', 'This profile is not active.') }}
</div>
</div>
</button>
</div>
</div>
</section>
@ -247,6 +259,10 @@ export default {
this.$emit('change-item', item);
},
toggle(profile) {
profile.toggle();
},
resetImport() {
this.import_error = false;
this.import_error_message = null;

View file

@ -13,7 +13,7 @@
@keyup.space="focusShow"
@click="togglePopup"
>
{{ t(context.currentProfile.i18n_key, context.currentProfile.title, context.currentProfile) }}
{{ context.currentProfile.i18n_key ? t(context.currentProfile.i18n_key, context.currentProfile.title, context.currentProfile) : context.currentProfile.title }}
</div>
<div
v-if="opened"
@ -51,6 +51,14 @@
@keyup.enter="changeProfile(p)"
@click="changeProfile(p)"
>
<div
v-if="! p.toggled"
class="tw-tooltip-wrapper ffz--profile-row__icon ffz-i-cancel tw-absolute"
>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
{{ t('setting.profiles.disabled', 'This profile is disabled.') }}
</div>
</div>
<div
v-if="p.live"
class="tw-tooltip-wrapper ffz--profile-row__icon ffz-i-ok tw-absolute"

View file

@ -526,6 +526,8 @@ export default class MainMenu extends Module {
order: context.manager.__profiles.indexOf(profile),
live: context.__profiles.includes(profile),
toggled: profile.toggled,
title: profile.name,
i18n_key: profile.i18n_key,
@ -541,6 +543,7 @@ export default class MainMenu extends Module {
profile.save()
},
toggle: () => profile.toggled = ! profile.toggled,
getBackup: () => deep_copy(profile.getBackup()),
context: deep_copy(profile.context),
@ -617,6 +620,11 @@ export default class MainMenu extends Module {
this._update_profiles(profile);
},
_profile_toggled(profile, val) {
Vue.set(profile_keys[profile.id], 'toggled', val);
this._update_profiles(profile);
},
_profile_deleted(profile) {
Vue.delete(profile_keys, profile.id);
this._update_profiles();
@ -641,6 +649,7 @@ export default class MainMenu extends Module {
_add_user() {
this._users++;
if ( this._users === 1 ) {
settings.on(':profile-toggled', this._profile_toggled, this);
settings.on(':profile-created', this._profile_created, this);
settings.on(':profile-changed', this._profile_changed, this);
settings.on(':profile-deleted', this._profile_deleted, this);
@ -654,6 +663,7 @@ export default class MainMenu extends Module {
_remove_user() {
this._users--;
if ( this._users === 0 ) {
settings.off(':profile-toggled', this._profile_toggled, this);
settings.off(':profile-created', this._profile_created, this);
settings.off(':profile-changed', this._profile_changed, this);
settings.off(':profile-deleted', this._profile_deleted, this);

View file

@ -60,7 +60,8 @@ export default class TooltipProvider extends Module {
}
onEnable() {
const container = document.querySelector('#root>div') || document.querySelector('#root') || document.querySelector('.clips-root') || document.body;
const container = document.querySelector('.sunlight-root') || document.querySelector('#root>.tw-absolute:not(.tw-flex)') || document.querySelector('#root') || document.querySelector('.clips-root') || document.body;
// is_minimal = false; //container && container.classList.contains('twilight-minimal-root');
this.tips = new Tooltip(container, 'ffz-tooltip', {

View 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>

View file

@ -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}`)

View file

@ -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;

View file

@ -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();
}

View file

@ -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;

View file

@ -236,6 +236,6 @@ Twilight.ROUTES = {
};
Twilight.DIALOG_EXCLUSIVE = '.twilight-main,.twilight-minimal-root>div,#root>div>.tw-full-height,.clips-root';
Twilight.DIALOG_MAXIMIZED = '.twilight-main,.twilight-minimal-root,#root .dashboard-side-nav+.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
Twilight.DIALOG_SELECTOR = '#root>div>.tw-full-height,.twilight-minimal-root>.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
Twilight.DIALOG_EXCLUSIVE = '.sunlight-root,.twilight-main,.twilight-minimal-root>div,#root>div>.tw-full-height,.clips-root';
Twilight.DIALOG_MAXIMIZED = '.sunlight-page,.twilight-main,.twilight-minimal-root,#root .dashboard-side-nav+.tw-full-height,.clips-root>.tw-full-height .scrollable-area';
Twilight.DIALOG_SELECTOR = '.sunlight-root,#root,.twilight-minimal-root>.tw-full-height,.clips-root>.tw-full-height .scrollable-area';

View file

@ -6,7 +6,7 @@
import {DEBUG} from 'utilities/constants';
import {SiteModule} from 'utilities/module';
import {createElement} from 'utilities/dom';
import {createElement, ClickOutside, setChildren} from 'utilities/dom';
export default class MenuButton extends SiteModule {
constructor(...args) {
@ -192,6 +192,7 @@ export default class MenuButton extends SiteModule {
{btn = (<button
class="tw-align-items-center tw-align-middle tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-button-icon tw-core-button tw-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative"
onClick={e => this.handleClick(e, btn)} // eslint-disable-line react/jsx-no-bind
onContextMenu={e => this.renderContext(e, btn)} // eslint-disable-line react/jsx-no-bind
>
<div class="tw-align-items-center tw-flex tw-flex-grow-0">
<span class="tw-button-icon__icon">
@ -256,6 +257,9 @@ export default class MenuButton extends SiteModule {
</div>);
container.insertBefore(el, container.lastElementChild);
if ( this._ctx_open )
this.renderContext(null, btn);
}
handleClick(event, btn) {
@ -269,13 +273,160 @@ export default class MenuButton extends SiteModule {
this.emit(':clicked', event, btn);
}
renderButtonIcon(profile) {
const live = this.settings.main_context.hasProfile(profile);
return (<figure class={`ffz--profile__icon ${live ? 'ffz-i-ok' : profile.toggled ? 'ffz-i-minus' : 'ffz-i-cancel'}`} />);
}
renderButtonTip(profile) {
if ( ! profile.toggled )
return this.i18n.t('setting.profiles.disabled', 'This profile is disabled.');
if ( this.settings.main_context.hasProfile(profile) )
return this.i18n.t('setting.profiles.active', 'This profile is enabled and active.');
return this.i18n.t('setting.profiles.disabled.rules', 'This profile is enabled, but inactive due to its rules.');
}
renderContext(event, btn) {
if ( event ) {
if ( event.shiftKey || event.ctrlKey )
return;
event.preventDefault();
}
const container = btn.parentElement.parentElement;
let ctx = container.querySelector('.ffz--menu-context');
if ( ctx ) {
if ( ctx._ffz_destroy )
ctx._ffz_destroy();
else
return;
}
const destroy = () => {
this._ctx_open = false;
if ( ctx._ffz_outside )
ctx._ffz_outside.destroy();
ctx.remove();
}
const profiles = [];
for(const profile of this.settings.__profiles) {
const toggle = (<button
class="tw-flex-shrink-0 tw-mg-r-1 tw-align-items-center tw-align-middle tw-border-radius-medium tw-button-icon tw-button-icon--secondary tw-core-button tw-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-relative ffz-tooltip ffz-tooltip--no-mouse"
data-title={this.renderButtonTip(profile)}
onClick={e => { // eslint-disable-line react/jsx-no-bind
profile.toggled = ! profile.toggled;
setChildren(toggle, this.renderButtonIcon(profile));
toggle.dataset.title = this.renderButtonTip(profile);
if ( toggle['_ffz_tooltip$0']?.rerender )
toggle['_ffz_tooltip$0'].rerender();
this.emit('tooltips:cleanup');
e.preventDefault();
e.stopPropagation();
}}
>
{ this.renderButtonIcon(profile) }
</button>)
profiles.push(<div class="tw-relative tw-border-b tw-pd-y-05 tw-pd-l-1 tw-flex">
{toggle}
<div>
<h4>{ profile.i18n_key ? this.i18n.t(profile.i18n_key, profile.name, profile) : profile.name }</h4>
{profile.description && (<div class="description">
{ profile.desc_i18n_key ? this.i18n.t(profile.desc_i18n_key, profile.description, profile) : profile.description }
</div>)}
</div>
</div>);
}
ctx = (<div class="tw-absolute tw-balloon tw-balloon--down tw-balloon--lg tw-balloon--right tw-block ffz--menu-context">
<div class="tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-4">
<div class="tw-c-text-base tw-elevation-1 tw-flex tw-flex-shrink-0 tw-pd-x-1 tw-pd-y-05 tw-popover-header">
<div class="tw-flex tw-flex-column tw-justify-content-center tw-mg-l-05 tw-popover-header__icon-slot--left" />
<div class="tw-align-items-center tw-flex tw-flex-column tw-flex-grow-1 tw-justify-content-center">
<h5 class="tw-align-center tw-c-text-alt tw-semibold">
{ this.i18n.t('site.menu_button.profiles', 'Profiles') }
</h5>
</div>
<div class="tw-flex tw-flex-column tw-justify-content-center tw-mg-l-05 tw-popover-header__icon-slot--right">
<div class="tw-inline-flex tw-relative tw-tooltip-wrapper">
<button
class="tw-align-items-center tw-align-middle tw-border-radius-medium tw-button-icon tw-button-icon--secondary tw-core-button tw-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden"
onClick={e => this.openSettings(e)} // eslint-disable-line react/jsx-no-bind
>
<span class="tw-button-icon__icon">
<figure class="ffz-i-cog" />
</span>
</button>
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-center">
{ this.i18n.t('setting.profiles.configure', 'Configure') }
</div>
</div>
</div>
<div class="tw-flex tw-flex-column tw-justify-content-center tw-mg-l-05 tw-popover-header__icon-slot--right">
<button
class="tw-align-items-center tw-align-middle tw-border-radius-medium tw-button-icon tw-button-icon--secondary tw-core-button tw-core-button--border tw-inline-flex tw-interactive tw-justify-content-center tw-overflow-hidden tw-relative"
onClick={destroy} // eslint-disable-line react/jsx-no-bind
>
<span class="tw-button-icon__icon">
<figure class="ffz-i-cancel" />
</span>
</button>
</div>
</div>
<div class="center-window__long-scrollable-area scrollable-area scrollable-area--suppress-scroll-x" data-simplebar>
{profiles}
</div>
</div>
</div>);
ctx._ffz_destroy = destroy;
ctx._ffz_outside = new ClickOutside(ctx, destroy);
container.appendChild(ctx);
this._ctx_open = true;
}
openSettings() {
const menu = this.resolve('main_menu');
if ( ! menu )
return;
menu.requestPage('data_management.profiles');
if ( menu.showing )
return;
this.emit(':clicked');
}
loadMenu(event, btn, page) {
const menu = this.resolve('main_menu');
if ( ! menu )
return;
loadMenu(event, btn) {
const cl = btn && btn.classList;
if ( cl )
cl.add('loading');
this.resolve('main_menu').enable(event).then(() => {
if ( page )
menu.requestPage(page);
if ( menu.showing )
return;
menu.enable(event).then(() => {
if ( cl )
cl.remove('loading');

View file

@ -80,5 +80,6 @@ export default [
"sort-alt-down",
"user",
"clip",
"youtube-play"
"youtube-play",
"minus"
];

View file

@ -487,6 +487,8 @@ const CARDINAL_TO_LANG = {
german: ['de', 'el', 'en', 'es', 'fi', 'hu', 'it', 'nl', 'no', 'nb', 'tr', 'sv'],
hebrew: ['he'],
persian: ['fa'],
polish: ['pl'],
serbian: ['sr'],
french: ['fr', 'pt'],
russian: ['ru','uk']
}
@ -522,6 +524,28 @@ const CARDINAL_TYPES = {
persian: (n, i) => (i === 0 || n === 1) ? 1 : 5,
serbian(n, i, v, t) {
if ( v !== 0 ) return 5;
const i1 = i % 10, i2 = i % 100;
const t1 = t % 10, t2 = t % 100;
if ( i1 === 1 && i2 !== 11 ) return 1;
if ( t1 === 1 && t2 !== 11 ) return 1;
if ( i1 >= 2 && i1 <= 4 && !(i2 >= 12 && i2 <= 14) ) return 3;
if ( t1 >= 2 && t1 <= 4 && !(t2 >= 12 && t2 <= 14) ) return 3;
return 5;
},
polish(n, i, v) {
if ( v !== 0 ) return 5;
if ( n === 1 ) return 1;
const n1 = n % 10, n2 = n % 100;
if ( n1 >= 2 && n1 <= 4 && !(n2 >= 12 && n2 <= 14) ) return 3;
if ( i !== 1 && (n1 === 0 || n1 === 1) ) return 4;
if ( n1 >= 5 && n1 <= 9 ) return 4;
if ( n2 >= 12 && n2 <= 14 ) return 4;
return 5;
},
russian(n,i,v) {
const n1 = n % 10, n2 = n % 100;
if ( n1 === 1 && n2 !== 11 ) return 1;

View file

@ -61,7 +61,10 @@
}
}
.ffz-has-dialog {
position: relative;
.ffz-has-dialog > :not(.ffz-dialog) {
visibility: hidden;
& > :not(.ffz-dialog) {
visibility: hidden;
}
}

View file

@ -52,6 +52,7 @@
.ffz-i-play:before { content: '\e832'; } /* '' */
.ffz-i-user:before { content: '\e833'; } /* '' */
.ffz-i-clip:before { content: '\e834'; } /* '' */
.ffz-i-minus:before { content: '\e835'; } /* '' */
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
.ffz-i-twitter:before { content: '\f099'; } /* '' */
.ffz-i-github:before { content: '\f09b'; } /* '' */

File diff suppressed because one or more lines are too long

View file

@ -52,6 +52,7 @@
.ffz-i-play { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
.ffz-i-user { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe833;&nbsp;'); }
.ffz-i-clip { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
.ffz-i-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe835;&nbsp;'); }
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf099;&nbsp;'); }
.ffz-i-github { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09b;&nbsp;'); }

View file

@ -63,6 +63,7 @@
.ffz-i-play { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe832;&nbsp;'); }
.ffz-i-user { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe833;&nbsp;'); }
.ffz-i-clip { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe834;&nbsp;'); }
.ffz-i-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe835;&nbsp;'); }
.ffz-i-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.ffz-i-twitter { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf099;&nbsp;'); }
.ffz-i-github { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf09b;&nbsp;'); }

View file

@ -1,11 +1,11 @@
@font-face {
font-family: 'ffz-fontello';
src: url('../font/ffz-fontello.eot?84652283');
src: url('../font/ffz-fontello.eot?84652283#iefix') format('embedded-opentype'),
url('../font/ffz-fontello.woff2?84652283') format('woff2'),
url('../font/ffz-fontello.woff?84652283') format('woff'),
url('../font/ffz-fontello.ttf?84652283') format('truetype'),
url('../font/ffz-fontello.svg?84652283#ffz-fontello') format('svg');
src: url('../font/ffz-fontello.eot?15513462');
src: url('../font/ffz-fontello.eot?15513462#iefix') format('embedded-opentype'),
url('../font/ffz-fontello.woff2?15513462') format('woff2'),
url('../font/ffz-fontello.woff?15513462') format('woff'),
url('../font/ffz-fontello.ttf?15513462') format('truetype'),
url('../font/ffz-fontello.svg?15513462#ffz-fontello') format('svg');
font-weight: normal;
font-style: normal;
}
@ -15,7 +15,7 @@
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'ffz-fontello';
src: url('../font/ffz-fontello.svg?84652283#ffz-fontello') format('svg');
src: url('../font/ffz-fontello.svg?15513462#ffz-fontello') format('svg');
}
}
*/
@ -108,6 +108,7 @@
.ffz-i-play:before { content: '\e832'; } /* '' */
.ffz-i-user:before { content: '\e833'; } /* '' */
.ffz-i-clip:before { content: '\e834'; } /* '' */
.ffz-i-minus:before { content: '\e835'; } /* '' */
.ffz-i-link-ext:before { content: '\f08e'; } /* '' */
.ffz-i-twitter:before { content: '\f099'; } /* '' */
.ffz-i-github:before { content: '\f09b'; } /* '' */

View file

@ -149,12 +149,18 @@ textarea.tw-input {
}
}
.ffz--profile-row__icon,
.ffz--profile__icon {
&.ffz-i-ok,
.ffz-i-ok {
color: var(--color-green-darker);
}
&.ffz-i-minus,
.ffz-i-minus {
color: var(--color-text-alt-2);
}
&.ffz-i-cancel,
.ffz-i-cancel {
color: var(--color-red);

View file

@ -18,9 +18,9 @@
border-left: 4px solid;
border-left-color: transparent;
&.live .ffz--profile-row__icon {
/*&.live .ffz--profile-row__icon {
color: var(--color-green-darker);
}
}*/
&:not(.live):not(:hover):not(:focus) {
opacity: .5;