mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-07-05 18:48:31 +00:00
4.0.0-rc13.11
* Added: Debug Log generation and easy uploading for issue reports. * Fixed: Bug with chat lines not having a state causing rendering to crash. * Fixed: Add an error boundary around the custom FFZ Emote Menu to catch errors, displaying feedback and sending error reports rather than silently failing. * Fixed: Typo in Fine when logging errors. * Changed: Update chat types with the latest values from Twitch's source. * Changed: Update the GitHub issue template with instructions on uploading logs from v4.
This commit is contained in:
parent
cb2f2b19ee
commit
d6504757b3
18 changed files with 413 additions and 99 deletions
|
@ -2,7 +2,7 @@
|
|||
|
||||
**Do you use BetterTTV or other Twitch extensions**:
|
||||
|
||||
**FFZ Logs (via FFZ Menu > About > Logs; if Applicable)**:
|
||||
**FFZ Logs (via FFZ Control Center > Home > Feedback >> Log; if Applicable)**:
|
||||
|
||||
|
||||
**Bug / Idea**:
|
||||
|
|
|
@ -123,6 +123,25 @@ export default class ExperimentManager extends Module {
|
|||
}
|
||||
|
||||
|
||||
generateLog() {
|
||||
const out = [
|
||||
`Unique ID: ${this.unique_id}`,
|
||||
''
|
||||
];
|
||||
|
||||
for(const [key, value] of Object.entries(this.experiments)) {
|
||||
out.push(`FFZ | ${value.name}: ${this.getAssignment(key)}${this.hasOverride(key) ? ' (Overriden)' : ''}`);
|
||||
}
|
||||
|
||||
for(const [key, value] of Object.entries(this.getTwitchExperiments())) {
|
||||
if ( this.usingTwitchExperiment(key) )
|
||||
out.push(`TWITCH | ${value.name}: ${this.getTwitchAssignment(key)}${this.hasTwitchOverride(key) ? ' (Overriden)' : ''}`)
|
||||
}
|
||||
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
|
||||
// Twitch Experiments
|
||||
|
||||
getTwitchExperiments() {
|
||||
|
|
54
src/main.js
54
src/main.js
|
@ -1,5 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
import RavenLogger from './raven';
|
||||
|
||||
import Logger from 'utilities/logging';
|
||||
|
@ -13,6 +14,8 @@ import {TranslationManager} from './i18n';
|
|||
import SocketClient from './socket';
|
||||
import Site from 'site';
|
||||
import Vue from 'utilities/vue';
|
||||
import { timeout } from './utilities/object';
|
||||
import { strict } from 'assert';
|
||||
|
||||
class FrankerFaceZ extends Module {
|
||||
constructor() {
|
||||
|
@ -30,10 +33,11 @@ class FrankerFaceZ extends Module {
|
|||
// Error Reporting and Logging
|
||||
// ========================================================================
|
||||
|
||||
if ( ! DEBUG )
|
||||
this.inject('raven', RavenLogger);
|
||||
|
||||
this.log = new Logger(null, null, null, this.raven);
|
||||
this.log.init = true;
|
||||
|
||||
this.core_log = this.log.get('core');
|
||||
|
||||
this.log.info(`FrankerFaceZ v${VER} (build ${VER.build}${VER.commit ? ` - commit ${VER.commit}` : ''})`);
|
||||
|
@ -61,9 +65,11 @@ class FrankerFaceZ extends Module {
|
|||
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;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -72,6 +78,50 @@ class FrankerFaceZ extends Module {
|
|||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Generate Log
|
||||
// ========================================================================
|
||||
|
||||
async generateLog() {
|
||||
const promises = [];
|
||||
for(const key in this.__modules) {
|
||||
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 => {
|
||||
return `${x[0]}
|
||||
-------------------------------------------------------------------------------
|
||||
${typeof x[1] === 'string' ? x[1] : JSON.stringify(x[1], null, 4)}`
|
||||
}).join('\n\n');
|
||||
}
|
||||
|
||||
|
||||
// ========================================================================
|
||||
// Modules
|
||||
// ========================================================================
|
||||
|
@ -100,7 +150,7 @@ class FrankerFaceZ extends Module {
|
|||
FrankerFaceZ.Logger = Logger;
|
||||
|
||||
const VER = FrankerFaceZ.version_info = {
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc13.10',
|
||||
major: 4, minor: 0, revision: 0, extra: '-rc13.11',
|
||||
commit: __git_commit__,
|
||||
build: __webpack_hash__,
|
||||
toString: () =>
|
||||
|
|
|
@ -267,6 +267,9 @@ export default class Badges extends Module {
|
|||
data = JSON.parse(target.dataset.badgeData),
|
||||
out = [];
|
||||
|
||||
if ( data == null )
|
||||
return out;
|
||||
|
||||
for(const d of data) {
|
||||
const p = d.provider;
|
||||
if ( p === 'twitch' ) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// ============================================================================
|
||||
// Chat
|
||||
// ============================================================================
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import Module from 'utilities/module';
|
||||
|
@ -559,6 +560,15 @@ export default class Chat extends Module {
|
|||
}
|
||||
|
||||
|
||||
generateLog() {
|
||||
const out = ['chat settings', '-------------------------------------------------------------------------------'];
|
||||
for(const [key, value] of this.context.__cache.entries())
|
||||
out.push(`${key}: ${JSON.stringify(value)}`);
|
||||
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
|
||||
onEnable() {
|
||||
for(const key in TOKENIZERS)
|
||||
if ( has(TOKENIZERS, key) )
|
||||
|
|
145
src/modules/main_menu/components/async-text.vue
Normal file
145
src/modules/main_menu/components/async-text.vue
Normal file
|
@ -0,0 +1,145 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--example-report tw-relative ">
|
||||
<div v-if="canUpload" class="tw-absolute ffz--report-upload">
|
||||
<div v-if="uploading">
|
||||
<button
|
||||
class="tw-button tw-button--disabled"
|
||||
disabled
|
||||
>
|
||||
<span class="tw-button__icon tw-button__icon--left">
|
||||
<figure class="ffz-i-upload-cloud" />
|
||||
</span>
|
||||
<span class="tw-button__text">
|
||||
{{ t('async-text.uploading', 'Uploading...') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else-if="url">
|
||||
<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"
|
||||
type="text"
|
||||
readonly
|
||||
@focusin="selectURL"
|
||||
>
|
||||
</div>
|
||||
<div v-else>
|
||||
<button
|
||||
class="tw-button"
|
||||
@click="upload"
|
||||
>
|
||||
<span class="tw-button__icon tw-button__icon--left">
|
||||
<figure class="ffz-i-upload-cloud" />
|
||||
</span>
|
||||
<span class="tw-button__text">
|
||||
{{ t('async-text.upload', 'Upload') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tw-c-background-alt-2 tw-font-size-5 tw-pd-y-05 tw-pd-x-1 tw-border-radius-large">
|
||||
<div v-if="loading" class="tw-align-center">
|
||||
<h1 class="tw-mg-5 ffz-i-zreknarf loading" />
|
||||
</div>
|
||||
<code v-else>{{ text }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: ['item', 'context'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
uploading: false,
|
||||
url: null,
|
||||
|
||||
loading: false,
|
||||
text: null
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
canUpload() {
|
||||
return ! this.loading && this.text;
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.refresh();
|
||||
|
||||
const ctx = this.context.context;
|
||||
for(const key of this.item.watch)
|
||||
ctx.on(`changed:${key}`, this.refresh, this);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
const ctx = this.context.context;
|
||||
for(const key of this.item.watch)
|
||||
ctx.off(`changed:${key}`, this.refresh, this);
|
||||
},
|
||||
|
||||
methods: {
|
||||
selectURL() {
|
||||
if ( this.$refs && this.$refs.url_box )
|
||||
this.$refs.url_box.select();
|
||||
},
|
||||
|
||||
async upload() {
|
||||
if ( this.uploading || this.url || this.text == null )
|
||||
return;
|
||||
|
||||
this.uploading = true;
|
||||
const response = await fetch('https://putco.de', {
|
||||
method: 'PUT',
|
||||
body: this.text
|
||||
});
|
||||
|
||||
if ( ! response.ok ) {
|
||||
this.uploading = false;
|
||||
this.url = 'An error occured.';
|
||||
}
|
||||
|
||||
this.url = await response.text();
|
||||
if ( this.url.startsWith('http://') )
|
||||
this.url = `https://${this.url.slice(7)}`;
|
||||
|
||||
this.uploading = false;
|
||||
},
|
||||
|
||||
async refresh() {
|
||||
this.uploading = false;
|
||||
this.url = null;
|
||||
this.loading = true;
|
||||
this.text = await this.item.data();
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ffz--example-report {
|
||||
div {
|
||||
max-height: 30rem;
|
||||
overflow-y: auto;
|
||||
|
||||
code {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ffz--report-upload {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
right: 3rem;
|
||||
}
|
||||
</style>
|
|
@ -1,58 +0,0 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--example-report">
|
||||
<h4 class="tw-mg-b-05">{{ t('reports.example', 'Example Report') }}</h4>
|
||||
<div class="tw-c-background-alt-2 tw-font-size-5 tw-pd-y-05 tw-pd-x-1 tw-border-radius-large">
|
||||
<code>{{ JSON.stringify(example, null, 4) }}</code>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: ['item', 'context'],
|
||||
|
||||
data() {
|
||||
return {
|
||||
example: null
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.refresh();
|
||||
|
||||
const ctx = this.context.context;
|
||||
for(const key of this.item.watch)
|
||||
ctx.on(`changed:${key}`, this.refresh, this);
|
||||
},
|
||||
|
||||
destroyed() {
|
||||
const ctx = this.context.context;
|
||||
for(const key of this.item.watch)
|
||||
ctx.off(`changed:${key}`, this.refresh, this);
|
||||
},
|
||||
|
||||
methods: {
|
||||
refresh() {
|
||||
this.example = 'Loading...';
|
||||
this.item.data().then(data => this.example = data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.ffz--example-report {
|
||||
div {
|
||||
max-height: 30rem;
|
||||
overflow-y: auto;
|
||||
|
||||
code {
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,15 +1,9 @@
|
|||
<template lang="html">
|
||||
<div class="ffz--home tw-border-t tw-pd-t-1">
|
||||
<div class="ffz--home tw-border-t tw-pd-y-1">
|
||||
<h2>Feedback</h2>
|
||||
|
||||
<div class="tw-mg-y-1 tw-c-background-accent tw-c-text-overlay tw-pd-1">
|
||||
<h3 class="ffz-i-attention">
|
||||
Please keep in mind that FrankerFaceZ v4 is under heavy development.
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Okay, still here? Great! You can provide feedback and bug reports by
|
||||
You can provide feedback and bug reports by
|
||||
<a href="https://github.com/FrankerFaceZ/FrankerFaceZ/issues" target="_blank" rel="noopener">
|
||||
opening an issue at our GitHub repository</a>.
|
||||
|
||||
|
@ -21,6 +15,11 @@
|
|||
When creating a GitHub issue, please check that someone else hasn't
|
||||
already created one for what you'd like to discuss or report.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
When creating an issue, please also upload the following logs and
|
||||
include a link in your report.
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -57,6 +57,16 @@ export default class MainMenu extends Module {
|
|||
component: 'feedback-page'
|
||||
});
|
||||
|
||||
this.settings.addUI('feedback.log', {
|
||||
path: 'Home > Feedback >> Log @{"sort": 1000}',
|
||||
component: 'async-text',
|
||||
watch: [
|
||||
'reports.error.include-user',
|
||||
'reports.error.include-settings'
|
||||
],
|
||||
data: () => this.resolve('core').generateLog()
|
||||
})
|
||||
|
||||
this.settings.addUI('changelog', {
|
||||
path: 'Home > Changelog',
|
||||
component: 'changelog'
|
||||
|
|
|
@ -832,6 +832,9 @@ export default class Metadata extends Module {
|
|||
|
||||
} else {
|
||||
stat = el.querySelector('.ffz-stat-text');
|
||||
if ( ! stat )
|
||||
return destroy();
|
||||
|
||||
old_color = el.dataset.color || '';
|
||||
|
||||
if ( el._ffz_order !== order )
|
||||
|
|
54
src/raven.js
54
src/raven.js
|
@ -6,12 +6,16 @@
|
|||
// Raven Logging
|
||||
// ============================================================================
|
||||
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import {DEBUG, SENTRY_ID} from 'utilities/constants';
|
||||
import {has} from 'utilities/object';
|
||||
import Module from 'utilities/module';
|
||||
|
||||
import Raven from 'raven-js';
|
||||
|
||||
const STRIP_URLS = /((?:\?|&)[^?&=]*?(?:oauth|token)[^?&=]*?=)[^?&]*?(&|$)/i;
|
||||
|
||||
const AVALON_REG = /\/(?:script|static)\/((?:babel\/)?avalon)(\.js)(\?|#|$)/,
|
||||
fix_url = url => url.replace(AVALON_REG, `/static/$1.${__webpack_hash__}$2$3`);
|
||||
|
||||
|
@ -90,8 +94,8 @@ export default class RavenLogger extends Module {
|
|||
});
|
||||
|
||||
this.settings.addUI('reports.error.example', {
|
||||
path: 'Data Management > Reporting >> Error Reports',
|
||||
component: 'example-report',
|
||||
path: 'Data Management > Reporting >> Example Report',
|
||||
component: 'async-text',
|
||||
|
||||
watch: [
|
||||
'reports.error.enable',
|
||||
|
@ -101,7 +105,9 @@ export default class RavenLogger extends Module {
|
|||
|
||||
data: () => new Promise(r => {
|
||||
// Why fake an error when we can *make* an error?
|
||||
this.__example_waiter = r;
|
||||
this.__example_waiter = data => {
|
||||
r(JSON.stringify(data, null, 4));
|
||||
};
|
||||
|
||||
// Generate the error in a timeout so that the end user
|
||||
// won't have a huge wall of a fake stack trace wasting
|
||||
|
@ -121,7 +127,7 @@ export default class RavenLogger extends Module {
|
|||
|
||||
this.raven = Raven;
|
||||
|
||||
Raven.config(SENTRY_ID, {
|
||||
const raven_config = {
|
||||
autoBreadcrumbs: {
|
||||
console: false
|
||||
},
|
||||
|
@ -134,9 +140,6 @@ export default class RavenLogger extends Module {
|
|||
'Access is denied.',
|
||||
'Zugriff verweigert'
|
||||
],
|
||||
whitelistUrls: [
|
||||
/cdn\.frankerfacez\.com/
|
||||
],
|
||||
sanitizeKeys: [
|
||||
/Token$/
|
||||
],
|
||||
|
@ -166,6 +169,10 @@ export default class RavenLogger extends Module {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Don't send errors in debug mode.
|
||||
//if ( DEBUG && !(data.tags && data.tags.example) )
|
||||
// return false;
|
||||
|
||||
const exc = data.exception && data.exception.values[0];
|
||||
|
||||
// We don't want any of Sentry's junk.
|
||||
|
@ -207,7 +214,38 @@ export default class RavenLogger extends Module {
|
|||
|
||||
return true;
|
||||
}
|
||||
}).install();
|
||||
};
|
||||
|
||||
if ( ! DEBUG )
|
||||
raven_config.whitelistUrls = [
|
||||
/cdn\.frankerfacez\.com/
|
||||
];
|
||||
|
||||
Raven.config(SENTRY_ID, raven_config).install();
|
||||
}
|
||||
|
||||
|
||||
generateLog() {
|
||||
if ( ! this.raven || ! this.raven._breadcrumbs )
|
||||
return 'No breadcrumbs to log.';
|
||||
|
||||
return this.raven._breadcrumbs.map(crumb => {
|
||||
const time = dayjs(crumb.timestamp).locale('en').format('H:mm:ss');
|
||||
if ( crumb.type == 'http' )
|
||||
return `[${time}] HTTP | ${crumb.category}: ${crumb.data.method} ${crumb.data.url} -> ${crumb.data.status_code}`;
|
||||
|
||||
let cat = 'LOG';
|
||||
if ( crumb.category && crumb.category.includes('ui.') )
|
||||
cat = 'UI';
|
||||
|
||||
return `[${time}] ${cat}${crumb.level ? `:${crumb.level}` : ''} | ${crumb.category}: ${crumb.message}${crumb.data ? `\n ${JSON.stringify(crumb.data)}` : ''}`;
|
||||
|
||||
}).map(x => {
|
||||
if ( typeof x !== 'string' )
|
||||
x = `${x}`;
|
||||
|
||||
return x.replace(STRIP_URLS, '$1REDACTED$2');
|
||||
}).join('\n');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -64,6 +64,14 @@ export default class SettingsManager extends Module {
|
|||
this.enable();
|
||||
}
|
||||
|
||||
generateLog() {
|
||||
const out = [];
|
||||
for(const [key, value] of this.main_context.__cache.entries())
|
||||
out.push(`${key}: ${JSON.stringify(value)}`);
|
||||
|
||||
return out.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the SettingsManager instance should be enabled.
|
||||
*/
|
||||
|
|
|
@ -277,7 +277,8 @@ export default class EmoteMenu extends Module {
|
|||
if ( ! this.props || ! has(this.props, 'channelOwnerID') || ! t.chat.context.get('chat.emote-menu.enabled') )
|
||||
return old_render.call(this);
|
||||
|
||||
return (<t.MenuComponent
|
||||
return (<t.MenuErrorWrapper visible={this.props.visible}>
|
||||
<t.MenuComponent
|
||||
visible={this.props.visible}
|
||||
toggleVisibility={this.props.toggleVisibility}
|
||||
onClickEmote={this.props.onClickEmote}
|
||||
|
@ -287,7 +288,8 @@ export default class EmoteMenu extends Module {
|
|||
channel_id={this.props.channelOwnerID}
|
||||
loading={this.state.gqlLoading}
|
||||
error={this.state.gqlError}
|
||||
/>)
|
||||
/>
|
||||
</t.MenuErrorWrapper>)
|
||||
}
|
||||
|
||||
this.EmoteMenu.forceUpdate();
|
||||
|
@ -731,6 +733,73 @@ export default class EmoteMenu extends Module {
|
|||
setTimeout(doClear, 100);
|
||||
};
|
||||
|
||||
this.MenuErrorWrapper = class FFZEmoteMenuErrorWrapper extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {errored: false, error: null};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error) {
|
||||
return {
|
||||
errored: true,
|
||||
error
|
||||
}
|
||||
}
|
||||
|
||||
componentDidCatch(error) { // eslint-disable-line class-methods-use-this
|
||||
t.log.capture(error);
|
||||
t.log.error('Error rendering the FFZ Emote Menu.');
|
||||
this.setState({
|
||||
errored: true,
|
||||
error
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if ( this.state.errored ) {
|
||||
if ( ! this.props.visible )
|
||||
return null;
|
||||
|
||||
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' : ''}`}
|
||||
data-a-target="emote-picker"
|
||||
>
|
||||
<div class="tw-border tw-elevation-1 tw-border-radius-small tw-c-background-base">
|
||||
<div
|
||||
class="emote-picker__tab-content scrollable-area"
|
||||
data-test-selector="scrollable-area-wrapper"
|
||||
data-simplebar
|
||||
>
|
||||
<div class="tw-align-center tw-pd-1">
|
||||
<div class="tw-mg-b-1">
|
||||
<div class="tw-mg-2">
|
||||
<img
|
||||
src="//cdn.frankerfacez.com/emoticon/26608/2"
|
||||
srcSet="//cdn.frankerfacez.com/emoticon/26608/2 1x, //cdn.frankerfacez.com/emoticon/26608/4 2x"
|
||||
/>
|
||||
</div>
|
||||
{t.i18n.t('emote-menu.error', 'There was an error rendering this menu.')}
|
||||
<br />
|
||||
{t.settings.get('reports.error.enable') ?
|
||||
t.i18n.t('emote-menu.error-report', 'An error report has been automatically submitted.')
|
||||
: ''
|
||||
}
|
||||
<div class="tw-mg-t-05 tw-border-t-1 tw-pd-t-05">
|
||||
{t.i18n.t('emote-menu.disable', 'As a temporary workaround, try disabling the FFZ Emote Menu in the FFZ Control Center.') }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
this.MenuComponent = class FFZEmoteMenuComponent extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
|
@ -105,7 +105,8 @@ const CHAT_TYPES = make_enum(
|
|||
'CrateGift',
|
||||
'RewardGift',
|
||||
'SubMysteryGift',
|
||||
'AnonSubMysteryGift'
|
||||
'AnonSubMysteryGift',
|
||||
'FirstCheerMessage'
|
||||
);
|
||||
|
||||
|
||||
|
@ -268,7 +269,6 @@ export default class ChatHook extends Module {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
get currentChat() {
|
||||
for(const inst of this.ChatController.instances)
|
||||
if ( inst && inst.chatService )
|
||||
|
|
|
@ -209,7 +209,7 @@ export default class ChatLine extends Module {
|
|||
const old_render = cls.prototype.render;
|
||||
|
||||
cls.prototype.shouldComponentUpdate = function(props, state) {
|
||||
const show = state.alwaysShowMessage || ! props.message.deleted,
|
||||
const show = state && state.alwaysShowMessage || ! props.message.deleted,
|
||||
old_show = this._ffz_show;
|
||||
|
||||
// We can't just compare props.message.deleted to this.props.message.deleted
|
||||
|
@ -256,7 +256,7 @@ export default class ChatLine extends Module {
|
|||
show = true;
|
||||
show_class = msg.deleted;
|
||||
} else {
|
||||
show = this.state.alwaysShowMessage || ! msg.deleted;
|
||||
show = this.state && this.state.alwaysShowMessage || ! msg.deleted;
|
||||
show_class = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -619,7 +619,7 @@ export class FineWrapper extends EventEmitter {
|
|||
}
|
||||
});
|
||||
|
||||
this.finelog.error(`An error occured when calling forceUpdate on an instance of ${this.name}`, err);
|
||||
this.fine.log.error(`An error occurred when calling forceUpdate on an instance of ${this.name}`, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -116,7 +116,7 @@ export default class Dialog extends EventEmitter {
|
|||
visible = this._visible = ! this._visible,
|
||||
container = this.getContainer();
|
||||
|
||||
if ( maximized )
|
||||
if ( maximized && container )
|
||||
container.classList.toggle('ffz-has-dialog', visible);
|
||||
|
||||
if ( ! visible ) {
|
||||
|
@ -129,6 +129,9 @@ export default class Dialog extends EventEmitter {
|
|||
return;
|
||||
}
|
||||
|
||||
if ( ! container )
|
||||
return;
|
||||
|
||||
if ( this.factory ) {
|
||||
const el = this.factory();
|
||||
if ( el instanceof Promise ) {
|
||||
|
@ -163,12 +166,14 @@ export default class Dialog extends EventEmitter {
|
|||
if ( container === old_container )
|
||||
return;
|
||||
|
||||
if ( maximized )
|
||||
if ( maximized ) {
|
||||
if ( container )
|
||||
container.classList.add('ffz-has-dialog');
|
||||
else
|
||||
} else if ( old_container )
|
||||
old_container.classList.remove('ffz-has-dialog');
|
||||
|
||||
this._element.remove();
|
||||
if ( container )
|
||||
container.appendChild(this._element);
|
||||
|
||||
this.emit('resize');
|
||||
|
|
|
@ -10,9 +10,14 @@ const RAVEN_LEVELS = {
|
|||
|
||||
export default class Logger {
|
||||
constructor(parent, name, level, raven) {
|
||||
this.root = parent ? parent.root : this;
|
||||
this.parent = parent;
|
||||
this.name = name;
|
||||
|
||||
if ( this.root == this )
|
||||
this.captured_init = [];
|
||||
|
||||
this.init = false;
|
||||
this.enabled = true;
|
||||
this.level = level || (parent && parent.level) || Logger.DEFAULT_LEVEL;
|
||||
this.raven = raven || (parent && parent.raven);
|
||||
|
@ -68,6 +73,14 @@ export default class Logger {
|
|||
|
||||
const message = Array.prototype.slice.call(args);
|
||||
|
||||
if ( this.root.init )
|
||||
this.root.captured_init.push({
|
||||
time: Date.now(),
|
||||
category: this.name,
|
||||
message: message.join(' '),
|
||||
level: RAVEN_LEVELS[level] || level
|
||||
});
|
||||
|
||||
this.crumb({
|
||||
message: message.join(' '),
|
||||
category: this.name,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue