mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-06-27 21:05:53 +00:00
4.20.61
This was a small update until Twitch ripped out half their CSS. * Added: Cross-Origin Storage Bridge for settings, better synchronizing settings on sub-domains with support for binary blobs at the cost of slightly increased start-up time. * Fixed: Rendering issues caused by missing CSS. * Fixed: FFZ Control Center button not appearing on dashboard pages, and appearing in the incorrect place. * Changed: Work towards splitting modules into their own JS files for a faster, more asynchronous startup. * API Added: Methods for serializing and deserializing Blobs for transmission across postMessage.
This commit is contained in:
parent
2c5937c8af
commit
264c375f13
88 changed files with 1685 additions and 500 deletions
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "frankerfacez",
|
||||
"author": "Dan Salvato LLC",
|
||||
"version": "4.20.60",
|
||||
"version": "4.20.61",
|
||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
|
|
105
src/bridge.js
105
src/bridge.js
|
@ -6,6 +6,7 @@ import Logger from 'utilities/logging';
|
|||
import Module from 'utilities/module';
|
||||
|
||||
import {DEBUG} from 'utilities/constants';
|
||||
import {serializeBlob, deserializeBlob} from 'utilities/blobs';
|
||||
|
||||
import SettingsManager from './settings/index';
|
||||
|
||||
|
@ -28,6 +29,7 @@ class FFZBridge extends Module {
|
|||
this.inject('raven', RavenLogger);
|
||||
|
||||
this.log = new Logger(null, null, null, this.raven);
|
||||
this.log.label = 'FFZBridge';
|
||||
this.log.init = true;
|
||||
|
||||
this.core_log = this.log.get('core');
|
||||
|
@ -60,15 +62,21 @@ class FFZBridge extends Module {
|
|||
return FFZBridge.instance;
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
async onEnable() {
|
||||
window.addEventListener('message', this.onMessage.bind(this));
|
||||
this.settings.provider.on('changed', this.onProviderChange, this);
|
||||
this.settings.provider.on('changed-blob', this.onProviderBlobChange, this);
|
||||
this.settings.provider.on('clear-blobs', this.onProviderClearBlobs, this);
|
||||
|
||||
await this.settings.awaitProvider();
|
||||
await this.settings.provider.awaitReady();
|
||||
|
||||
this.send({
|
||||
ffz_type: 'ready'
|
||||
});
|
||||
}
|
||||
|
||||
onMessage(event) {
|
||||
async onMessage(event) {
|
||||
const msg = event.data;
|
||||
if ( ! msg || ! msg.ffz_type )
|
||||
return;
|
||||
|
@ -83,13 +91,86 @@ class FFZBridge extends Module {
|
|||
data: out
|
||||
});
|
||||
|
||||
} else if ( msg.ffz_type === 'change' )
|
||||
return;
|
||||
|
||||
} else if ( msg.ffz_type === 'change' ) {
|
||||
this.onChange(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! msg.id )
|
||||
return this.log.warn('Received command with no reply ID');
|
||||
|
||||
let reply, transfer;
|
||||
|
||||
try {
|
||||
if ( msg.ffz_type === 'init-load' ) {
|
||||
reply = {
|
||||
blobs: this.settings.provider.supportsBlobs,
|
||||
values: {}
|
||||
};
|
||||
|
||||
for(const [key,value] of this.settings.provider.entries())
|
||||
reply.values[key] = value;
|
||||
|
||||
} else if ( msg.ffz_type === 'set' )
|
||||
this.settings.provider.set(msg.key, msg.value);
|
||||
|
||||
else if ( msg.ffz_type === 'delete' )
|
||||
this.settings.provider.delete(msg.key);
|
||||
|
||||
else if ( msg.ffz_type === 'clear' )
|
||||
this.settings.provider.clear();
|
||||
|
||||
else if ( msg.ffz_type === 'get-blob' ) {
|
||||
reply = await serializeBlob(await this.settings.provider.getBlob(msg.key));
|
||||
if ( reply )
|
||||
transfer = reply.buffer;
|
||||
|
||||
} else if ( msg.ffz_type === 'set-blob' ) {
|
||||
const blob = deserializeBlob(msg.value);
|
||||
await this.settings.provider.setBlob(msg.key, blob);
|
||||
|
||||
} else if ( msg.ffz_type === 'delete-blob' )
|
||||
await this.settings.provider.deleteBlob(msg.key);
|
||||
|
||||
else if ( msg.ffz_type === 'has-blob' )
|
||||
reply = await this.settings.provider.hasBlob(msg.key);
|
||||
|
||||
else if ( msg.ffz_type === 'clear-blobs' )
|
||||
await this.settings.provider.clearBlobs();
|
||||
|
||||
else if ( msg.ffz_type === 'blob-keys' )
|
||||
reply = await this.settings.provider.blobKeys();
|
||||
|
||||
else if ( msg.ffz_type === 'flush' )
|
||||
await this.settings.provider.flush();
|
||||
|
||||
else
|
||||
return this.send({
|
||||
ffz_type: 'reply-error',
|
||||
id: msg.id,
|
||||
error: 'bad-command'
|
||||
});
|
||||
|
||||
this.send({
|
||||
ffz_type: 'reply',
|
||||
id: msg.id,
|
||||
reply
|
||||
}, transfer);
|
||||
|
||||
} catch(err) {
|
||||
this.log.error('Error handling command.', err);
|
||||
this.send({
|
||||
ffz_type: 'reply-error',
|
||||
id: msg.id
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
send(msg) { // eslint-disable-line class-methods-use-this
|
||||
send(msg, blob) { // eslint-disable-line class-methods-use-this
|
||||
try {
|
||||
window.parent.postMessage(msg, '*')
|
||||
window.parent.postMessage(msg, '*', blob ? [blob] : undefined)
|
||||
} catch(err) { this.log.error('send error', err); /* no-op */ }
|
||||
}
|
||||
|
||||
|
@ -112,6 +193,20 @@ class FFZBridge extends Module {
|
|||
deleted
|
||||
});
|
||||
}
|
||||
|
||||
onProviderBlobChange(key, deleted) {
|
||||
this.send({
|
||||
ffz_type: 'change-blob',
|
||||
key,
|
||||
deleted
|
||||
});
|
||||
}
|
||||
|
||||
onProviderClearBlobs() {
|
||||
this.send({
|
||||
ffz_type: 'clear-blobs'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
FFZBridge.Logger = Logger;
|
||||
|
|
27
src/main.js
27
src/main.js
|
@ -68,17 +68,17 @@ class FrankerFaceZ extends Module {
|
|||
// Startup
|
||||
// ========================================================================
|
||||
|
||||
this.discoverModules();
|
||||
this.discoverModules()
|
||||
.then(() => this.enable())
|
||||
.then(() => this.enableInitialModules()).then(() => {
|
||||
const duration = performance.now() - start_time;
|
||||
this.core_log.info(`Initialization complete in ${duration.toFixed(5)}ms.`);
|
||||
this.log.init = false;
|
||||
|
||||
this.enable().then(() => this.enableInitialModules()).then(() => {
|
||||
const duration = performance.now() - start_time;
|
||||
this.core_log.info(`Initialization complete in ${duration.toFixed(5)}ms.`);
|
||||
this.log.init = false;
|
||||
|
||||
}).catch(err => {
|
||||
this.core_log.error('An error occurred during initialization.', err);
|
||||
this.log.init = false;
|
||||
});
|
||||
}).catch(err => {
|
||||
this.core_log.error('An error occurred during initialization.', err);
|
||||
this.log.init = false;
|
||||
});
|
||||
}
|
||||
|
||||
static get() {
|
||||
|
@ -132,9 +132,10 @@ ${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`).join('\n\n'
|
|||
// Modules
|
||||
// ========================================================================
|
||||
|
||||
discoverModules() {
|
||||
const ctx = require.context('src/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/),
|
||||
modules = this.populate(ctx, this.core_log);
|
||||
async discoverModules() {
|
||||
// TODO: Actually do async modules.
|
||||
const ctx = await require.context('src/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/ /*, 'lazy-once' */);
|
||||
const modules = this.populate(ctx, this.core_log);
|
||||
|
||||
this.core_log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<input
|
||||
id="edit_reason"
|
||||
v-model.trim="value.reason"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
@input="$emit('input', value)"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
:id="'edit_chat$' + id"
|
||||
v-model="value.command"
|
||||
:placeholder="defaults.command"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
@input="$emit('input', value)"
|
||||
>
|
||||
|
||||
|
@ -17,16 +17,16 @@
|
|||
{{ t('setting.actions.variables', 'Available Variables: {vars}', {vars}) }}
|
||||
</div>
|
||||
|
||||
<div class="tw-checkbox">
|
||||
<div class="ffz-checkbox">
|
||||
<input
|
||||
:id="'chat-paste$' + id"
|
||||
v-model="value.paste"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="$emit('input', value)"
|
||||
>
|
||||
|
||||
<label :for="'chat-paste$' + id" class="tw-checkbox__label">
|
||||
<label :for="'chat-paste$' + id" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.set-chat', 'Paste this message into chat rather than sending it directly.') }}
|
||||
</span>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<input
|
||||
id="edit_text"
|
||||
v-model="value.text"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
@input="$emit('input', value)"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<input
|
||||
id="edit_image"
|
||||
v-model="value.image"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
@input="$emit('input', value)"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<input
|
||||
id="edit_text"
|
||||
v-model="value.text"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
@input="$emit('input', value)"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
id="edit_duration"
|
||||
v-model="value.duration_rich"
|
||||
:placeholder="defaults.duration"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
type="text"
|
||||
@input="update()"
|
||||
>
|
||||
|
@ -23,7 +23,7 @@
|
|||
<input
|
||||
id="edit_reason"
|
||||
v-model.trim="value.reason"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
@input="$emit('input', value)"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
id="edit_url"
|
||||
v-model="value.url"
|
||||
:placeholder="defaults.url"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
@input="$emit('input', value)"
|
||||
>
|
||||
|
||||
|
|
|
@ -263,7 +263,7 @@ export default class Actions extends Module {
|
|||
reason_elements.push(<li class="tw-full-width tw-relative">
|
||||
<a
|
||||
href="#"
|
||||
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-pd-05"
|
||||
class="tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive tw-pd-05"
|
||||
onClick={click_fn(text)}
|
||||
>
|
||||
{text}
|
||||
|
@ -280,7 +280,7 @@ export default class Actions extends Module {
|
|||
reason_elements.push(<li class="tw-full-width tw-relative">
|
||||
<a
|
||||
href="#"
|
||||
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-pd-05"
|
||||
class="tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive tw-pd-05"
|
||||
onClick={click_fn(rule)}
|
||||
>
|
||||
{rule}
|
||||
|
@ -350,9 +350,9 @@ export default class Actions extends Module {
|
|||
hover_events: true,
|
||||
no_update: true,
|
||||
|
||||
tooltipClass: 'ffz-action-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
|
||||
arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute',
|
||||
arrowInner: 'tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
|
||||
tooltipClass: 'ffz-action-balloon ffz-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
|
||||
arrowClass: 'ffz-balloon__tail tw-overflow-hidden tw-absolute',
|
||||
arrowInner: 'ffz-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
|
||||
innerClass: '',
|
||||
|
||||
popper: {
|
||||
|
|
|
@ -281,8 +281,8 @@ export default {
|
|||
class: [
|
||||
tooltip && 'ffz-tooltip',
|
||||
this.accent && 'ffz-accent-card',
|
||||
!this.error && 'tw-interactable--hover-enabled',
|
||||
'tw-block tw-border-radius-medium tw-full-width tw-interactable tw-interactable--default tw-interactive'
|
||||
!this.error && 'ffz-interactable--hover-enabled',
|
||||
'tw-block tw-border-radius-medium tw-full-width ffz-interactable ffz-interactable--default tw-interactive'
|
||||
],
|
||||
attrs: {
|
||||
'data-tooltip-type': 'link',
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<input
|
||||
id="user-name"
|
||||
ref="name"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input tw-flex-grow-1"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input tw-flex-grow-1"
|
||||
:value="name"
|
||||
@change="updateName"
|
||||
>
|
||||
|
|
|
@ -67,9 +67,9 @@ export default class Overrides extends Module {
|
|||
no_update: true,
|
||||
no_auto_remove: true,
|
||||
|
||||
tooltipClass: 'ffz-action-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
|
||||
arrowClass: '', //tw-balloon__tail tw-overflow-hidden tw-absolute',
|
||||
arrowInner: '', //tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
|
||||
tooltipClass: 'ffz-action-balloon ffz-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
|
||||
arrowClass: '', //ffz-balloon__tail tw-overflow-hidden tw-absolute',
|
||||
arrowInner: '', //ffz-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
|
||||
innerClass: '',
|
||||
|
||||
popper: {
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
ref="json"
|
||||
v-model="json"
|
||||
readonly
|
||||
class="tw-full-width tw-full-height tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="tw-full-width tw-full-height tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
@focus="$event.target.select()"
|
||||
/>
|
||||
</template>
|
||||
|
@ -34,7 +34,7 @@
|
|||
|
||||
<input
|
||||
v-model="edit_data.appearance.tooltip"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
@ -47,7 +47,7 @@
|
|||
id="renderer_type"
|
||||
ref="renderer_type"
|
||||
v-model="edit_data.appearance.type"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option
|
||||
v-for="(r, key) in data.renderers"
|
||||
|
@ -91,7 +91,7 @@
|
|||
<select
|
||||
id="vis_mod"
|
||||
v-model="edit_data.display.mod"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -113,7 +113,7 @@
|
|||
<select
|
||||
id="vis_mod_icons"
|
||||
v-model="edit_data.display.mod_icons"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -135,7 +135,7 @@
|
|||
<select
|
||||
id="vis_deleted"
|
||||
v-model="edit_data.display.deleted"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -157,7 +157,7 @@
|
|||
<select
|
||||
id="vis_emote"
|
||||
v-model="edit_data.display.emoteOnly"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -179,7 +179,7 @@
|
|||
<select
|
||||
id="vis_slow"
|
||||
v-model="edit_data.display.slowMode"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -201,7 +201,7 @@
|
|||
<select
|
||||
id="vis_subs"
|
||||
v-model="edit_data.display.followersOnly"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -223,7 +223,7 @@
|
|||
<select
|
||||
id="vis_subs"
|
||||
v-model="edit_data.display.subsMode"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -245,7 +245,7 @@
|
|||
<select
|
||||
id="vis_r9k"
|
||||
v-model="edit_data.display.r9kMode"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option :value="undefined" selected>
|
||||
{{ t('setting.unset', 'Unset') }}
|
||||
|
@ -266,80 +266,80 @@
|
|||
|
||||
<div>
|
||||
<div class="ffz--inline tw-flex">
|
||||
<div class="tw-pd-r-1 tw-checkbox">
|
||||
<div class="tw-pd-r-1 ffz-checkbox">
|
||||
<input
|
||||
:id="'key_ctrl$' + id"
|
||||
ref="key_ctrl"
|
||||
:checked="edit_data.display.keys & 1"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onChangeKeys"
|
||||
>
|
||||
<label :for="'key_ctrl$' + id" class="tw-checkbox__label">
|
||||
<label :for="'key_ctrl$' + id" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.key.ctrl', 'Ctrl') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="tw-pd-r-1 tw-checkbox">
|
||||
<div class="tw-pd-r-1 ffz-checkbox">
|
||||
<input
|
||||
:id="'key_shift$' + id"
|
||||
ref="key_shift"
|
||||
:checked="edit_data.display.keys & 2"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onChangeKeys"
|
||||
>
|
||||
<label :for="'key_shift$' + id" class="tw-checkbox__label">
|
||||
<label :for="'key_shift$' + id" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.key.shift', 'Shift') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="tw-pd-r-1 tw-checkbox">
|
||||
<div class="tw-pd-r-1 ffz-checkbox">
|
||||
<input
|
||||
:id="'key_alt$' + id"
|
||||
ref="key_alt"
|
||||
:checked="edit_data.display.keys & 4"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onChangeKeys"
|
||||
>
|
||||
<label :for="'key_alt$' + id" class="tw-checkbox__label">
|
||||
<label :for="'key_alt$' + id" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.key.alt', 'Alt') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="tw-pd-r-1 tw-checkbox">
|
||||
<div class="tw-pd-r-1 ffz-checkbox">
|
||||
<input
|
||||
:id="'key_meta$' + id"
|
||||
ref="key_meta"
|
||||
:checked="edit_data.display.keys & 8"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onChangeKeys"
|
||||
>
|
||||
<label :for="'key_meta$' + id" class="tw-checkbox__label">
|
||||
<label :for="'key_meta$' + id" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.key.meta', 'Meta') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="tw-pd-r-1 tw-checkbox">
|
||||
<div class="tw-pd-r-1 ffz-checkbox">
|
||||
<input
|
||||
:id="'key_hover$' + id"
|
||||
ref="key_hover"
|
||||
:checked="edit_data.display.hover"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onChangeKeys"
|
||||
>
|
||||
<label :for="'key_hover$' + id" class="tw-checkbox__label">
|
||||
<label :for="'key_hover$' + id" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.key.hover', 'Hover') }}
|
||||
</span>
|
||||
|
@ -361,7 +361,7 @@
|
|||
<select
|
||||
id="action_type"
|
||||
v-model="edit_data.action"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
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"
|
||||
>
|
||||
<option
|
||||
v-for="(a, key) in data.actions"
|
||||
|
|
|
@ -61,7 +61,7 @@
|
|||
<input
|
||||
ref="unlisted"
|
||||
:placeholder="t('addon.unlisted.id', 'add-on id')"
|
||||
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
@keydown.enter="addUnlisted"
|
||||
>
|
||||
<button
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<input
|
||||
ref="url_box"
|
||||
:value="url"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
type="text"
|
||||
readonly
|
||||
@focusin="selectURL"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
<select
|
||||
v-if="editing"
|
||||
v-model="edit_data.v"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width ffz-select tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
>
|
||||
<optgroup
|
||||
v-for="section in badges"
|
||||
|
|
|
@ -32,20 +32,20 @@
|
|||
<header
|
||||
v-if="sec.id"
|
||||
:class="{default: badgeDefault(sec.id)}"
|
||||
class="tw-flex tw-checkbox"
|
||||
class="tw-flex ffz-checkbox"
|
||||
>
|
||||
<input
|
||||
:id="sec.id"
|
||||
:checked="badgeChecked(sec.id)"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@click="onChange(sec.id, $event)"
|
||||
@contextmenu.prevent="reset(sec.id)"
|
||||
>
|
||||
<label
|
||||
:for="sec.id"
|
||||
:title="t('setting.right-click-reset', 'Right-Click to Reset')"
|
||||
class="tw-checkbox__label"
|
||||
class="ffz-checkbox__label"
|
||||
@contextmenu.prevent="reset(sec.id)"
|
||||
>
|
||||
<span class="tw-mg-l-1">
|
||||
|
@ -61,17 +61,17 @@
|
|||
v-for="i in sort(sec.badges)"
|
||||
:key="i.id"
|
||||
:class="{default: badgeDefault(i.id)}"
|
||||
class="ffz--badge-info tw-pd-y-1 tw-pd-r-1 tw-flex tw-checkbox"
|
||||
class="ffz--badge-info tw-pd-y-1 tw-pd-r-1 tw-flex ffz-checkbox"
|
||||
>
|
||||
<input
|
||||
:id="i.id"
|
||||
:checked="badgeChecked(i.id)"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@click="onChange(i.id, $event)"
|
||||
>
|
||||
|
||||
<label :for="i.id" class="tw-checkbox__label">
|
||||
<label :for="i.id" class="ffz-checkbox__label">
|
||||
<div class="tw-mg-l-1 tw-flex">
|
||||
<div
|
||||
:style="{backgroundColor: i.color, backgroundImage: i.styleImage }"
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<select
|
||||
v-if="editing"
|
||||
v-model="edit_data.v"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width ffz-select tw-pd-x-1 tw-pd-y-05 tw-mg-y-05"
|
||||
>
|
||||
<option
|
||||
v-for="type in types"
|
||||
|
|
|
@ -11,22 +11,22 @@
|
|||
|
||||
<div v-if=" ! addons" class="tw-mg-b-1 tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1" />
|
||||
<div class="tw-checkbox tw-relative tw-tooltip__container">
|
||||
<div class="ffz-checkbox tw-relative tw-tooltip__container">
|
||||
<input
|
||||
id="nonversioned"
|
||||
ref="nonversioned"
|
||||
v-model="nonversioned"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
>
|
||||
|
||||
<label for="nonversioned" class="tw-checkbox__label">
|
||||
<label for="nonversioned" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('home.changelog.show-nonversioned', 'Include non-versioned commits.') }}
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div class="tw-tooltip tw-balloon--md tw-tooltip--wrap tw-tooltip--down tw-tooltip--align-right">
|
||||
<div class="tw-tooltip ffz-balloon--md tw-tooltip--wrap tw-tooltip--down tw-tooltip--align-right">
|
||||
{{ t('home.changelog.about-nonversioned', 'Non-versioned commits are commits to the FrankerFaceZ repository not associated with a release build. They typically represent maintenance or contributions from the community that will be included in a subsequent release.') }}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -58,7 +58,7 @@
|
|||
>
|
||||
<figure
|
||||
v-if="commit.author.avatar_url"
|
||||
class="tw-avatar tw-avatar--size-20 tw-mg-r-05"
|
||||
class="ffz-avatar ffz-avatar--size-20 tw-mg-r-05"
|
||||
>
|
||||
<img
|
||||
:src="commit.author.avatar_url"
|
||||
|
|
|
@ -12,153 +12,153 @@
|
|||
<div class="tw-flex tw-flex-wrap tw-align-items-center ffz--inline">
|
||||
{{ t('setting.actions.preview', 'Preview:') }}
|
||||
|
||||
<div class="tw-pd-x-1 tw-checkbox">
|
||||
<div class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="as_mod"
|
||||
ref="as_mod"
|
||||
:checked="is_moderator"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="as_mod" class="tw-checkbox__label">
|
||||
<label for="as_mod" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.mod', 'As Moderator') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="has_msg" class="tw-pd-x-1 tw-checkbox">
|
||||
<div v-if="has_msg" class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="is_deleted"
|
||||
ref="is_deleted"
|
||||
:checked="is_deleted"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="is_deleted" class="tw-checkbox__label">
|
||||
<label for="is_deleted" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.deleted', 'Deleted Message') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="has_icons" class="tw-pd-x-1 tw-checkbox">
|
||||
<div v-if="has_icons" class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="with_mod_icons"
|
||||
ref="with_mod_icons"
|
||||
:checked="with_mod_icons"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="with_mod_icons" class="tw-checkbox__label">
|
||||
<label for="with_mod_icons" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.mod_icons', 'With Mod Icons') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="has_mode" class="tw-pd-x-1 tw-checkbox">
|
||||
<div v-if="has_mode" class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="with_slow"
|
||||
ref="with_slow"
|
||||
:checked="with_slow"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="with_slow" class="tw-checkbox__label">
|
||||
<label for="with_slow" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.slow', 'Slow Mode') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="has_mode" class="tw-pd-x-1 tw-checkbox">
|
||||
<div v-if="has_mode" class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="with_emote"
|
||||
ref="with_emote"
|
||||
:checked="with_emote"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="with_emote" class="tw-checkbox__label">
|
||||
<label for="with_emote" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.emote-only', 'Emote-Only') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="has_mode" class="tw-pd-x-1 tw-checkbox">
|
||||
<div v-if="has_mode" class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="with_subs"
|
||||
ref="with_subs"
|
||||
:checked="with_subs"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="with_subs" class="tw-checkbox__label">
|
||||
<label for="with_subs" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.subs', 'Subs Only') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="has_mode" class="tw-pd-x-1 tw-checkbox">
|
||||
<div v-if="has_mode" class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="with_followers"
|
||||
ref="with_followers"
|
||||
:checked="with_followers"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="with_followers" class="tw-checkbox__label">
|
||||
<label for="with_followers" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.followers', 'Followers-Only') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div v-if="has_mode" class="tw-pd-x-1 tw-checkbox">
|
||||
<div v-if="has_mode" class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="with_r9k"
|
||||
ref="with_r9k"
|
||||
:checked="with_r9k"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="with_r9k" class="tw-checkbox__label">
|
||||
<label for="with_r9k" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.r9k', 'R9k Mode') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="tw-pd-x-1 tw-checkbox">
|
||||
<div class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="show_all"
|
||||
ref="show_all"
|
||||
:checked="show_all"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onPreview"
|
||||
>
|
||||
|
||||
<label for="show_all" class="tw-checkbox__label">
|
||||
<label for="show_all" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.actions.preview.all', 'Show All') }}
|
||||
</span>
|
||||
|
@ -230,7 +230,7 @@
|
|||
<input
|
||||
ref="paste"
|
||||
:placeholder="t('setting.paste-json.json', '[json]')"
|
||||
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
@keydown.enter="addFromJSON"
|
||||
>
|
||||
<button
|
||||
|
@ -245,7 +245,7 @@
|
|||
</div>
|
||||
<div v-else class="tw-pd-y-1">
|
||||
<button
|
||||
class="tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-full-width"
|
||||
class="ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive tw-full-width"
|
||||
@click="add_pasting = true"
|
||||
>
|
||||
<div class="tw-flex tw-align-items-center tw-pd-y-05 tw-pd-x-1">
|
||||
|
@ -264,7 +264,7 @@
|
|||
v-else
|
||||
:key="idx"
|
||||
:disabled="preset.disabled"
|
||||
class="tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-full-width"
|
||||
class="ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive tw-full-width"
|
||||
@click="add(preset.value)"
|
||||
>
|
||||
<div class="tw-flex tw-align-items-center tw-pd-y-05 tw-pd-x-1">
|
||||
|
|
|
@ -16,16 +16,16 @@
|
|||
<div
|
||||
v-for="(type, key) in types"
|
||||
:key="key"
|
||||
class="tw-checkbox tw-relative tw-mg-y-05"
|
||||
class="ffz-checkbox tw-relative tw-mg-y-05"
|
||||
>
|
||||
<input
|
||||
:id="key"
|
||||
:ref="key"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
>
|
||||
|
||||
<label :for="key" class="tw-checkbox__label">
|
||||
<label :for="key" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t(`setting.clear.opt.${key}`, type.label || key) }}
|
||||
</span>
|
||||
|
@ -41,7 +41,7 @@
|
|||
<input
|
||||
v-model="entered"
|
||||
type="text"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-input tw-pd-x-1 tw-pd-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<input
|
||||
ref="code"
|
||||
type="text"
|
||||
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"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
@keydown.enter="enterCode"
|
||||
|
@ -31,7 +31,7 @@
|
|||
</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"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||
@change="onSort"
|
||||
>
|
||||
<option :selected="sort_by === 0">
|
||||
|
@ -44,16 +44,16 @@
|
|||
</div>
|
||||
<div class="tw-mg-b-2 tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1" />
|
||||
<div class="tw-checkbox tw-relative">
|
||||
<div class="ffz-checkbox tw-relative">
|
||||
<input
|
||||
id="unused"
|
||||
ref="unused"
|
||||
v-model="unused"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
>
|
||||
|
||||
<label for="unused" class="tw-checkbox__label">
|
||||
<label for="unused" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('setting.experiments.show-unused', 'Display unused experiments.') }}
|
||||
</span>
|
||||
|
@ -90,7 +90,7 @@
|
|||
<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"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<option
|
||||
|
@ -169,7 +169,7 @@
|
|||
<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"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-x-05"
|
||||
@change="onTwitchChange($event)"
|
||||
>
|
||||
<option
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<select
|
||||
v-once
|
||||
ref="add_box"
|
||||
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-select"
|
||||
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-select"
|
||||
>
|
||||
<option
|
||||
v-for="(filter, key) in filters"
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<select
|
||||
id="selector"
|
||||
ref="selector"
|
||||
class="tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||
class="tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||
@change="onSelectChange"
|
||||
>
|
||||
<option
|
||||
|
@ -27,7 +27,7 @@
|
|||
<input
|
||||
ref="text"
|
||||
:disabled="! isCustomURL"
|
||||
class="ffz-mg-t-1p tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="ffz-mg-t-1p tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
@blur="updateText"
|
||||
@input="onTextChange"
|
||||
>
|
||||
|
@ -37,34 +37,34 @@
|
|||
<div class="tw-flex tw-mg-b-1">
|
||||
<div class="tw-flex-grow-1" />
|
||||
|
||||
<div class="tw-pd-x-1 tw-checkbox">
|
||||
<div class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="force_media"
|
||||
ref="force_media"
|
||||
:checked="force_media"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onCheck"
|
||||
>
|
||||
|
||||
<label for="force_media" class="tw-checkbox__label">
|
||||
<label for="force_media" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('debug.link-provider.allow.media', 'Allow Media') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="tw-pd-x-1 tw-checkbox">
|
||||
<div class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="force_unsafe"
|
||||
ref="force_unsafe"
|
||||
:checked="force_unsafe"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onCheck"
|
||||
>
|
||||
|
||||
<label for="force_unsafe" class="tw-checkbox__label">
|
||||
<label for="force_unsafe" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('debug.link-provider.allow.unsafe', 'Allow NSFW') }}
|
||||
</span>
|
||||
|
@ -104,17 +104,17 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<div class="tw-pd-x-1 tw-checkbox">
|
||||
<div class="tw-pd-x-1 ffz-checkbox">
|
||||
<input
|
||||
id="force_tooltip"
|
||||
ref="force_tooltip"
|
||||
:checked="force_tooltip"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onTooltip"
|
||||
>
|
||||
|
||||
<label for="force_tooltip" class="tw-checkbox__label">
|
||||
<label for="force_tooltip" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('debug.link-provider.force-tooltip', 'Force Tooltip') }}
|
||||
</span>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="tw-search-input">
|
||||
<label for="ffz-main-menu.search" class="tw-hide-accessible">{{ t('main-menu.search', 'Search Settings') }}</label>
|
||||
<div class="tw-relative">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height ffz-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<figure class="ffz-i-search" />
|
||||
</div>
|
||||
<input
|
||||
|
@ -19,7 +19,7 @@
|
|||
v-model="query"
|
||||
:placeholder="t('main-menu.search', 'Search Settings')"
|
||||
type="search"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-3 tw-pd-r-1 tw-pd-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-l-3 tw-pd-r-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
id="ffz:editor:name"
|
||||
ref="name"
|
||||
v-model="name"
|
||||
class="tw-full-width tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="tw-full-width tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
>
|
||||
</div>
|
||||
|
||||
|
@ -111,7 +111,7 @@
|
|||
id="ffz:editor:description"
|
||||
ref="desc"
|
||||
v-model="desc"
|
||||
class="tw-full-width tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="tw-full-width tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
ref="button"
|
||||
:class="{active: opened}"
|
||||
tabindex="0"
|
||||
class="tw-c-background-alt tw-block tw-border tw-border-radius-medium tw-font-size-6 tw-full-width tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||
class="tw-c-background-alt tw-block tw-border 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"
|
||||
@keyup.up.stop.prevent="focusShow"
|
||||
@keyup.left.stop.prevent="focusShow"
|
||||
@keyup.down.stop.prevent="focusShow"
|
||||
|
@ -18,7 +18,7 @@
|
|||
<div
|
||||
v-if="opened"
|
||||
v-on-clickaway="hide"
|
||||
class="tw-balloon tw-block tw-balloon--lg tw-balloon--down tw-balloon--left tw-z-above"
|
||||
class="ffz-balloon tw-block ffz-balloon--lg ffz-balloon--down ffz-balloon--left tw-z-above"
|
||||
>
|
||||
<div
|
||||
class="ffz--profile-list tw-elevation-2 tw-c-background-alt"
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--provider tw-pd-t-05">
|
||||
<div v-if="not_www" class="tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-b-1">
|
||||
<h3 class="ffz-i-attention">
|
||||
{{ t('setting.provider.warn-domain.title', 'You\'re far from home!') }}
|
||||
</h3>
|
||||
<div>
|
||||
<markdown :source="t('setting.provider.warn-domain.desc', 'You are currently changing settings for a sub-domain of Twitch. Any changes here will only affect this sub-domain. You probably want to change this from Twitch\'s main website, and not here.')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tw-c-background-accent tw-c-text-overlay tw-pd-1 tw-mg-b-1">
|
||||
<h3 class="ffz-i-attention">
|
||||
{{ t('setting.provider.warn.title', 'Be careful!') }}
|
||||
|
@ -14,18 +23,18 @@
|
|||
</section>
|
||||
|
||||
<div class="ffz-options">
|
||||
<div v-for="val of providers" :key="val.key" class="tw-pd-b-1 tw-radio ffz-radio-top">
|
||||
<div v-for="val of providers" :key="val.key" class="tw-pd-b-1 ffz-radio ffz-radio-top">
|
||||
<input
|
||||
:id="'ffz--provider-opt-' + val.key"
|
||||
v-model="selected"
|
||||
:value="val.key"
|
||||
name="ffz--provider-opt"
|
||||
type="radio"
|
||||
class="tw-radio__input"
|
||||
class="ffz-radio__input"
|
||||
>
|
||||
<label
|
||||
:for="'ffz--provider-opt-' + val.key"
|
||||
class="tw-block tw-radio__label"
|
||||
class="tw-block ffz-radio__label"
|
||||
>
|
||||
<div class="tw-mg-l-1">
|
||||
<div>
|
||||
|
@ -51,20 +60,23 @@
|
|||
</div>
|
||||
|
||||
<div class="tw-border-t tw-pd-t-1">
|
||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||
<input id="transfer" ref="transfer" checked type="checkbox" class="tw-checkbox__input">
|
||||
<label for="transfer" class="tw-checkbox__label">
|
||||
<div v-if="canTransfer" class="tw-flex tw-align-items-center ffz-checkbox">
|
||||
<input id="transfer" ref="transfer" checked type="checkbox" class="ffz-checkbox__input">
|
||||
<label for="transfer" class="ffz-checkbox__label">
|
||||
<div class="tw-mg-l-1">
|
||||
{{ t('setting.provider.transfer', 'Transfer my settings when switching provider.') }}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<section class="tw-c-text-alt-2" style="padding-left:2.5rem">
|
||||
<section v-if="canTransfer" class="tw-c-text-alt-2 tw-pd-b-05" style="padding-left:2.5rem">
|
||||
<markdown :source="t('setting.provider.transfer.desc', '**Note:** It is recommended to leave this enabled unless you know what you\'re doing.')" />
|
||||
</section>
|
||||
<div class="tw-mg-t-1 tw-flex tw-align-items-center tw-checkbox">
|
||||
<input id="backup" ref="backup" v-model="backup" type="checkbox" class="tw-checkbox__input">
|
||||
<label for="backup" class="tw-checkbox__label">
|
||||
<div v-else class="tw-flex tw-align-items-center" style="padding-left:2.5rem">
|
||||
{{ t('setting.provider.no-transfer', 'Automatically transfering settings from your current provider to the selected provider is not allowed. Please use Backup and Restore.') }}
|
||||
</div>
|
||||
<div class="tw-mg-t-1 tw-flex tw-align-items-center ffz-checkbox">
|
||||
<input id="backup" ref="backup" v-model="backup" type="checkbox" class="ffz-checkbox__input">
|
||||
<label for="backup" class="ffz-checkbox__label">
|
||||
<div class="tw-mg-l-1">
|
||||
{{ t('setting.provider.backup', 'Yes, I made a backup.') }}
|
||||
</div>
|
||||
|
@ -97,33 +109,41 @@ export default {
|
|||
data() {
|
||||
const ffz = this.context.getFFZ(),
|
||||
settings = ffz.resolve('settings'),
|
||||
providers = [];
|
||||
providers = [],
|
||||
transfers = {};
|
||||
|
||||
for(const [key, val] of Object.entries(settings.getProviders())) {
|
||||
const prov = {
|
||||
key,
|
||||
priority: val.priority || 0,
|
||||
has_data: null,
|
||||
has_blobs: val.supportsBlobs,
|
||||
has_trans: val.allowTransfer,
|
||||
i18n_key: `setting.provider.${key}.title`,
|
||||
title: val.title || key,
|
||||
desc_i18n_key: val.description ? `setting.provider.${key}.desc` : null,
|
||||
description: val.description
|
||||
};
|
||||
|
||||
transfers[key] = val.allowTransfer;
|
||||
|
||||
if ( val.supported() )
|
||||
Promise.resolve(val.hasContent()).then(v => {
|
||||
prov.has_data = v;
|
||||
});
|
||||
|
||||
providers.push(prov);
|
||||
|
||||
}
|
||||
|
||||
providers.sort((a,b) => b.priority - a.priority);
|
||||
|
||||
const current = settings.getActiveProvider();
|
||||
|
||||
return {
|
||||
backup: false,
|
||||
not_www: window.location.host !== 'www.twitch.tv',
|
||||
providers,
|
||||
transfers,
|
||||
current,
|
||||
selected: current
|
||||
}
|
||||
|
@ -132,6 +152,10 @@ export default {
|
|||
computed: {
|
||||
enabled() {
|
||||
return this.selected !== this.current && this.backup
|
||||
},
|
||||
|
||||
canTransfer() {
|
||||
return this.transfers[this.selected] && this.transfers[this.current]
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
v-model="edit_data.text"
|
||||
:placeholder="adding ? t('setting.reasons.add-placeholder', 'Add a new reason') : edit_data.text"
|
||||
type="text"
|
||||
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"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
>
|
||||
|
|
|
@ -3,17 +3,17 @@
|
|||
:class="{inherits: isInherited, default: isDefault}"
|
||||
class="ffz--widget ffz--checkbox"
|
||||
>
|
||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||
<div class="tw-flex tw-align-items-center ffz-checkbox">
|
||||
<input
|
||||
:id="item.full_key"
|
||||
ref="control"
|
||||
:checked="value"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
@change="onChange"
|
||||
>
|
||||
|
||||
<label :for="item.full_key" class="tw-checkbox__label">
|
||||
<label :for="item.full_key" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t(item.i18n_key, item.title) }}
|
||||
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<select
|
||||
:id="item.full_key"
|
||||
ref="control"
|
||||
class="tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||
class="tw-border-top-left-radius-medium tw-border-top-right-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05"
|
||||
@change="onChange"
|
||||
>
|
||||
<option
|
||||
|
@ -31,7 +31,7 @@
|
|||
ref="text"
|
||||
:value="value"
|
||||
:disabled="! isCustom"
|
||||
class="ffz-mg-t-1p tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="ffz-mg-t-1p tw-border-bottom-left-radius-medium tw-border-bottom-right-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
@change="onTextChange"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template lang="html">
|
||||
<div class="tw-input">
|
||||
<div class="ffz-input">
|
||||
<header>
|
||||
{{ t(item.i18n_key, item.title) }}
|
||||
<span v-if="unseen" class="tw-pill">{{ t('setting.new', 'New') }}</span>
|
||||
|
@ -19,11 +19,11 @@
|
|||
:name="item.full_key"
|
||||
:value="i.value"
|
||||
type="radio"
|
||||
class="tw-radio__input"
|
||||
class="ffz-radio__input"
|
||||
>
|
||||
<label
|
||||
:for="item.full_key + idx"
|
||||
class="tw-pd-y-05 tw-radio__label"
|
||||
class="tw-pd-y-05 ffz-radio__label"
|
||||
>
|
||||
{{ t(i.i18n_key, i.title, i) }}
|
||||
</label>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<select
|
||||
:id="item.full_key"
|
||||
ref="control"
|
||||
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-05"
|
||||
class="tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-05"
|
||||
@change="onChange"
|
||||
>
|
||||
<template v-for="i in nested_data">
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-mg-05 tw-input"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-mg-05 ffz-input"
|
||||
@change="onChange"
|
||||
>
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
v-model="edit_data.v"
|
||||
:placeholder="adding ? t('setting.terms.add-placeholder', 'Add a new term') : edit_data.v"
|
||||
type="text"
|
||||
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"
|
||||
class="tw-block tw-full-width tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
>
|
||||
|
@ -40,7 +40,7 @@
|
|||
<select
|
||||
v-else
|
||||
v-model="edit_data.t"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 ffz-min-width-unset"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 ffz-min-width-unset"
|
||||
>
|
||||
<option value="text">
|
||||
{{ t('setting.terms.type.text', 'Text') }}
|
||||
|
|
|
@ -210,7 +210,7 @@ export default class Metadata extends Module {
|
|||
return false;
|
||||
} : null;
|
||||
|
||||
tip.element.classList.add('tw-balloon--lg');
|
||||
tip.element.classList.add('ffz-balloon--lg');
|
||||
|
||||
return (<div>
|
||||
<div class="tw-pd-b-1 tw-mg-b-1 tw-border-b tw-semibold">
|
||||
|
@ -220,7 +220,7 @@ export default class Metadata extends Module {
|
|||
</div>
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<input
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input tw-full-width"
|
||||
class="tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input tw-full-width"
|
||||
type="text"
|
||||
value={url}
|
||||
onFocus={e => e.target.select()}
|
||||
|
@ -737,10 +737,10 @@ export default class Metadata extends Module {
|
|||
live: false,
|
||||
html: true,
|
||||
|
||||
tooltipClass: 'ffz-metadata-balloon tw-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
|
||||
tooltipClass: 'ffz-metadata-balloon ffz-balloon tw-block tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base',
|
||||
// Hide the arrow for now, until we re-do our CSS to make it render correctly.
|
||||
arrowClass: 'tw-balloon__tail tw-overflow-hidden tw-absolute',
|
||||
arrowInner: 'tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
|
||||
arrowClass: 'ffz-balloon__tail tw-overflow-hidden tw-absolute',
|
||||
arrowInner: 'ffz-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-c-background-base tw-absolute',
|
||||
innerClass: 'tw-pd-1',
|
||||
|
||||
popper: {
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
<textarea
|
||||
ref="editor"
|
||||
v-model="value"
|
||||
:class="{'tw-textarea--error': ! valid}"
|
||||
class="tw-block tw-font-size-6 tw-full-width tw-textarea"
|
||||
:class="{'ffz-textarea--error': ! valid}"
|
||||
class="tw-block tw-font-size-6 tw-full-width ffz-textarea"
|
||||
@input="onInput"
|
||||
@blur="onBlur"
|
||||
@focus="open = true"
|
||||
|
@ -86,7 +86,7 @@
|
|||
:id="`ui-ctx:${entry.key}:${val.key}`"
|
||||
:type="val.is_number ? 'number' : 'text'"
|
||||
:value="val.value"
|
||||
class="tw-full-width tw-block tw-border-radius-medium tw-font-size-6 tw-input tw-pd-x-1 tw-pd-y-05"
|
||||
class="tw-full-width tw-block tw-border-radius-medium tw-font-size-6 ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
@input="updateContext(val.key, $event)"
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<div class="tw-search-input">
|
||||
<label for="ffz-main-menu.search" class="tw-hide-accessible">{{ t('i18n.ui.search', 'Search Strings') }}</label>
|
||||
<div class="tw-relative">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height ffz-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<figure class="ffz-i-search" />
|
||||
</div>
|
||||
<input
|
||||
|
@ -17,7 +17,7 @@
|
|||
v-model="query"
|
||||
:placeholder="t('i18n.ui.search', 'Search Strings')"
|
||||
type="search"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-3 tw-pd-r-1 tw-pd-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-l-3 tw-pd-r-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
|
@ -99,7 +99,7 @@
|
|||
ref="pager"
|
||||
:value="page"
|
||||
:max="pages"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-input tw-pd-x-1 tw-pd-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
type="number"
|
||||
min="1"
|
||||
@keydown.enter="closePage"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<div class="tw-flex tw-flex-column tw-full-height tw-full-width viewer-card__overlay">
|
||||
<div class="tw-align-center tw-align-items-start tw-c-background-overlay tw-c-text-overlay tw-flex tw-flex-grow-1 tw-flex-row tw-full-width tw-justify-content-start tw-pd-05 tw-relative viewer-card__banner">
|
||||
<div class="tw-mg-l-05 tw-mg-y-05 tw-inline-flex viewer-card-drag-cancel">
|
||||
<figure class="tw-avatar tw-avatar--size-50">
|
||||
<figure class="ffz-avatar ffz-avatar--size-50">
|
||||
<img
|
||||
v-if="loaded"
|
||||
:src="user.profileImageURL"
|
||||
|
|
151
src/player.js
Normal file
151
src/player.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
'use strict';
|
||||
|
||||
import RavenLogger from './raven';
|
||||
|
||||
import Logger from 'utilities/logging';
|
||||
import Module from 'utilities/module';
|
||||
import { timeout } from 'utilities/object';
|
||||
|
||||
import {DEBUG} from 'utilities/constants';
|
||||
|
||||
import SettingsManager from './settings/index';
|
||||
import ExperimentManager from './experiments';
|
||||
import {TranslationManager} from './i18n';
|
||||
|
||||
class FFZPlayer extends Module {
|
||||
constructor() {
|
||||
super();
|
||||
const start_time = performance.now(),
|
||||
VER = FFZPlayer.version_info;
|
||||
|
||||
FFZPlayer.instance = this;
|
||||
|
||||
this.name = 'ffz_player';
|
||||
this.__state = 0;
|
||||
this.__modules.core = this;
|
||||
|
||||
// ========================================================================
|
||||
// Error Reporting and Logging
|
||||
// ========================================================================
|
||||
|
||||
this.inject('raven', RavenLogger);
|
||||
|
||||
this.log = new Logger(null, null, null, this.raven);
|
||||
this.log.label = 'FFZPlayer';
|
||||
this.log.init = true;
|
||||
|
||||
this.core_log = this.log.get('core');
|
||||
|
||||
this.log.info(`FrankerFaceZ Standalone Player v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`);
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Core Systems
|
||||
// ========================================================================
|
||||
|
||||
this.inject('settings', SettingsManager);
|
||||
this.inject('experiments', ExperimentManager);
|
||||
this.inject('i18n', TranslationManager);
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Startup
|
||||
// ========================================================================
|
||||
|
||||
this.enable().then(() => {
|
||||
const duration = performance.now() - start_time;
|
||||
this.core_log.info(`Initialization complete in ${duration.toFixed(5)}ms.`);
|
||||
this.log.init = false;
|
||||
}).catch(err => {
|
||||
this.core_log.error(`An error occurred during initialization.`, err);
|
||||
this.log.init = false;
|
||||
});
|
||||
}
|
||||
|
||||
static get() {
|
||||
return FFZPlayer.instance;
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Generate Log
|
||||
// ========================================================================
|
||||
|
||||
async generateLog() {
|
||||
const promises = [];
|
||||
for(const key in this.__modules) { // eslint-disable-line guard-for-in
|
||||
const module = this.__modules[key];
|
||||
if ( module instanceof Module && module.generateLog && module != this )
|
||||
promises.push((async () => {
|
||||
try {
|
||||
return [
|
||||
key,
|
||||
await timeout(Promise.resolve(module.generateLog()), 5000)
|
||||
];
|
||||
} catch(err) {
|
||||
return [
|
||||
key,
|
||||
`Error: ${err}`
|
||||
]
|
||||
}
|
||||
})());
|
||||
}
|
||||
|
||||
const out = await Promise.all(promises);
|
||||
|
||||
if ( this.log.captured_init && this.log.captured_init.length > 0 ) {
|
||||
const logs = [];
|
||||
for(const msg of this.log.captured_init) {
|
||||
const time = dayjs(msg.time).locale('en').format('H:mm:ss');
|
||||
logs.push(`[${time}] ${msg.level} | ${msg.category || 'core'}: ${msg.message}`);
|
||||
}
|
||||
|
||||
out.unshift(['initialization', logs.join('\n')]);
|
||||
}
|
||||
|
||||
return out.map(x => `${x[0]}
|
||||
-------------------------------------------------------------------------------
|
||||
${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`).join('\n\n');
|
||||
}
|
||||
|
||||
async onEnable() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
FFZPlayer.Logger = Logger;
|
||||
|
||||
const VER = FFZPlayer.version_info = {
|
||||
major: __version_major__,
|
||||
minor: __version_minor__,
|
||||
revision: __version_patch__,
|
||||
extra: __version_prerelease__?.length && __version_prerelease__[0],
|
||||
commit: __git_commit__,
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
`${VER.major}.${VER.minor}.${VER.revision}${VER.extra || ''}${DEBUG ? '-dev' : ''}`
|
||||
}
|
||||
|
||||
FFZPlayer.utilities = {
|
||||
addon: require('utilities/addon'),
|
||||
color: require('utilities/color'),
|
||||
constants: require('utilities/constants'),
|
||||
dialog: require('utilities/dialog'),
|
||||
dom: require('utilities/dom'),
|
||||
events: require('utilities/events'),
|
||||
fontAwesome: require('utilities/font-awesome'),
|
||||
graphql: require('utilities/graphql'),
|
||||
logging: require('utilities/logging'),
|
||||
module: require('utilities/module'),
|
||||
object: require('utilities/object'),
|
||||
time: require('utilities/time'),
|
||||
tooltip: require('utilities/tooltip'),
|
||||
i18n: require('utilities/translation-core'),
|
||||
dayjs: require('dayjs'),
|
||||
popper: require('popper.js').default
|
||||
}
|
||||
|
||||
|
||||
window.FFZPlayer = FFZPlayer;
|
||||
window.ffz_player = new FFZPlayer();
|
|
@ -61,10 +61,10 @@ export const Profiles = {
|
|||
|
||||
export const Everything = {
|
||||
label: 'Absolutely Everything',
|
||||
clear(provider, settings) {
|
||||
async clear(provider, settings) {
|
||||
provider.clear();
|
||||
if ( provider.supportsBlobs() )
|
||||
provider.clearBlobs();
|
||||
if ( provider.supportsBlobs )
|
||||
await provider.clearBlobs();
|
||||
|
||||
settings.loadProfiles();
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
<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 tw-checkbox tw-mg-y-05">
|
||||
<div class="tw-flex tw-align-items-center ffz-checkbox tw-mg-y-05">
|
||||
<input
|
||||
:id="'enabled$' + value.id"
|
||||
v-model="value.data"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
>
|
||||
<label :for="'enabled$' + value.id" class="tw-checkbox__label">
|
||||
<label :for="'enabled$' + value.id" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t(type.i18n, type.title) }}
|
||||
</span>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
v-if="current"
|
||||
:alt="current.displayName || current.name"
|
||||
:src="current.boxArtURL"
|
||||
class="tw-avatar__img tw-image"
|
||||
class="ffz-avatar__img tw-image"
|
||||
>
|
||||
</aspect>
|
||||
</div>
|
||||
|
|
|
@ -6,13 +6,13 @@
|
|||
</label>
|
||||
|
||||
<div class="ffz--search-avatar tw-mg-x-05">
|
||||
<figure class="tw-avatar tw-avatar--size-30">
|
||||
<figure class="ffz-avatar ffz-avatar--size-30">
|
||||
<div class="tw-border-radius-rounded tw-overflow-hidden">
|
||||
<img
|
||||
v-if="current"
|
||||
:alt="current.displayName"
|
||||
:src="current.profileImageURL"
|
||||
class="tw-avatar__img tw-image"
|
||||
class="ffz-avatar__img tw-image"
|
||||
>
|
||||
</div>
|
||||
</figure>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<select
|
||||
:id="'page$' + id"
|
||||
v-model="value.data.route"
|
||||
class="tw-flex-grow-1 tw-mg-l-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-select"
|
||||
class="tw-flex-grow-1 tw-mg-l-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-select"
|
||||
>
|
||||
<option
|
||||
v-for="(_, key) in routes"
|
||||
|
@ -45,7 +45,7 @@
|
|||
<input
|
||||
:id="'page$' + id + '$part-' + part.key"
|
||||
v-model="value.data.values[part.key]"
|
||||
class="tw-mg-l-1 tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 tw-input"
|
||||
class="tw-mg-l-1 tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
: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"
|
||||
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 ffz-input"
|
||||
>
|
||||
|
||||
<label :for="'end-time$' + id" class="tw-mg-l-1">
|
||||
|
@ -24,7 +24,7 @@
|
|||
: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"
|
||||
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 ffz-input"
|
||||
>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
:id="'title$' + id"
|
||||
v-model="value.data.title"
|
||||
type="text"
|
||||
class="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"
|
||||
class="tw-flex-grow-1 tw-border-radius-medium tw-font-size-6 tw-mg-x-1 tw-pd-x-1 tw-pd-y-05 ffz-input"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
>
|
||||
|
@ -30,7 +30,7 @@
|
|||
<select
|
||||
:id="'mode$' + id"
|
||||
v-model="value.data.mode"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 ffz-min-width-unset"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 ffz-select tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 ffz-min-width-unset"
|
||||
>
|
||||
<option value="text">
|
||||
{{ t('setting.terms.type.text', 'Text') }}
|
||||
|
@ -43,14 +43,14 @@
|
|||
</option>
|
||||
</select>
|
||||
|
||||
<div class="tw-flex tw-align-items-center tw-checkbox tw-mg-l-1 tw-mg-y-05">
|
||||
<div class="tw-flex tw-align-items-center ffz-checkbox tw-mg-l-1 tw-mg-y-05">
|
||||
<input
|
||||
:id="'sensitive$' + id"
|
||||
v-model="value.data.sensitive"
|
||||
type="checkbox"
|
||||
class="ffz-min-width-unset tw-checkbox__input"
|
||||
class="ffz-min-width-unset ffz-checkbox__input"
|
||||
>
|
||||
<label :for="'sensitive$' + id" class="tw-checkbox__label tw-relative tw-tooltip__container">
|
||||
<label :for="'sensitive$' + id" class="ffz-checkbox__label tw-relative tw-tooltip__container">
|
||||
<span class="tw-mg-l-05">
|
||||
Aa
|
||||
<div class="tw-tooltip tw-tooltip--down tw-tooltip--align-right">
|
||||
|
|
|
@ -366,9 +366,13 @@ export default class SettingsManager extends Module {
|
|||
* @returns {SettingsProvider} The provider to store everything.
|
||||
*/
|
||||
async _createProvider() {
|
||||
let wanted = localStorage.ffzProvider;
|
||||
// If we should be using Cross-Origin Storage Bridge, do so.
|
||||
//if ( this.providers.cosb && this.providers.cosb.supported() )
|
||||
// return new this.providers.cosb(this);
|
||||
|
||||
let wanted = localStorage.ffzProviderv2;
|
||||
if ( wanted == null )
|
||||
wanted = localStorage.ffzProvider = await this.sniffProvider();
|
||||
wanted = localStorage.ffzProviderv2 = await this.sniffProvider();
|
||||
|
||||
if ( this.providers[wanted] ) {
|
||||
const provider = new this.providers[wanted](this);
|
||||
|
@ -434,31 +438,34 @@ export default class SettingsManager extends Module {
|
|||
// Are we transfering settings?
|
||||
if ( transfer ) {
|
||||
const new_provider = new this.providers[key](this);
|
||||
await new_provider.awaitReady();
|
||||
|
||||
old_provider.disableEvents();
|
||||
if ( new_provider.allowTransfer && old_provider.allowTransfer ) {
|
||||
old_provider.disableEvents();
|
||||
|
||||
// When transfering, we clear all existing settings.
|
||||
await new_provider.clear();
|
||||
if ( new_provider.supportsBlobs )
|
||||
await new_provider.clearBlobs();
|
||||
// When transfering, we clear all existing settings.
|
||||
await new_provider.clear();
|
||||
if ( new_provider.supportsBlobs )
|
||||
await new_provider.clearBlobs();
|
||||
|
||||
for(const [key,val] of old_provider.entries())
|
||||
new_provider.set(key, val);
|
||||
for(const [key,val] of old_provider.entries())
|
||||
new_provider.set(key, val);
|
||||
|
||||
if ( old_provider.supportsBlobs && new_provider.supportsBlobs ) {
|
||||
for(const key of await old_provider.blobKeys() ) {
|
||||
const blob = await old_provider.getBlob(key); // eslint-disable-line no-await-in-loop
|
||||
if ( blob )
|
||||
await new_provider.setBlob(key, blob); // eslint-disable-line no-await-in-loop
|
||||
if ( old_provider.supportsBlobs && new_provider.supportsBlobs ) {
|
||||
for(const key of await old_provider.blobKeys() ) {
|
||||
const blob = await old_provider.getBlob(key); // eslint-disable-line no-await-in-loop
|
||||
if ( blob )
|
||||
await new_provider.setBlob(key, blob); // eslint-disable-line no-await-in-loop
|
||||
}
|
||||
|
||||
await old_provider.clearBlobs();
|
||||
}
|
||||
|
||||
await old_provider.clearBlobs();
|
||||
old_provider.clear();
|
||||
|
||||
await old_provider.flush();
|
||||
await new_provider.flush();
|
||||
}
|
||||
|
||||
old_provider.clear();
|
||||
|
||||
await old_provider.flush();
|
||||
await new_provider.flush();
|
||||
}
|
||||
|
||||
// Change over.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import { isValidBlob, deserializeBlob, serializeBlob } from 'src/utilities/blobs';
|
||||
// ============================================================================
|
||||
// Settings Providers
|
||||
// ============================================================================
|
||||
|
@ -7,12 +8,8 @@
|
|||
import {EventEmitter} from 'utilities/events';
|
||||
import {has} from 'utilities/object';
|
||||
|
||||
const DB_VERSION = 1;
|
||||
|
||||
|
||||
export function isValidBlob(blob) {
|
||||
return blob instanceof Blob || blob instanceof File || blob instanceof ArrayBuffer || blob instanceof Uint8Array;
|
||||
}
|
||||
const DB_VERSION = 1,
|
||||
NOT_WWW = window.location.host !== 'www.twitch.tv';
|
||||
|
||||
|
||||
// ============================================================================
|
||||
|
@ -43,6 +40,7 @@ export class SettingsProvider extends EventEmitter {
|
|||
}
|
||||
|
||||
static supportsBlobs = false;
|
||||
static allowTransfer = true;
|
||||
|
||||
awaitReady() {
|
||||
if ( this.ready )
|
||||
|
@ -51,6 +49,8 @@ export class SettingsProvider extends EventEmitter {
|
|||
return Promise.reject(new Error('Not Implemented'));
|
||||
}
|
||||
|
||||
get allowTransfer() { return this.constructor.allowTransfer; }
|
||||
|
||||
broadcastTransfer() { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this
|
||||
disableEvents() { throw new Error('Not Implemented') } // eslint-disable-line class-methods-use-this
|
||||
|
||||
|
@ -932,4 +932,252 @@ export class IndexedDBProvider extends SettingsProvider {
|
|||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// CrossOriginStorageBridge
|
||||
// ============================================================================
|
||||
|
||||
export class CrossOriginStorageBridge extends SettingsProvider {
|
||||
constructor(manager) {
|
||||
super(manager);
|
||||
|
||||
this._start_time = performance.now();
|
||||
|
||||
this._rpc = new Map;
|
||||
|
||||
this._cached = new Map;
|
||||
this.resolved_ready = false;
|
||||
this.ready = false;
|
||||
this._ready_wait = null;
|
||||
|
||||
this._blobs = null;
|
||||
this._last_id = 0;
|
||||
|
||||
const frame = this.frame = document.createElement('iframe');
|
||||
frame.src = '//www.twitch.tv/p/ffz_bridge/';
|
||||
frame.id = 'ffz-settings-bridge';
|
||||
frame.style.width = 0;
|
||||
frame.style.height = 0;
|
||||
|
||||
window.addEventListener('message', this.onMessage.bind(this));
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
|
||||
|
||||
// Static Properties
|
||||
|
||||
static supported() { return NOT_WWW; }
|
||||
static hasContent() { return NOT_WWW; }
|
||||
|
||||
static key = 'cosb';
|
||||
static priority = 100;
|
||||
static title = 'Cross-Origin Storage Bridge';
|
||||
static description = 'This provider uses an `<iframe>` to synchronize storage across subdomains. Due to the `<iframe>`, this provider takes longer than others to load, but should perform roughly the same once loaded. You should be using this on non-www subdomains of Twitch unless you don\'t want your settings to automatically synchronize for some reason.';
|
||||
static supportsBlobs = true;
|
||||
static allowTransfer = false;
|
||||
|
||||
get supportsBlobs() {
|
||||
return this._blobs;
|
||||
}
|
||||
|
||||
|
||||
// Initialization
|
||||
|
||||
_resolveReady(success, data) {
|
||||
if ( this.manager )
|
||||
this.manager.log.info(`COSB ready in ${(performance.now() - this._start_time).toFixed(5)}ms`);
|
||||
|
||||
this.resolved_ready = true;
|
||||
this.ready = success;
|
||||
const waiters = this._ready_wait;
|
||||
this._ready_wait = null;
|
||||
if ( waiters )
|
||||
for(const pair of waiters)
|
||||
pair[success ? 0 : 1](data);
|
||||
}
|
||||
|
||||
awaitReady() {
|
||||
if ( this.resolved_ready ) {
|
||||
if ( this.ready )
|
||||
return Promise.resolve();
|
||||
return Promise.reject();
|
||||
}
|
||||
|
||||
return new Promise((s,f) => {
|
||||
const waiters = this._ready_wait = this._ready_wait || [];
|
||||
waiters.push([s,f]);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// Provider Methods
|
||||
|
||||
get(key, default_value) {
|
||||
return this._cached.has(key) ? this._cached.get(key) : default_value;
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
if ( value === undefined ) {
|
||||
if ( this.has(key) )
|
||||
this.delete(key);
|
||||
return;
|
||||
}
|
||||
|
||||
this._cached.set(key, value);
|
||||
this.rpc({ffz_type: 'set', key, value}).catch(err => this.manager.log.error('Error setting value', err));
|
||||
this.emit('set', key, value, false);
|
||||
}
|
||||
|
||||
delete(key) {
|
||||
this._cached.delete(key);
|
||||
this.rpc({ffz_type: 'delete', key}).catch(err => this.manager.log.error('Error deleting value', err));
|
||||
this.emit('set', key, undefined, true);
|
||||
}
|
||||
|
||||
clear() {
|
||||
const old_cache = this._cached;
|
||||
this._cached = new Map;
|
||||
for(const key of old_cache.keys())
|
||||
this.emit('changed', key, undefined, true);
|
||||
|
||||
this.rpc('clear').catch(err => this.manager.log.error('Error clearing storage', err));
|
||||
}
|
||||
|
||||
has(key) { return this._cached.has(key); }
|
||||
keys() { return this._cached.keys(); }
|
||||
entries() { return this._cached.entries(); }
|
||||
get size() { return this._cached.size; }
|
||||
|
||||
async flush() {
|
||||
await this.rpc('flush');
|
||||
}
|
||||
|
||||
|
||||
// Provider Methods: Blobs
|
||||
|
||||
async getBlob(key) {
|
||||
const msg = await this.rpc({ffz_type: 'get-blob', key});
|
||||
return msg.reply && deserializeBlob(msg.reply);
|
||||
}
|
||||
|
||||
async setBlob(key, value) {
|
||||
await this.rpc({
|
||||
ffz_type: 'set-blob',
|
||||
key,
|
||||
value: await serializeBlob(value)
|
||||
});
|
||||
}
|
||||
|
||||
async deleteBlob(key) {
|
||||
await this.rpc({
|
||||
ffz_type: 'delete-blob',
|
||||
key
|
||||
});
|
||||
}
|
||||
|
||||
async hasBlob(key) {
|
||||
const msg = await this.rpc({ffz_type: 'has-blob', key});
|
||||
return msg.reply;
|
||||
}
|
||||
|
||||
async clearBlobs() {
|
||||
await this.rpc('clear-blobs');
|
||||
}
|
||||
|
||||
async blobKeys() {
|
||||
const msg = await this.rpc('blob-keys');
|
||||
return msg.reply;
|
||||
}
|
||||
|
||||
|
||||
// CORS Communication
|
||||
|
||||
send(msg, transfer) {
|
||||
if ( typeof msg === 'string' )
|
||||
msg = {ffz_type: msg};
|
||||
|
||||
try {
|
||||
this.frame.contentWindow.postMessage(
|
||||
msg,
|
||||
'*',
|
||||
transfer ? (Array.isArray(transfer) ? transfer : [transfer]) : undefined
|
||||
);
|
||||
} catch(err) {
|
||||
this.manager.log.error('Error sending message to bridge.', err, msg, transfer);
|
||||
}
|
||||
}
|
||||
|
||||
rpc(msg, transfer) {
|
||||
const id = ++this._last_id;
|
||||
|
||||
return new Promise((s,f) => {
|
||||
this._rpc.set(id, [s,f]);
|
||||
|
||||
if ( typeof msg === 'string' )
|
||||
msg = {ffz_type: msg};
|
||||
|
||||
msg.id = id;
|
||||
|
||||
this.send(msg, transfer);
|
||||
});
|
||||
}
|
||||
|
||||
onMessage(event) {
|
||||
const msg = event.data;
|
||||
if ( ! msg || ! msg.ffz_type )
|
||||
return;
|
||||
|
||||
if ( msg.ffz_type === 'ready' )
|
||||
this.rpc('init-load').then(msg => {
|
||||
this._blobs = msg.reply.blobs;
|
||||
for(const [key, value] of Object.entries(msg.reply.values))
|
||||
this._cached.set(key, value);
|
||||
|
||||
this._resolveReady(true);
|
||||
}).catch(err => {
|
||||
this._resolveReady(false, err);
|
||||
});
|
||||
|
||||
else if ( msg.ffz_type === 'change' )
|
||||
this.onChange(msg);
|
||||
|
||||
else if ( msg.ffz_type === 'change-blob' )
|
||||
this.emit('changed-blob', msg.key, msg.deleted);
|
||||
|
||||
else if ( msg.ffz_type === 'clear-blobs' )
|
||||
this.emit('clear-blobs');
|
||||
|
||||
else if ( msg.ffz_type === 'reply' || msg.ffz_type === 'reply-error' )
|
||||
this.onReply(msg);
|
||||
|
||||
else
|
||||
this.log.warn('Unknown Message', msg.ffz_type, msg);
|
||||
}
|
||||
|
||||
onChange(msg) {
|
||||
const key = msg.key,
|
||||
value = msg.value,
|
||||
deleted = msg.deleted;
|
||||
|
||||
if ( deleted ) {
|
||||
this._cached.delete(key);
|
||||
this.emit('changed', key, undefined, true);
|
||||
} else {
|
||||
this._cached.set(key, value);
|
||||
this.emit('changed', key, value, false);
|
||||
}
|
||||
}
|
||||
|
||||
onReply(msg) {
|
||||
const id = msg.id,
|
||||
success = msg.ffz_type === 'reply',
|
||||
cbs = this._rpc.get(id);
|
||||
if ( ! cbs )
|
||||
return this.log.warn('Received reply for unknown ID', id);
|
||||
|
||||
this._rpc.delete(id);
|
||||
cbs[success ? 0 : 1](msg);
|
||||
}
|
||||
}
|
|
@ -15,9 +15,9 @@ export default class BaseSite extends Module {
|
|||
this.log.info(`Using: ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
populateModules() {
|
||||
const ctx = require.context('site/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/);
|
||||
const modules = this.populate(ctx, this.log);
|
||||
async populateModules() {
|
||||
const ctx = await require.context('site/modules', true, /(?:^(?:\.\/)?[^/]+|index)\.jsx?$/);
|
||||
const modules = await this.populate(ctx, this.log);
|
||||
this.log.info(`Loaded descriptions of ${Object.keys(modules).length} modules.`);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,8 +42,8 @@ export default class Twilight extends BaseSite {
|
|||
this._dom_updates = [];
|
||||
}
|
||||
|
||||
onLoad() {
|
||||
this.populateModules();
|
||||
async onLoad() {
|
||||
await this.populateModules();
|
||||
|
||||
this.web_munch.known(Twilight.KNOWN_MODULES);
|
||||
|
||||
|
|
|
@ -429,7 +429,7 @@ export default class EmoteMenu extends Module {
|
|||
return (<button
|
||||
key={data.code}
|
||||
data-tone={tone}
|
||||
class="tw-interactive tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive tw-pd-y-05 tw-pd-x-2"
|
||||
class="tw-interactive tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive tw-pd-y-05 tw-pd-x-2"
|
||||
onClick={this.clickTone}
|
||||
>
|
||||
{this.renderEmoji(data)}
|
||||
|
@ -446,7 +446,7 @@ export default class EmoteMenu extends Module {
|
|||
|
||||
const tones = Object.entries(emoji.variants).map(([tone, emoji]) => this.renderTone(emoji, tone));
|
||||
|
||||
return (<div class="tw-absolute tw-balloon tw-balloon--up tw-balloon--right tw-balloon tw-block">
|
||||
return (<div class="tw-absolute ffz-balloon ffz-balloon--up ffz-balloon--right ffz-balloon tw-block">
|
||||
<div class="tw-border-b tw-border-l tw-border-r tw-border-t tw-border-radius-medium tw-c-background-base tw-elevation-1">
|
||||
{this.renderTone(emoji, null)}
|
||||
{tones}
|
||||
|
@ -903,7 +903,7 @@ export default class EmoteMenu extends Module {
|
|||
const padding = t.chat.context.get('chat.emote-menu.reduced-padding');
|
||||
|
||||
return (<div
|
||||
class={`tw-balloon tw-balloon--md tw-balloon--up tw-balloon--right tw-block tw-absolute ffz--emote-picker${padding ? ' reduced-padding' : ''}`}
|
||||
class={`ffz-balloon ffz-balloon--md ffz-balloon--up ffz-balloon--right tw-block tw-absolute ffz--emote-picker${padding ? ' reduced-padding' : ''}`}
|
||||
data-a-target="emote-picker"
|
||||
>
|
||||
<div class="tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base">
|
||||
|
@ -2092,7 +2092,7 @@ export default class EmoteMenu extends Module {
|
|||
return (<div class="tw-block">
|
||||
<div class="tw-absolute tw-attached tw-attached--right tw-attached--up">
|
||||
<div
|
||||
class={`tw-balloon tw-balloon--auto tw-inline-block tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-2 ffz--emote-picker${padding ? ' reduced-padding' : ''}`}
|
||||
class={`ffz-balloon ffz-balloon--auto tw-inline-block tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-2 ffz--emote-picker${padding ? ' reduced-padding' : ''}`}
|
||||
data-a-target="emote-picker"
|
||||
role="dialog"
|
||||
>
|
||||
|
@ -2128,7 +2128,7 @@ export default class EmoteMenu extends Module {
|
|||
<div class="tw-flex">
|
||||
<input
|
||||
type="text"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-x-1 tw-pd-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-x-1 tw-pd-y-05"
|
||||
placeholder={
|
||||
is_emoji ?
|
||||
t.i18n.t('emote-menu.search-emoji', 'Search for Emoji') :
|
||||
|
@ -2170,7 +2170,7 @@ export default class EmoteMenu extends Module {
|
|||
<div class="emote-picker__tab-nav-container tw-flex tw-border-t tw-c-background-alt">
|
||||
{! visibility && <div class={`emote-picker-tab-item${tab === 'fav' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'fav' ? ' tw-interactable--selected' : ''}`}
|
||||
class={`ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'fav' ? ' ffz-interactable--selected' : ''}`}
|
||||
id="emote-picker__fav"
|
||||
data-tab="fav"
|
||||
data-tooltip-type="html"
|
||||
|
@ -2184,7 +2184,7 @@ export default class EmoteMenu extends Module {
|
|||
</div>}
|
||||
{this.state.has_channel_tab && <div class={`emote-picker-tab-item${tab === 'channel' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'channel' ? ' tw-interactable--selected' : ''}`}
|
||||
class={`ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'channel' ? ' ffz-interactable--selected' : ''}`}
|
||||
id="emote-picker__channel"
|
||||
data-tab="channel"
|
||||
data-tooltip-type="html"
|
||||
|
@ -2198,7 +2198,7 @@ export default class EmoteMenu extends Module {
|
|||
</div>}
|
||||
<div class={`emote-picker-tab-item${tab === 'all' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'all' ? ' tw-interactable--selected' : ''}`}
|
||||
class={`ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'all' ? ' ffz-interactable--selected' : ''}`}
|
||||
id="emote-picker__all"
|
||||
data-tab="all"
|
||||
data-tooltip-type="html"
|
||||
|
@ -2212,7 +2212,7 @@ export default class EmoteMenu extends Module {
|
|||
</div>
|
||||
{! visibility && this.state.has_emoji_tab && <div class={`emote-picker-tab-item${tab === 'emoji' ? ' emote-picker-tab-item--active' : ''} tw-relative`}>
|
||||
<button
|
||||
class={`ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive${tab === 'emoji' ? ' tw-interactable--selected' : ''}`}
|
||||
class={`ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive${tab === 'emoji' ? ' ffz-interactable--selected' : ''}`}
|
||||
id="emote-picker__emoji"
|
||||
data-tab="emoji"
|
||||
data-tooltip-type="html"
|
||||
|
@ -2227,7 +2227,7 @@ export default class EmoteMenu extends Module {
|
|||
<div class="tw-flex-grow-1" />
|
||||
<div class="emote-picker-tab-item tw-relative">
|
||||
<button
|
||||
class="ffz-tooltip tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
|
||||
class="ffz-tooltip tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||
data-tooltip-type="html"
|
||||
data-title={t.i18n.t('emote-menu.settings', 'Open Settings')}
|
||||
onClick={this.clickSettings}
|
||||
|
|
|
@ -227,7 +227,7 @@ export default class RichContent extends Module {
|
|||
const tooltip = this.props.card_tooltip && this.state.full && ! this.props.force_full;
|
||||
if ( this.state.url ) {
|
||||
content = (<a
|
||||
class={`${tooltip ? 'ffz-tooltip ' : ''}${this.state.accent ? 'ffz-accent-card ' : ''}${this.state.error ? '': 'tw-interactable--hover-enabled '}tw-block tw-border-radius-medium tw-full-width tw-interactable tw-interactable--default tw-interactive`}
|
||||
class={`${tooltip ? 'ffz-tooltip ' : ''}${this.state.accent ? 'ffz-accent-card ' : ''}${this.state.error ? '': 'ffz-interactable--hover-enabled '}tw-block tw-border-radius-medium tw-full-width ffz-interactable ffz-interactable--default tw-interactive`}
|
||||
data-tooltip-type="link"
|
||||
data-url={this.state.url}
|
||||
data-is-mail={false}
|
||||
|
|
|
@ -57,7 +57,7 @@ export default class SettingsMenu extends Module {
|
|||
this.ffzPauseClick = () => this.setState({ffzPauseMenu: ! this.state.ffzPauseMenu});
|
||||
|
||||
val.props.children.push(<div class="tw-full-width tw-relative">
|
||||
<button class="tw-block tw-border-radius-medium tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive" onClick={this.ffzSettingsClick}>
|
||||
<button class="tw-block tw-border-radius-medium tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive" onClick={this.ffzSettingsClick}>
|
||||
<div class="tw-align-items-center tw-flex tw-pd-05 tw-relative">
|
||||
<div class="tw-flex-grow-1">
|
||||
{t.i18n.t('site.menu_button', 'FrankerFaceZ Control Center')}
|
||||
|
@ -85,7 +85,7 @@ export default class SettingsMenu extends Module {
|
|||
|
||||
val.props.children.push(<div class="tw-full-width tw-relative">
|
||||
<button
|
||||
class="tw-block tw-border-radius-medium tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
|
||||
class="tw-block tw-border-radius-medium tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||
onClick={this.ffzPauseClick}
|
||||
>
|
||||
<div class="tw-align-items-center tw-flex tw-pd-05 tw-relative">
|
||||
|
@ -110,7 +110,7 @@ export default class SettingsMenu extends Module {
|
|||
if ( ! this.ffzPauseClick )
|
||||
this.ffzPauseClick = () => this.setState({ffzPauseMenu: ! this.state.ffzPauseMenu});
|
||||
|
||||
return (<div class="tw-absolute tw-balloon tw-balloon--auto tw-balloon--right tw-balloon--up tw-block" data-a-target="chat-settings-balloon" style={{marginRight: '-5.3rem'}}>
|
||||
return (<div class="tw-absolute ffz-balloon ffz-balloon--auto ffz-balloon--right ffz-balloon--up tw-block" data-a-target="chat-settings-balloon" style={{marginRight: '-5.3rem'}}>
|
||||
<div class="tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-2">
|
||||
<div class="chat-settings__popover">
|
||||
<div class="chat-settings__header tw-align-items-center tw-c-background-base tw-flex tw-pd-x-1 tw-relative">
|
||||
|
@ -143,7 +143,7 @@ export default class SettingsMenu extends Module {
|
|||
</p>
|
||||
</div>
|
||||
<button
|
||||
class="tw-block tw-border-radius-medium tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
|
||||
class="tw-block tw-border-radius-medium tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||
data-page="chat.behavior"
|
||||
onClick={this.ffzSettingsClick}
|
||||
>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
.user-avatar-animated,
|
||||
.search-result-card,
|
||||
.ffz-avatar,
|
||||
.tw-avatar {
|
||||
--border-radius-rounded: 0 !important;
|
||||
}
|
||||
|
|
|
@ -160,7 +160,7 @@ export default class Following extends SiteModule {
|
|||
|
||||
// Hosted Channel Content
|
||||
simplebarContentChildren.push(<a
|
||||
class="tw-block tw-border-radius-small tw-full-width tw-interactable tw-interactable--default tw-interactable--hover-enabled tw-interactive"
|
||||
class="tw-block tw-border-radius-small tw-full-width ffz-interactable ffz-interactable--default ffz-interactable--hover-enabled tw-interactive"
|
||||
href={`/${inst.props.channelLogin}`}
|
||||
onClick={e => this.parent.hijackUserClick(e, inst.props.channelLogin, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind
|
||||
>
|
||||
|
@ -182,7 +182,7 @@ export default class Following extends SiteModule {
|
|||
// Hosting Channels Content
|
||||
for (const channel of channels) {
|
||||
simplebarContentChildren.push(<a
|
||||
class="tw-block tw-border-radius-small tw-full-width tw-interactable tw-interactable--default tw-interactable--hover-enabled tw-interactive"
|
||||
class="tw-block tw-border-radius-small tw-full-width ffz-interactable ffz-interactable--default ffz-interactable--hover-enabled tw-interactive"
|
||||
href={`/${channel.login}`}
|
||||
onClick={e => this.parent.hijackUserClick(e, channel.login, this.destroyHostMenu.bind(this))} // eslint-disable-line react/jsx-no-bind
|
||||
>
|
||||
|
@ -197,7 +197,7 @@ export default class Following extends SiteModule {
|
|||
</a>);
|
||||
}
|
||||
|
||||
this.hostMenu = (<div class="ffz-host-menu tw-balloon tw-block">
|
||||
this.hostMenu = (<div class="ffz-host-menu ffz-balloon tw-block">
|
||||
<div class="tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base tw-pd-05">
|
||||
<div class="scrollable-area" data-simplebar>
|
||||
{simplebarContentChildren}
|
||||
|
|
|
@ -46,6 +46,21 @@ export default class FeaturedFollow extends Module {
|
|||
}
|
||||
});
|
||||
|
||||
this.follow_data = {};
|
||||
|
||||
this.socket.on(':command:follow_buttons', data => {
|
||||
for(const channel_login in data)
|
||||
if ( has(data, channel_login) )
|
||||
this.follow_data[channel_login] = data[channel_login];
|
||||
|
||||
if ( this.vueFeaturedFollow )
|
||||
this.vueFeaturedFollow.data.hasUpdate = true;
|
||||
|
||||
this.metadata.updateMetadata('following');
|
||||
});
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
this.metadata.definitions.following = {
|
||||
order: 150,
|
||||
button: true,
|
||||
|
@ -60,7 +75,7 @@ export default class FeaturedFollow extends Module {
|
|||
|
||||
this._featured_follow_tip = tip;
|
||||
tip.element.classList.remove('tw-pd-1');
|
||||
tip.element.classList.add('tw-balloon--lg');
|
||||
tip.element.classList.add('ffz-balloon--lg');
|
||||
vue.component('featured-follow', featured_follows_vue.default);
|
||||
return this.buildFeaturedFollowMenu(vue, data.channel.login, follows, add_callback);
|
||||
},
|
||||
|
@ -82,18 +97,7 @@ export default class FeaturedFollow extends Module {
|
|||
icon: 'ffz-i-heart'
|
||||
};
|
||||
|
||||
this.follow_data = {};
|
||||
|
||||
this.socket.on(':command:follow_buttons', data => {
|
||||
for(const channel_login in data)
|
||||
if ( has(data, channel_login) )
|
||||
this.follow_data[channel_login] = data[channel_login];
|
||||
|
||||
if ( this.vueFeaturedFollow )
|
||||
this.vueFeaturedFollow.data.hasUpdate = true;
|
||||
|
||||
this.metadata.updateMetadata('following');
|
||||
});
|
||||
this.metadata.updateMetadata('following');
|
||||
}
|
||||
|
||||
async getFollowsForLogin(login) {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
:data-id="host.id"
|
||||
class="tw-border-t ffz--host-user"
|
||||
>
|
||||
<div class="tw-interactable tw-interactable--default">
|
||||
<div class="ffz-interactable ffz-interactable--default">
|
||||
<div class="tw-align-items-center tw-flex tw-flex-row tw-flex-nowrap tw-mg-x-1">
|
||||
<figure class="ffz-i-ellipsis-vert handle" />
|
||||
<div class="ffz-channel-avatar">
|
||||
|
@ -68,16 +68,16 @@
|
|||
<div class="simplebar-content">
|
||||
<div class="tw-pd-1">
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||
<div class="tw-flex tw-align-items-center ffz-checkbox">
|
||||
<input
|
||||
id="autoHostSettings:enabled"
|
||||
:checked="autoHostSettings.enabled"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
data-setting="enabled"
|
||||
@change="updateCheckbox"
|
||||
>
|
||||
<label for="autoHostSettings:enabled" class="tw-checkbox__label">
|
||||
<label for="autoHostSettings:enabled" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('metadata.host.setting.auto-hosting.title', 'Auto Hosting') }}
|
||||
</span>
|
||||
|
@ -88,16 +88,16 @@
|
|||
</section>
|
||||
</div>
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||
<div class="tw-flex tw-align-items-center ffz-checkbox">
|
||||
<input
|
||||
id="autoHostSettings:teamHost"
|
||||
:checked="autoHostSettings.teamHost"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
data-setting="teamHost"
|
||||
@change="updateCheckbox"
|
||||
>
|
||||
<label for="autoHostSettings:teamHost" class="tw-checkbox__label">
|
||||
<label for="autoHostSettings:teamHost" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('metadata.host.setting.team-hosting.title', 'Team Hosting') }}
|
||||
</span>
|
||||
|
@ -108,16 +108,16 @@
|
|||
</section>
|
||||
</div>
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||
<div class="tw-flex tw-align-items-center ffz-checkbox">
|
||||
<input
|
||||
id="autoHostSettings:deprioritizeVodcast"
|
||||
:checked="autoHostSettings.deprioritizeVodcast"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
data-setting="deprioritizeVodcast"
|
||||
@change="updateCheckbox"
|
||||
>
|
||||
<label for="autoHostSettings:deprioritizeVodcast" class="tw-checkbox__label">
|
||||
<label for="autoHostSettings:deprioritizeVodcast" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('metadata.host.setting.vodcast-hosting.title', 'Host pre-recorded videos') }}
|
||||
</span>
|
||||
|
@ -128,16 +128,16 @@
|
|||
</section>
|
||||
</div>
|
||||
<div class="ffz--widget ffz--checkbox">
|
||||
<div class="tw-flex tw-align-items-center tw-checkbox">
|
||||
<div class="tw-flex tw-align-items-center ffz-checkbox">
|
||||
<input
|
||||
id="autoHostSettings:strategy"
|
||||
:checked="autoHostSettings.strategy === 'RANDOM'"
|
||||
type="checkbox"
|
||||
class="tw-checkbox__input"
|
||||
class="ffz-checkbox__input"
|
||||
data-setting="strategy"
|
||||
@change="updateCheckbox"
|
||||
>
|
||||
<label for="autoHostSettings:strategy" class="tw-checkbox__label">
|
||||
<label for="autoHostSettings:strategy" class="ffz-checkbox__label">
|
||||
<span class="tw-mg-l-1">
|
||||
{{ t('metadata.host.setting.strategy.title', 'Randomize Host Order') }}
|
||||
</span>
|
||||
|
|
|
@ -53,67 +53,6 @@ export default class HostButton extends Module {
|
|||
this.metadata.updateMetadata('host');
|
||||
}
|
||||
});
|
||||
|
||||
this.metadata.definitions.host = {
|
||||
order: 150,
|
||||
border: true,
|
||||
button: true,
|
||||
fade_in: true,
|
||||
modview: true,
|
||||
|
||||
disabled: () => this._host_updating || this._host_error,
|
||||
|
||||
click: data => {
|
||||
if ( data.channel )
|
||||
this.sendHostUnhostCommand(data.channel.login);
|
||||
},
|
||||
|
||||
popup: async (data, tip) => {
|
||||
const vue = this.resolve('vue'),
|
||||
_host_options_vue = import(/* webpackChunkName: "host-options" */ './host-options.vue'),
|
||||
_autoHosts = this.fetchAutoHosts(),
|
||||
_autoHostSettings = this.fetchAutoHostSettings();
|
||||
|
||||
const [, host_options_vue, autoHosts, autoHostSettings] = await Promise.all([vue.enable(), _host_options_vue, _autoHosts, _autoHostSettings]);
|
||||
|
||||
this._auto_host_tip = tip;
|
||||
tip.element.classList.remove('tw-pd-1');
|
||||
tip.element.classList.add('tw-balloon--lg');
|
||||
vue.component('host-options', host_options_vue.default);
|
||||
return this.buildAutoHostMenu(vue, autoHosts, autoHostSettings, data.channel);
|
||||
},
|
||||
|
||||
label: data => {
|
||||
const ffz_user = this.site.getUser();
|
||||
|
||||
if ( ! this.settings.get('metadata.host-button') || ! ffz_user || ! data.channel || data.channel.login === ffz_user.login )
|
||||
return;
|
||||
|
||||
if ( data.channel.video && ! this.isChannelHosted(data.channel.login) )
|
||||
return;
|
||||
|
||||
if ( this._host_updating )
|
||||
return this.i18n.t('metadata.host-button.updating', 'Updating...');
|
||||
|
||||
return (this._last_hosted_channel && this.isChannelHosted(data.channel && data.channel.login))
|
||||
? this.i18n.t('metadata.host-button.unhost', 'Unhost')
|
||||
: this.i18n.t('metadata.host-button.host', 'Host');
|
||||
},
|
||||
|
||||
tooltip: () => {
|
||||
if (this._host_error) {
|
||||
return this.i18n.t(
|
||||
`metadata.host-button.tooltip.error.${this._host_error.key}`,
|
||||
this._host_error.text);
|
||||
} else {
|
||||
return this.i18n.t('metadata.host-button.tooltip',
|
||||
'Currently hosting: {channel}',
|
||||
{
|
||||
channel: this._last_hosted_channel || this.i18n.t('metadata.host-button.tooltip.none', 'None')
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
isChannelHosted(channelLogin) {
|
||||
|
@ -198,6 +137,67 @@ export default class HostButton extends Module {
|
|||
onEnable() {
|
||||
this.on('i18n:update', () => this.metadata.updateMetadata('host'));
|
||||
|
||||
this.metadata.definitions.host = {
|
||||
order: 150,
|
||||
border: true,
|
||||
button: true,
|
||||
fade_in: true,
|
||||
modview: true,
|
||||
|
||||
disabled: () => this._host_updating || this._host_error,
|
||||
|
||||
click: data => {
|
||||
if ( data.channel )
|
||||
this.sendHostUnhostCommand(data.channel.login);
|
||||
},
|
||||
|
||||
popup: async (data, tip) => {
|
||||
const vue = this.resolve('vue'),
|
||||
_host_options_vue = import(/* webpackChunkName: "host-options" */ './host-options.vue'),
|
||||
_autoHosts = this.fetchAutoHosts(),
|
||||
_autoHostSettings = this.fetchAutoHostSettings();
|
||||
|
||||
const [, host_options_vue, autoHosts, autoHostSettings] = await Promise.all([vue.enable(), _host_options_vue, _autoHosts, _autoHostSettings]);
|
||||
|
||||
this._auto_host_tip = tip;
|
||||
tip.element.classList.remove('tw-pd-1');
|
||||
tip.element.classList.add('ffz-balloon--lg');
|
||||
vue.component('host-options', host_options_vue.default);
|
||||
return this.buildAutoHostMenu(vue, autoHosts, autoHostSettings, data.channel);
|
||||
},
|
||||
|
||||
label: data => {
|
||||
const ffz_user = this.site.getUser();
|
||||
|
||||
if ( ! this.settings.get('metadata.host-button') || ! ffz_user || ! data.channel || data.channel.login === ffz_user.login )
|
||||
return;
|
||||
|
||||
if ( data.channel.video && ! this.isChannelHosted(data.channel.login) )
|
||||
return;
|
||||
|
||||
if ( this._host_updating )
|
||||
return this.i18n.t('metadata.host-button.updating', 'Updating...');
|
||||
|
||||
return (this._last_hosted_channel && this.isChannelHosted(data.channel && data.channel.login))
|
||||
? this.i18n.t('metadata.host-button.unhost', 'Unhost')
|
||||
: this.i18n.t('metadata.host-button.host', 'Host');
|
||||
},
|
||||
|
||||
tooltip: () => {
|
||||
if (this._host_error) {
|
||||
return this.i18n.t(
|
||||
`metadata.host-button.tooltip.error.${this._host_error.key}`,
|
||||
this._host_error.text);
|
||||
} else {
|
||||
return this.i18n.t('metadata.host-button.tooltip',
|
||||
'Currently hosting: {channel}',
|
||||
{
|
||||
channel: this._last_hosted_channel || this.i18n.t('metadata.host-button.tooltip.none', 'None')
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.metadata.updateMetadata('host');
|
||||
|
||||
this.chat.ChatService.ready((cls, instances) => {
|
||||
|
|
|
@ -18,6 +18,7 @@ export default class MenuButton extends SiteModule {
|
|||
this.inject('i18n');
|
||||
this.inject('settings');
|
||||
this.inject('site.fine');
|
||||
this.inject('site.elemental');
|
||||
//this.inject('addons');
|
||||
|
||||
this.should_enable = true;
|
||||
|
@ -44,10 +45,16 @@ export default class MenuButton extends SiteModule {
|
|||
['mod-view']
|
||||
);
|
||||
|
||||
this.SunlightDash = this.fine.define(
|
||||
/*this.SunlightDash = this.fine.define(
|
||||
'sunlight-dash',
|
||||
n => n.getIsChannelEditor && n.getIsChannelModerator && n.getIsAdsEnabled && n.getIsSquadStreamsEnabled,
|
||||
Twilight.SUNLIGHT_ROUTES
|
||||
);*/
|
||||
|
||||
this.SunlightNav = this.elemental.define(
|
||||
'sunlight-nav', '.sunlight-top-nav > .tw-flex > .tw-flex > .tw-justify-content-end > .tw-flex',
|
||||
Twilight.SUNLIGHT_ROUTES,
|
||||
{attributes: true}, 1
|
||||
);
|
||||
|
||||
this.NavBar = this.fine.define(
|
||||
|
@ -195,8 +202,11 @@ export default class MenuButton extends SiteModule {
|
|||
for(const inst of this.MultiController.instances)
|
||||
this.updateButton(inst);
|
||||
|
||||
for(const inst of this.SunlightDash.instances)
|
||||
this.updateButton(inst);
|
||||
//for(const inst of this.SunlightDash.instances)
|
||||
// this.updateButton(inst);
|
||||
|
||||
for(const el of this.SunlightNav.instances)
|
||||
this.updateButton(null, el, true);
|
||||
|
||||
for(const inst of this.ModBar.instances)
|
||||
this.updateButton(inst);
|
||||
|
@ -216,9 +226,13 @@ export default class MenuButton extends SiteModule {
|
|||
this.MultiController.on('mount', this.updateButton, this);
|
||||
this.MultiController.on('update', this.updateButton, this);
|
||||
|
||||
this.SunlightDash.ready(() => this.update());
|
||||
/*this.SunlightDash.ready(() => this.update());
|
||||
this.SunlightDash.on('mount', this.updateButton, this);
|
||||
this.SunlightDash.on('update', this.updateButton, this);
|
||||
this.SunlightDash.on('update', this.updateButton, this);*/
|
||||
|
||||
this.SunlightNav.on('mount', el => this.updateButton(null, el, true));
|
||||
this.SunlightNav.on('mutate', el => this.updateButton(null, el, true));
|
||||
this.SunlightNav.each(el => this.updateButton(null, el, true));
|
||||
|
||||
this.ModBar.ready(() => this.update());
|
||||
this.ModBar.on('mount', this.updateButton, this);
|
||||
|
@ -241,11 +255,13 @@ export default class MenuButton extends SiteModule {
|
|||
});
|
||||
}
|
||||
|
||||
updateButton(inst) {
|
||||
updateButton(inst, container, is_sunlight) {
|
||||
const root = this.fine.getChildNode(inst);
|
||||
let is_squad = false,
|
||||
is_sunlight = false,
|
||||
is_mod = false,
|
||||
//is_sunlight = false,
|
||||
is_mod = false;
|
||||
|
||||
if ( ! container )
|
||||
container = root && root.querySelector('.top-nav__menu');
|
||||
|
||||
if ( ! container ) {
|
||||
|
@ -265,7 +281,7 @@ export default class MenuButton extends SiteModule {
|
|||
}
|
||||
|
||||
if ( ! container && inst.getIsAdsEnabled ) {
|
||||
container = root && root.querySelector('.sunlight-top-nav > .tw-flex');
|
||||
container = root && root.querySelector('.sunlight-top-nav > .tw-flex > .tw-flex > .tw-justify-content-end > .tw-flex');
|
||||
if ( container )
|
||||
is_sunlight = true;
|
||||
}
|
||||
|
@ -279,7 +295,7 @@ export default class MenuButton extends SiteModule {
|
|||
if ( ! container )
|
||||
return;
|
||||
|
||||
if ( ! is_squad && ! is_mod ) {
|
||||
if ( ! is_squad && ! is_mod && ! is_sunlight ) {
|
||||
let user_stuff = null;
|
||||
try {
|
||||
user_stuff = container.querySelector(':scope > .tw-justify-content-end:last-child');
|
||||
|
@ -314,7 +330,7 @@ export default class MenuButton extends SiteModule {
|
|||
</span>
|
||||
</div>
|
||||
</button>)}
|
||||
{this.has_error && (<div class={`tw-absolute tw-balloon tw-balloon--lg tw-block ${is_mod ? 'tw-tooltip--up tw-tooltip--align-left' : 'tw-tooltip--down tw-tooltip--align-right'}`}>
|
||||
{this.has_error && (<div class={`tw-absolute ffz-balloon ffz-balloon--lg tw-block ${is_mod ? 'tw-tooltip--up tw-tooltip--align-left' : 'tw-tooltip--down tw-tooltip--align-right'}`}>
|
||||
<div class="tw-border-radius-large tw-c-background-base tw-c-text-inherit tw-elevation-4 tw-pd-1">
|
||||
<div class="tw-flex tw-align-items-center">
|
||||
<div class="tw-flex-grow-1">
|
||||
|
@ -377,8 +393,16 @@ export default class MenuButton extends SiteModule {
|
|||
|
||||
if ( is_mod )
|
||||
container.insertBefore(el, container.firstElementChild);
|
||||
else
|
||||
container.insertBefore(el, container.lastElementChild);
|
||||
else {
|
||||
let before = container.lastElementChild;
|
||||
if ( before && before.classList.contains('resize-detector') )
|
||||
before = before.previousElementSibling;
|
||||
|
||||
if ( before )
|
||||
container.insertBefore(el, before);
|
||||
else
|
||||
container.appendChild(el);
|
||||
}
|
||||
|
||||
if ( this._ctx_open )
|
||||
this.renderContext(null, btn);
|
||||
|
@ -476,7 +500,7 @@ export default class MenuButton extends SiteModule {
|
|||
|
||||
|
||||
ctx = (<div class={`tw-absolute tw-attached ${is_mod ? 'tw-attached--up tw-attached--left' : 'tw-attached--down tw-attached--right'}`}>
|
||||
<div class={`tw-balloon tw-balloon--lg tw-block ffz--menu-context`}>
|
||||
<div class={`ffz-balloon ffz-balloon--lg 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">
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
// ============================================================================
|
||||
// Settings Sync
|
||||
// ============================================================================
|
||||
|
||||
import Module from 'utilities/module';
|
||||
import {createElement} from 'utilities/dom';
|
||||
|
||||
export default class SettingsSync extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
this.should_enable = window.location.host !== 'www.twitch.tv';
|
||||
|
||||
this.inject('settings');
|
||||
}
|
||||
|
||||
onEnable() {
|
||||
const frame = this.frame = createElement('iframe');
|
||||
frame.src = '//www.twitch.tv/p/ffz_bridge/';
|
||||
frame.id = 'ffz-settings-bridge';
|
||||
frame.style.width = 0;
|
||||
frame.style.height = 0;
|
||||
|
||||
this.settings.provider.on('set', this.onProviderSet, this);
|
||||
window.addEventListener('message', this.onMessage.bind(this));
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
|
||||
send(msg) {
|
||||
try {
|
||||
this.frame.contentWindow.postMessage(msg, '*');
|
||||
} catch(err) { this.log.error('send error', err); /* no-op */ }
|
||||
}
|
||||
|
||||
onMessage(event) {
|
||||
const msg = event.data;
|
||||
if ( ! msg || ! msg.ffz_type )
|
||||
return;
|
||||
|
||||
if ( msg.ffz_type === 'ready' )
|
||||
this.send({ffz_type: 'load'});
|
||||
else if ( msg.ffz_type === 'loaded' )
|
||||
this.onLoad(msg.data);
|
||||
else if ( msg.ffz_type === 'change' )
|
||||
this.onChange(msg);
|
||||
else
|
||||
this.log.info('Unknown Message', msg.ffz_type, msg);
|
||||
}
|
||||
|
||||
onLoad(data) {
|
||||
if ( ! data )
|
||||
return;
|
||||
|
||||
const provider = this.settings.provider,
|
||||
old_keys = new Set(provider.keys());
|
||||
|
||||
this.skip = true;
|
||||
|
||||
for(const [key, value] of Object.entries(data)) {
|
||||
old_keys.delete(key);
|
||||
if ( provider.get(key) === value )
|
||||
continue;
|
||||
|
||||
provider.set(key, value);
|
||||
provider.emit('changed', key, value, false);
|
||||
}
|
||||
|
||||
for(const key of old_keys) {
|
||||
provider.delete(key);
|
||||
provider.emit('changed', key, undefined, true);
|
||||
}
|
||||
|
||||
this.skip = false;
|
||||
}
|
||||
|
||||
onProviderSet(key, value, deleted) {
|
||||
if ( this.skip )
|
||||
return;
|
||||
|
||||
this.send({
|
||||
ffz_type: 'change',
|
||||
key,
|
||||
value,
|
||||
deleted
|
||||
});
|
||||
}
|
||||
|
||||
onChange(msg) {
|
||||
const key = msg.key,
|
||||
value = msg.value,
|
||||
deleted = msg.deleted;
|
||||
|
||||
this.skip = true;
|
||||
if ( deleted )
|
||||
this.settings.provider.delete(key);
|
||||
else
|
||||
this.settings.provider.set(key, value);
|
||||
this.skip = false;
|
||||
|
||||
this.settings.provider.emit('changed', key, value, deleted, true);
|
||||
}
|
||||
}
|
|
@ -126,19 +126,19 @@ export default class VideoChatHook extends Module {
|
|||
<figure class="ffz-i-ellipsis-vert" />
|
||||
</span>
|
||||
</button>
|
||||
<div class={`tw-absolute tw-balloon tw-balloon--down tw-balloon--right tw-balloon--sm ${is_open ? 'tw-block' : 'tw-hide'}`}>
|
||||
<div class="tw-absolute tw-balloon__tail tw-overflow-hidden">
|
||||
<div class="tw-absolute tw-balloon__tail-symbol tw-border-b tw-border-l tw-border-r tw-border-t tw-c-background-base" />
|
||||
<div class={`tw-absolute ffz-balloon ffz-balloon--down ffz-balloon--right ffz-balloon--sm ${is_open ? 'tw-block' : 'tw-hide'}`}>
|
||||
<div class="tw-absolute ffz-balloon__tail tw-overflow-hidden">
|
||||
<div class="tw-absolute ffz-balloon__tail-symbol tw-border-b tw-border-l tw-border-r tw-border-t tw-c-background-base" />
|
||||
</div>
|
||||
<div class="tw-border-b tw-border-l tw-border-r tw-border-radius-medium tw-border-t tw-c-background-base tw-elevation-1 tw-pd-y-1">
|
||||
<button class="tw-interactable tw-interactable--inverted tw-full-width tw-pd-y-05 tw-pd-x-1">{
|
||||
<button class="ffz-interactable ffz-interactable--inverted tw-full-width tw-pd-y-05 tw-pd-x-1">{
|
||||
t.i18n.t('video-chat.copy-link', 'Copy Link')
|
||||
}</button>
|
||||
<button class="tw-interactable tw-interactable--alert tw-full-width tw-pd-y-05 tw-pd-x-1">{
|
||||
<button class="ffz-interactable ffz-interactable--alert tw-full-width tw-pd-y-05 tw-pd-x-1">{
|
||||
t.i18n.t('video-chat.delete', 'Delete')
|
||||
}</button>
|
||||
<div class="tw-mg-1 tw-border-b" />
|
||||
<button class="tw-interactable tw-interactable--alert tw-full-width tw-pd-y-05 tw-pd-x-1">{
|
||||
<button class="ffz-interactable ffz-interactable--alert tw-full-width tw-pd-y-05 tw-pd-x-1">{
|
||||
t.i18n.t('video-chat.ban', 'Ban User')
|
||||
}</button>
|
||||
</div>
|
||||
|
@ -302,7 +302,7 @@ export default class VideoChatHook extends Module {
|
|||
{hide_timestamps || (<div data-test-selector="message-timestamp" class="tw-align-right tw-flex tw-flex-shrink-0 vod-message__header">
|
||||
<div class="tw-mg-r-05">
|
||||
<div class="tw-inline-flex tw-relative tw-tooltip__container">
|
||||
<button class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive" onClick={this.onTimestampClickHandler}>
|
||||
<button class="tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive" onClick={this.onTimestampClickHandler}>
|
||||
<div class="tw-pd-x-05">
|
||||
<p class="tw-font-size-7">{print_duration(context.comment.contentOffset)}</p>
|
||||
</div>
|
||||
|
|
|
@ -195,7 +195,7 @@
|
|||
}
|
||||
|
||||
.ffz--action &,
|
||||
.tw-interactable:hover &:not(.disabled),
|
||||
.ffz-interactable:hover &:not(.disabled),
|
||||
&:not(.disabled):hover {
|
||||
.tw-root--theme-dark &, & {
|
||||
&.tw-c-text-alt-2 {
|
||||
|
@ -335,7 +335,7 @@
|
|||
}
|
||||
|
||||
.ffz--emoji-tone-picker {
|
||||
.tw-balloon {
|
||||
.ffz-balloon {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
max-height: 25vh;
|
||||
}
|
||||
|
||||
.tw-interactable {
|
||||
.ffz-interactable {
|
||||
padding: .1rem 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="tw-search-input" data-a-target="dropdown-search-input">
|
||||
<label v-if="placeholder" :for="_id" class="tw-hide-accessible">{{ placeholder }}</label>
|
||||
<div class="tw-relative">
|
||||
<div v-if="hasIcon" class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<div v-if="hasIcon" class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height ffz-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<figure :class="icon" />
|
||||
</div>
|
||||
<input
|
||||
|
@ -12,7 +12,7 @@
|
|||
:placeholder="placeholder"
|
||||
:class="[hasIcon ? 'tw-pd-l-3' : 'tw-pd-l-1']"
|
||||
type="search"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-r-1 tw-pd-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-r-1 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
|
@ -45,8 +45,8 @@
|
|||
v-for="(item, idx) of filteredItems"
|
||||
:id="'ffz-autocomplete-item-' + id + '-' + idx"
|
||||
:key="has(item, 'id') ? item.id : idx"
|
||||
:class="{'tw-interactable--hover' : idx === index}"
|
||||
class="tw-block tw-full-width tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
|
||||
:class="{'ffz-interactable--hover' : idx === index}"
|
||||
class="tw-block tw-full-width ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||
tabindex="-1"
|
||||
data-selectable="true"
|
||||
@mouseenter="index = idx"
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template lang="html">
|
||||
<div
|
||||
:class="classes"
|
||||
class="tw-balloon tw-block tw-absolute tw-z-above"
|
||||
class="ffz-balloon tw-block tw-absolute tw-z-above"
|
||||
>
|
||||
<div class="tw-balloon__tail tw-overflow-hidden tw-absolute">
|
||||
<div class="ffz-balloon__tail tw-overflow-hidden tw-absolute">
|
||||
<div
|
||||
:class="`tw-c-${color}`"
|
||||
class="tw-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-absolute"
|
||||
class="ffz-balloon__tail-symbol tw-border-t tw-border-r tw-border-b tw-border-l tw-border-radius-small tw-absolute"
|
||||
/>
|
||||
</div>
|
||||
<div class="tw-border-t tw-border-r tw-border-b tw-border-l tw-elevation-1 tw-border-radius-small">
|
||||
|
@ -41,7 +41,7 @@ export default {
|
|||
}).join(' ');
|
||||
}
|
||||
|
||||
return `tw-c-${this.color} ${this.size ? `tw-balloon--${this.size}` : ''} ${dir}`;
|
||||
return `tw-c-${this.color} ${this.size ? `ffz-balloon--${this.size}` : ''} ${dir}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
v-model="color"
|
||||
v-bind="$attrs"
|
||||
type="text"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
|
@ -47,7 +47,7 @@
|
|||
v-if="open"
|
||||
v-on-clickaway="closePicker"
|
||||
:class="{'ffz-bottom-100': openUp}"
|
||||
class="tw-absolute tw-z-above tw-balloon--up tw-balloon--right"
|
||||
class="tw-absolute tw-z-above ffz-balloon--up ffz-balloon--right"
|
||||
>
|
||||
<chrome-picker :disable-alpha="! alpha" :value="colors" @input="onPick" />
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div class="tw-search-input tw-full-width">
|
||||
<label v-if="open" :for="'icon-search$' + id" class="tw-hide-accessible">{{ t('setting.icon.search', 'Search for Icon') }}</label>
|
||||
<div class="tw-relative">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height tw-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<div class="tw-absolute tw-align-items-center tw-c-text-alt-2 tw-flex tw-full-height ffz-input__icon tw-justify-content-center tw-left-0 tw-top-0 tw-z-default">
|
||||
<figure :class="[(isOpen || ! val || ! val.length) ? 'ffz-i-search' : val]" />
|
||||
</div>
|
||||
<input
|
||||
|
@ -13,7 +13,7 @@
|
|||
:value="isOpen ? search : val"
|
||||
:class="[clearable ? 'tw-pd-r-5' : 'tw-pd-r-1']"
|
||||
type="text"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-3 tw-pd-y-05"
|
||||
class="tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-l-3 tw-pd-y-05"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
autocomplete="off"
|
||||
|
@ -46,9 +46,9 @@
|
|||
v-for="i of visible"
|
||||
:key="i[0]"
|
||||
:aria-checked="val === i[0]"
|
||||
:class="{'tw-interactable--selected': val === i[0]}"
|
||||
:class="{'ffz-interactable--selected': val === i[0]}"
|
||||
:data-title="i[1]"
|
||||
class="ffz-tooltip ffz-icon tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive"
|
||||
class="ffz-tooltip ffz-icon ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive"
|
||||
role="radio"
|
||||
tabindex="0"
|
||||
@keydown.space.stop.prevent=""
|
||||
|
@ -156,7 +156,7 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
if ( this.val ) {
|
||||
const root = this.$refs.list,
|
||||
el = root && root.querySelector('.tw-interactable--selected');
|
||||
el = root && root.querySelector('.ffz-interactable--selected');
|
||||
|
||||
if ( el )
|
||||
el.scrollIntoViewIfNeeded();
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div
|
||||
ref="input"
|
||||
v-bind="$attrs"
|
||||
class="default-dimmable tw-block tw-border-radius-medium tw-font-size-6 tw-full-width tw-input tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
class="default-dimmable tw-block tw-border-radius-medium tw-font-size-6 tw-full-width ffz-input tw-pd-l-1 tw-pd-r-3 tw-pd-y-05 tw-mg-y-05"
|
||||
tabindex="0"
|
||||
@click="startRecording"
|
||||
@keydown="onKey"
|
||||
|
|
59
src/utilities/blobs.js
Normal file
59
src/utilities/blobs.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
'use strict';
|
||||
|
||||
export function isValidBlob(blob) {
|
||||
return blob instanceof Blob || blob instanceof File || blob instanceof ArrayBuffer || blob instanceof Uint8Array;
|
||||
}
|
||||
|
||||
export async function serializeBlob(blob) {
|
||||
if ( ! blob )
|
||||
return null;
|
||||
|
||||
if ( blob instanceof Blob )
|
||||
return {
|
||||
type: 'blob',
|
||||
mime: blob.type,
|
||||
buffer: await blob.arrayBuffer(),
|
||||
}
|
||||
|
||||
if ( blob instanceof File )
|
||||
return {
|
||||
type: 'file',
|
||||
mime: blob.type,
|
||||
name: blob.name,
|
||||
modified: blob.lastModified,
|
||||
buffer: await blob.arrayBuffer()
|
||||
}
|
||||
|
||||
if ( blob instanceof ArrayBuffer )
|
||||
return {
|
||||
type: 'ab',
|
||||
buffer: blob
|
||||
}
|
||||
|
||||
if ( blob instanceof Uint8Array )
|
||||
return {
|
||||
type: 'u8',
|
||||
buffer: blob.buffer
|
||||
}
|
||||
|
||||
throw new TypeError('Invalid type');
|
||||
}
|
||||
|
||||
export function deserializeBlob(data) {
|
||||
if ( ! data || ! data.type )
|
||||
return null;
|
||||
|
||||
if ( data.type === 'blob' )
|
||||
return new Blob([data.buffer], {type: data.mime});
|
||||
|
||||
if ( data.type === 'file' )
|
||||
return new File([data.buffer], data.name, {type: data.mime, lastModified: data.modified});
|
||||
|
||||
if ( data.type === 'ab' )
|
||||
return data.buffer;
|
||||
|
||||
if ( data.type === 'u8' )
|
||||
return new Uint8Array(data.buffer);
|
||||
|
||||
throw new TypeError('Invalid type');
|
||||
}
|
|
@ -14,8 +14,10 @@ export class Logger {
|
|||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
if ( this.root == this )
|
||||
if ( this.root == this ) {
|
||||
this.captured_init = [];
|
||||
this.label = 'FFZ';
|
||||
}
|
||||
|
||||
this.init = false;
|
||||
this.enabled = true;
|
||||
|
@ -92,9 +94,9 @@ export class Logger {
|
|||
});
|
||||
|
||||
if ( this.name )
|
||||
message.unshift(`%cFFZ [%c${this.name}%c]:%c`, 'color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
|
||||
message.unshift(`%c${this.root.label} [%c${this.name}%c]:%c`, 'color:#755000; font-weight:bold', '', 'color:#755000; font-weight:bold', '');
|
||||
else
|
||||
message.unshift('%cFFZ:%c', 'color:#755000; font-weight:bold', '');
|
||||
message.unshift(`%c${this.root.label}:%c`, 'color:#755000; font-weight:bold', '');
|
||||
|
||||
if ( level === Logger.DEBUG )
|
||||
console.debug(...message);
|
||||
|
|
|
@ -613,11 +613,11 @@ export class Module extends EventEmitter {
|
|||
}
|
||||
|
||||
|
||||
populate(ctx, log) {
|
||||
async populate(ctx, log) {
|
||||
log = log || this.log;
|
||||
const added = {};
|
||||
for(const raw_path of ctx.keys()) {
|
||||
const raw_module = ctx(raw_path),
|
||||
const raw_module = await ctx(raw_path), // eslint-disable-line no-await-in-loop
|
||||
module = raw_module.module || raw_module.default,
|
||||
lix = raw_path.lastIndexOf('.'),
|
||||
trimmed = lix > 2 ? raw_path.slice(2, lix) : raw_path,
|
||||
|
|
|
@ -816,7 +816,7 @@ TOKEN_TYPES.link = function(token, createElement, ctx) {
|
|||
|
||||
const klass = [];
|
||||
if ( token.interactive )
|
||||
klass.push(`tw-interactable tw-interactable--hover-enabled tw-interactable--default tw-interactive`);
|
||||
klass.push(`ffz-interactable ffz-interactable--hover-enabled ffz-interactable--default tw-interactive`);
|
||||
|
||||
if ( token.tooltip !== false )
|
||||
klass.push('ffz-tooltip');
|
||||
|
|
268
styles/input/checkbox.scss
Normal file
268
styles/input/checkbox.scss
Normal file
|
@ -0,0 +1,268 @@
|
|||
.ffz-checkbox .ffz-checkbox__label:before,
|
||||
.ffz-radio__label:before {
|
||||
background-clip: padding-box;
|
||||
box-sizing: border-box;
|
||||
content: "";
|
||||
height: 1.6rem;
|
||||
margin-top: -.8rem;
|
||||
transition: box-shadow .1s ease-in, background .1s ease-in;
|
||||
width: 1.6rem;
|
||||
}
|
||||
|
||||
.ffz-checkbox__input,
|
||||
.ffz-radio__input {
|
||||
clip: rect(0 0 0 0);
|
||||
height: .1rem;
|
||||
margin: -.1rem;
|
||||
overflow: hidden;
|
||||
width: .1rem;
|
||||
}
|
||||
|
||||
.ffz-checkbox__input {
|
||||
border: none;
|
||||
color: var(--color-text-label);
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
|
||||
&:hover:checked,
|
||||
&:hover:indeterminate {
|
||||
& + .ffz-checkbox__label:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-checked);
|
||||
}
|
||||
}
|
||||
|
||||
&:checked,
|
||||
&:indeterminate {
|
||||
& + .ffz-checkbox__label {
|
||||
&:before {
|
||||
background-color: var(--color-background-input-checkbox-checked-background);
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-checked);
|
||||
}
|
||||
|
||||
&:after {
|
||||
background-color: var(--color-background-input-checkbox-checked);
|
||||
content: "";
|
||||
left: .4rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: .8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:checked + .ffz-checkbox__label:after {
|
||||
height: .8rem;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
}
|
||||
|
||||
&:indeterminate + .ffz-checkbox__label:after {
|
||||
display: block;
|
||||
height: .2rem;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
&:disabled + .ffz-checkbox__label {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.js-focus-visible &:focus:not([data-focus-visible-added]) + .ffz-checkbox__label {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&[data-focus-visible-added] + .ffz-checkbox__label:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-focus);
|
||||
box-shadow: var(--shadow-input-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-checkbox {
|
||||
.ffz-checkbox__label {
|
||||
border-radius: var(--border-radius-medium);
|
||||
color: var(--color-text-label);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 0 0 1.6rem;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox);
|
||||
border-radius: .2rem;
|
||||
border-radius: var(--border-radius-small);
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
border-color: var(--color-border-input-checkbox-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-checkbox--error .ffz-checkbox__label:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-error);
|
||||
}
|
||||
|
||||
.ffz-checkbox--overlay {
|
||||
.ffz-checkbox__input {
|
||||
&:checked + .ffz-checkbox__label {
|
||||
&:before {
|
||||
background-color: var(--color-background-input-checkbox-checked-background-overlay);
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-checked-overlay);
|
||||
}
|
||||
|
||||
&:after {
|
||||
background-color: var(--color-background-input-checkbox-checked-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover:checked,
|
||||
&:indeterminate {
|
||||
& + .ffz-checkbox__label:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-checked-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
&:indeterminate + .ffz-checkbox__label {
|
||||
&:before {
|
||||
background-color: var(--color-background-input-checkbox-checked-background-overlay);
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-checked-overlay);
|
||||
}
|
||||
|
||||
&:after {
|
||||
background: var(--color-background-input-checkbox-checked-overlay);
|
||||
content: "";
|
||||
display: block;
|
||||
height: .2rem;
|
||||
left: .4rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: .8rem;
|
||||
}
|
||||
}
|
||||
|
||||
.js-focus-visible &:focus:not([data-focus-visible-added]) + .ffz-checkbox__label {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&[data-focus-visible-added] + .ffz-checkbox__label:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-overlay-focus);
|
||||
box-shadow: 0 0 6px 0 var(--color-border-input-overlay-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-checkbox__label {
|
||||
color: var(--color-text-overlay);
|
||||
|
||||
&:before {
|
||||
background-color: var(--color-background-input-overlay);
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-overlay);
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
border-color: var(--color-border-input-checkbox-hover-overlay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Radio
|
||||
|
||||
.ffz-radio__input {
|
||||
border: none;
|
||||
color: var(--color-text-label);
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
|
||||
&:checked + .ffz-radio__label:after {
|
||||
background-color: var(--color-background-input-checkbox-checked);
|
||||
border-radius: 50%;
|
||||
height: .8rem;
|
||||
left: .4rem;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate3d(0, -50%, 0);
|
||||
width: .8rem;
|
||||
}
|
||||
|
||||
&:disabled + .ffz-radio__label {
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.js-focus-visible &:focus:not([data-focus-visible-added]) + .ffz-radio__label {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&[data-focus-visible-added] + .ffz-radio__label:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-focus);
|
||||
box-shadow: var(--shadow-input-focus);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-radio__label {
|
||||
border-radius: var(--border-radius-medium);
|
||||
color: var(--color-text-label);
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
padding: 0 0 0 1.6rem;
|
||||
position: relative;
|
||||
|
||||
&:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox);
|
||||
border-radius: 50%;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
}
|
||||
|
||||
&:after {
|
||||
background-color: var(--color-background-input);
|
||||
content: "";
|
||||
display: block;
|
||||
transition: background .1s ease-in;
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
border-color: var(--color-border-input-checkbox-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-radio--error .ffz-radio__label:before {
|
||||
border: var(--border-width-input) solid var(--color-border-input-checkbox-error);
|
||||
}
|
||||
|
||||
// TODO: radio overlay
|
||||
|
||||
|
||||
// Selection Stuff
|
||||
|
||||
@media (-webkit-min-device-pixel-ratio: 0) {
|
||||
.ffz-checkbox--overlay .ffz-checkbox__input:focus+.ffz-checkbox__label:before,
|
||||
.ffz-checkbox__input:focus+.ffz-checkbox__label,
|
||||
.ffz-radio--overlay .ffz-radio__input:focus+.ffz-radio__label:before,
|
||||
.ffz-radio__input:focus+.ffz-radio__label,
|
||||
.ffz-segmented-button-option__input--checkbox:focus+.ffz-segmented-button-option__label,
|
||||
.ffz-segmented-button-option__input--radio:focus+.ffz-segmented-button-option__label,
|
||||
.ffz-toggle__input:focus+.ffz-toggle__button {
|
||||
outline-color: -webkit-focus-ring-color;
|
||||
outline-style: auto
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-checkbox--overlay .ffz-checkbox__input:focus+.ffz-checkbox__label:before,
|
||||
.ffz-checkbox__input:focus+.ffz-checkbox__label,
|
||||
.ffz-radio--overlay .ffz-radio__input:focus+.ffz-radio__label:before,
|
||||
.ffz-radio__input:focus+.ffz-radio__label,
|
||||
.ffz-segmented-button-option__input--checkbox:focus+.ffz-segmented-button-option__label,
|
||||
.ffz-segmented-button-option__input--radio:focus+.ffz-segmented-button-option__label,
|
||||
.ffz-toggle__input:focus+.ffz-toggle__button {
|
||||
outline: 5px solid Highlight;
|
||||
}
|
3
styles/input/index.scss
Normal file
3
styles/input/index.scss
Normal file
|
@ -0,0 +1,3 @@
|
|||
@import "checkbox";
|
||||
@import "text";
|
||||
@import "interactable";
|
115
styles/input/interactable.scss
Normal file
115
styles/input/interactable.scss
Normal file
|
@ -0,0 +1,115 @@
|
|||
.ffz-interactable {
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.ffz-interactable--hover-enabled:hover:active,
|
||||
&:active {
|
||||
background-color: var(--color-background-interactable-active);
|
||||
}
|
||||
|
||||
&--selected, &:focus {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&--disabled, &:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-interactable--default {
|
||||
&.ffz-interactable--hover-forced,
|
||||
&[data-focus-visible-added],
|
||||
.tw-root--hover &.ffz-interactable--hover-enabled:hover {
|
||||
background-color: var(--color-background-interactable-hover);
|
||||
}
|
||||
|
||||
&.ffz-interactable--active,
|
||||
&.ffz-interactable--hover-enabled:hover:active,
|
||||
&:active {
|
||||
background-color: var(--color-background-interactable-active);
|
||||
}
|
||||
|
||||
&.ffz-interactable--selected {
|
||||
color: var(--color-text-interactable-selected);
|
||||
|
||||
&,
|
||||
&.ffz-interactable--hover-enabled:hover:active,
|
||||
&.ffz-interactable--hover-forced,
|
||||
&:active,
|
||||
&[data-focus-visible-added],
|
||||
.tw-root &.ffz-interactable--hover-enabled:hover {
|
||||
background-color: var(--color-background-interactable-selected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-interactable--alert {
|
||||
&, &:hover {
|
||||
color: var(--color-text-alert);
|
||||
}
|
||||
|
||||
&.ffz-interactable--hover-forced,
|
||||
&[data-focus-visible-added],
|
||||
.tw-root--hover &.ffz-interactable--hover-enabled:hover {
|
||||
background-color: var(--color-background-interactable-destructive-hover);
|
||||
color: var(--color-text-interactable-inverted);
|
||||
}
|
||||
|
||||
&.ffz-interactable--active,
|
||||
&.ffz-interactable--hover-enabled:hover:active,
|
||||
&:active {
|
||||
background-color: var(--color-background-interactable-destructive-active);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-interactable--overlay {
|
||||
background-color: transparent;
|
||||
|
||||
&.ffz-interactable--hover-forced,
|
||||
&[data-focus-visible-added],
|
||||
.tw-root--hover &.ffz-interactable--hover-enabled:hover {
|
||||
background-color: var(--color-background-interactable-overlay-hover);
|
||||
}
|
||||
|
||||
&.ffz-interactable--active,
|
||||
&.ffz-interactable--hover-enabled:hover:active,
|
||||
&:active {
|
||||
background-color: var(--color-background-interactable-overlay-active);
|
||||
}
|
||||
|
||||
&.ffz-interactable--selected {
|
||||
color: var(--color-text-interactable-overlay-selected);
|
||||
|
||||
&,
|
||||
&.ffz-interactable--hover-enabled:hover:active,
|
||||
&.ffz-interactable--hover-forced,
|
||||
&:active,
|
||||
&[data-focus-visible-added],
|
||||
.tw-root &.ffz-interactable--hover-enabled:hover {
|
||||
background: var(--color-background-interactable-overlay-selected);
|
||||
}
|
||||
}
|
||||
|
||||
&.ffz-interactable--border {
|
||||
border-color: var(--color-border-overlay);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-interactable--border {
|
||||
border: var(--border-width-default) solid var(--color-border-base);
|
||||
|
||||
&.ffz-interactable--selected {
|
||||
border: var(--border-width-default) solid var(--color-border-interactable-selected);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-interactable--selectable-text {
|
||||
-ms-user-select: text;
|
||||
-webkit-user-select: text;
|
||||
user-select: text
|
||||
}
|
232
styles/input/text.scss
Normal file
232
styles/input/text.scss
Normal file
|
@ -0,0 +1,232 @@
|
|||
/*
|
||||
* Extracted styles for Twitch-like input elements
|
||||
* because Twitch is stupid and removed their nice,
|
||||
* standardized classes with nice standardized names
|
||||
* in favor of procedural classes.
|
||||
*/
|
||||
|
||||
|
||||
// Input / Shared
|
||||
|
||||
.ffz-input {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border: var(--border-width-input) solid var(--color-border-input);
|
||||
color: var(--color-text-input);
|
||||
line-height: 1.5;
|
||||
|
||||
&[type=number] {
|
||||
-moz-appearance: textfield;
|
||||
|
||||
&::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
&::-webkit-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-form-tag, .ffz-input, .ffz-select, .ffz-select-button, .ffz-textarea {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.ffz-input, .ffz-select, .ffz-textarea {
|
||||
font-family: inherit;
|
||||
transition: box-shadow .1s ease-in, border .1s ease-in, background-color .1s ease-in;
|
||||
|
||||
//&::-webkit-input-placeholder,
|
||||
//&:-ms-input-placeholder,
|
||||
//&::-ms-input-placeholder,
|
||||
&::placeholder {
|
||||
color: var(--color-text-input-placeholder);
|
||||
}
|
||||
|
||||
&::-ms-clear {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:-moz-focus-inner {
|
||||
border: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&, &:hover {
|
||||
background-color: var(--color-background-input);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--color-border-input-hover);
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&:focus, &:focus:hover {
|
||||
background-color: var(--color-background-input-focus);
|
||||
border: var(--border-width-input) solid var(--color-border-input-focus);
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-input--error, .ffz-select--error {
|
||||
box-shadow: var(--shadow-input-error);
|
||||
|
||||
&, &:focus {
|
||||
border: var(--border-width-input) solid var(--color-border-input-error);
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-input--error:focus,
|
||||
.ffz-select--error:focus {
|
||||
box-shadow: var(--shadow-input-error-focus);
|
||||
}
|
||||
|
||||
.ffz-input--overlay,
|
||||
.ffz-select--overlay,
|
||||
.ffz-textarea--overlay {
|
||||
background-color: var(--color-background-input-overlay);
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 var(--border-width-input) var(--color-border-input-overlay);
|
||||
color: var(--color-text-overlay);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-background-input-overlay);
|
||||
box-shadow: inset 0 0 0 var(--border-width-input) var(--color-border-input-overlay-hover);
|
||||
}
|
||||
|
||||
&:focus, &:focus:hover {
|
||||
background-color: var(--color-background-input-overlay-focus);
|
||||
border: none;
|
||||
box-shadow: inset 0 0 0 var(--border-width-input) var(--color-border-input-overlay-focus);
|
||||
}
|
||||
|
||||
//&::-webkit-input-placeholder,
|
||||
//&:-ms-input-placeholder,
|
||||
//&::-ms-input-placeholder,
|
||||
&::placeholder {
|
||||
color: var(--color-text-input-placeholder-overlay);
|
||||
}
|
||||
|
||||
&.ffz-input--error,
|
||||
&.ffz-select--error {
|
||||
border: var(--border-width-input) solid var(--color-border-input-error);
|
||||
box-shadow: var(--shadow-input-error);
|
||||
|
||||
&:focus {
|
||||
border: var(--border-width-input) solid var(--color-border-input-error);
|
||||
box-shadow: var(--shadow-input-error-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-input--password {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
.ffz-input__icon {
|
||||
min-width: 3rem;
|
||||
}
|
||||
|
||||
.ffz-input__icon--overlay {
|
||||
color: var(--color-text-input-placeholder-overlay);
|
||||
}
|
||||
|
||||
.ffz-input--small,
|
||||
.ffz-select--small {
|
||||
height: 2.4rem;
|
||||
padding: .2rem 0
|
||||
}
|
||||
|
||||
.ffz-input, .ffz-select {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.ffz-input--large,
|
||||
.ffz-select--large {
|
||||
height: 3.6rem
|
||||
}
|
||||
|
||||
|
||||
// Select
|
||||
|
||||
.ffz-select {
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%230e0e10' d='M10.5 13.683l2.85-2.442 1.3 1.518-3.337 2.86a1.25 1.25 0 01-1.626 0l-3.338-2.86 1.302-1.518 2.849 2.442zm0-7.366L7.65 8.76l-1.3-1.518 3.337-2.86a1.25 1.25 0 011.627 0l3.337 2.86-1.302 1.518L10.5 6.317z'/%3E%3C/svg%3E");
|
||||
background-position: right .8rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 2rem;
|
||||
border: var(--border-width-input) solid var(--color-border-input);
|
||||
color: var(--color-text-input);
|
||||
cursor: pointer;
|
||||
line-height: 1.5;
|
||||
line-height: normal;
|
||||
|
||||
.tw-root--theme-dark & {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23efeff1' d='M10.5 13.683l2.85-2.442 1.3 1.518-3.337 2.86a1.25 1.25 0 01-1.626 0l-3.338-2.86 1.302-1.518 2.849 2.442zm0-7.366L7.65 8.76l-1.3-1.518 3.337-2.86a1.25 1.25 0 011.627 0l3.337 2.86-1.302 1.518L10.5 6.317z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
// option isn't scoped, unlike default Twitch, to avoid flicker
|
||||
&[data-focus-visible-added], option {
|
||||
background-color: var(--color-background-input-focus);
|
||||
border-color: var(--color-border-input-focus)
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-select--overlay {
|
||||
&,
|
||||
.tw-root--theme-dark &,
|
||||
.tw-root--theme-light & {
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M10.5 13.683l2.85-2.442 1.3 1.518-3.337 2.86a1.25 1.25 0 01-1.626 0l-3.338-2.86 1.302-1.518 2.849 2.442zm0-7.366L7.65 8.76l-1.3-1.518 3.337-2.86a1.25 1.25 0 011.627 0l3.337 2.86-1.302 1.518L10.5 6.317z'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
&[data-focus-visible-added] {
|
||||
&, & option {
|
||||
background-color: var(--color-background-input-overlay-focus);
|
||||
box-shadow: inset 0 0 0 var(--border-width-input) var(--color-border-input-overlay-focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-select--small {
|
||||
background-size: 1.6rem;
|
||||
}
|
||||
|
||||
.ffz-select--large {
|
||||
background-size: 2.4rem;
|
||||
}
|
||||
|
||||
|
||||
// Textarea
|
||||
|
||||
.ffz-textarea {
|
||||
-moz-appearance: none;
|
||||
-ms-overflow-style: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
border: var(--border-width-input) solid var(--color-border-input);
|
||||
color: var(--color-text-input);
|
||||
padding: .5rem 1rem;
|
||||
resize: vertical;
|
||||
|
||||
&[cols] {
|
||||
max-width: 100%;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.ffz-textarea--error:focus {
|
||||
box-shadow: var(--shadow-input-error-focus);
|
||||
}
|
||||
|
||||
.ffz-textarea--no-resize {
|
||||
resize: none;
|
||||
}
|
|
@ -3,6 +3,9 @@
|
|||
@import 'widgets';
|
||||
@import 'dialog';
|
||||
|
||||
@import 'input/index';
|
||||
@import 'structure/index';
|
||||
|
||||
@import 'chat';
|
||||
|
||||
@keyframes ffz-rotateplane {
|
||||
|
@ -12,7 +15,6 @@
|
|||
100% { transform: rotateY(90deg) rotateX(0deg) }
|
||||
}
|
||||
|
||||
|
||||
.ffz-i-t-reset.loading,
|
||||
.ffz-i-t-reset-clicked.loading,
|
||||
.ffz-i-zreknarf.loading {
|
||||
|
|
23
styles/structure/balloon.scss
Normal file
23
styles/structure/balloon.scss
Normal file
|
@ -0,0 +1,23 @@
|
|||
.ffz-balloon {
|
||||
max-width: 90vw;
|
||||
min-width: 16rem;
|
||||
|
||||
&--auto {
|
||||
min-width: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&--xs {
|
||||
min-width: 10rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
&--sm { width: 20rem }
|
||||
&--md { width: 30rem }
|
||||
&--lg { width: 40rem }
|
||||
&--xl { width: 50rem }
|
||||
}
|
||||
|
||||
.ffz-balloon--overlay-border {
|
||||
border: 2px solid var(--color-border-balloon-overlay);
|
||||
}
|
26
styles/structure/index.scss
Normal file
26
styles/structure/index.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
@import "balloon";
|
||||
|
||||
.ffz-avatar {
|
||||
background-color: inherit;
|
||||
position: relative;
|
||||
|
||||
&--size-10 { height: 1rem; width: 1rem }
|
||||
&--size-15 { width: 1.5rem; height: 1.5rem }
|
||||
&--size-20 { width: 2rem; height: 2rem }
|
||||
&--size-24 { width: 2.4rem; height: 2.4rem }
|
||||
&--size-30 { width: 3rem; height: 3rem }
|
||||
&--size-36 { width: 3.6rem; height: 3.6rem }
|
||||
&--size-40 { width: 4rem; height: 4rem }
|
||||
&--size-50 { width: 5rem; height: 5rem }
|
||||
&--size-60 { width: 6rem; height: 6rem }
|
||||
&--size-64 { width: 6.4rem; height: 6.4rem }
|
||||
&--size-80 { width: 8rem; height: 8rem }
|
||||
&--size-96 { width: 9.6rem; height: 9.6rem }
|
||||
&--size-120 { width: 12rem; height: 12rem }
|
||||
&--size-300 { width: 30rem; height: 30rem }
|
||||
}
|
||||
|
||||
.ffz-image-avatar,
|
||||
.ffz-avatar__img {
|
||||
width: 100%;
|
||||
}
|
|
@ -20,41 +20,41 @@ body {
|
|||
}
|
||||
|
||||
|
||||
.tw-balloon {
|
||||
&[x-placement^="bottom"] > .tw-balloon__tail {
|
||||
.ffz-balloon {
|
||||
&[x-placement^="bottom"] > .ffz-balloon__tail {
|
||||
bottom: 100%;
|
||||
|
||||
.tw-balloon__tail-symbol {
|
||||
.ffz-balloon__tail-symbol {
|
||||
top: auto;
|
||||
bottom: -8px;
|
||||
left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="top"] > .tw-balloon__tail {
|
||||
&[x-placement^="top"] > .ffz-balloon__tail {
|
||||
top: 100%;
|
||||
|
||||
.tw-balloon__tail-symbol {
|
||||
.ffz-balloon__tail-symbol {
|
||||
top: auto;
|
||||
bottom: 8px;
|
||||
left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="right"] > .tw-balloon__tail {
|
||||
&[x-placement^="right"] > .ffz-balloon__tail {
|
||||
right: 100%;
|
||||
|
||||
.tw-balloon__tail-symbol {
|
||||
.ffz-balloon__tail-symbol {
|
||||
left: auto;
|
||||
right: -8px;
|
||||
top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&[x-placement^="left"] > .tw-balloon__tail {
|
||||
&[x-placement^="left"] > .ffz-balloon__tail {
|
||||
left: 100%;
|
||||
|
||||
.tw-balloon__tail-symbol {
|
||||
.ffz-balloon__tail-symbol {
|
||||
left: auto;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
}
|
||||
|
||||
.ffz-radio-top {
|
||||
.tw-radio__input:checked+.tw-radio__label:after,
|
||||
.tw-radio__label:before {
|
||||
.ffz-radio__input:checked+.ffz-radio__label:after,
|
||||
.ffz-radio__label:before {
|
||||
top: 1rem;
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
textarea.tw-input {
|
||||
textarea.ffz-input {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tw-checkbox__input:checked+.tw-checkbox__label:after,
|
||||
.ffz-checkbox__input:checked+.ffz-checkbox__label:after,
|
||||
label:before, label:after {
|
||||
top: 1.05rem !important;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.tw-checkbox__input {
|
||||
&:indeterminate + .tw-checkbox__label {
|
||||
.ffz-checkbox__input {
|
||||
&:indeterminate + .ffz-checkbox__label {
|
||||
&:before {
|
||||
background: var(--ffz-color-accent-8);
|
||||
border: 1px solid var(--ffz-color-accent-8);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
.ffz--profile-selector {
|
||||
position: relative;
|
||||
|
||||
.tw-balloon {
|
||||
.ffz-balloon {
|
||||
position: absolute;
|
||||
margin-top: 0 !important
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const PRODUCTION = process.env.NODE_ENV === 'production';
|
|||
module.exports = {
|
||||
entry: {
|
||||
bridge: './src/bridge.js',
|
||||
player: './src/player.js',
|
||||
avalon: './src/main.js'
|
||||
},
|
||||
resolve: {
|
||||
|
@ -39,6 +40,9 @@ module.exports = {
|
|||
},
|
||||
optimization: {
|
||||
splitChunks: {
|
||||
chunks(chunk) {
|
||||
return chunk.name !== 'avalon' && chunk.name !== 'bridge' && chunk.name !== 'player'
|
||||
},
|
||||
cacheGroups: {
|
||||
vendors: false
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue