mirror of
https://github.com/FrankerFaceZ/FrankerFaceZ.git
synced 2025-08-10 16:10:55 +00:00
4.59.0
* Fixed: Appearance of the page when viewing a Watch Party. * Fixed: During the initial load, some CSS blocks could be incorrectly injected into the page due to a race condition. * Fixed: The sample embed in Chat > Appearance >> Rich Content not appearing correctly. * API Added: New event class `FFZWaitableEvent`, a subclass of `FFZEvent` providing a framework for asynchronous event handlers. * API Added: `site.channel:update-bar` event, fired whenever the channel info bar is updated. * API Fixed: `chat.removeTokenizer()`, `chat.removeLinkProvider()`, and `chat.removeRichProvider()` failing to fully remove their respective items. * API Removed: The `emitAsync` method has been removed from modules. Nothing was using it, and it was problematic due to the concurrent access protection on events. Instead, `FFZWaitableEvent` should be used if asynchronous waiting is necessary.
This commit is contained in:
parent
675512e811
commit
a7e131070e
13 changed files with 156 additions and 112 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "frankerfacez",
|
"name": "frankerfacez",
|
||||||
"author": "Dan Salvato LLC",
|
"author": "Dan Salvato LLC",
|
||||||
"version": "4.58.0",
|
"version": "4.59.0",
|
||||||
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
"description": "FrankerFaceZ is a Twitch enhancement suite.",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|
|
@ -9,7 +9,7 @@ let tokenizer;
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['data', 'url', 'events', 'forceFull', 'forceUnsafe', 'forceMedia', 'forceMid', 'noLink', 'noTooltip', 'noElevation', 'noUnsafe'],
|
props: ['data', 'url', 'events', 'forceFull', 'forceUnsafe', 'forceMedia', 'forceShort', 'forceMid', 'noLink', 'noTooltip', 'noElevation', 'noUnsafe'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -256,9 +256,13 @@ export default {
|
||||||
|
|
||||||
renderBody(h) {
|
renderBody(h) {
|
||||||
let body;
|
let body;
|
||||||
if ( this.forceFull === true || (this.forceFull !== false && this.full) )
|
if ( this.forceShort )
|
||||||
|
body = this.short;
|
||||||
|
else if ( this.forceMid )
|
||||||
|
body = this.mid;
|
||||||
|
else if ( this.forceFull || (this.forceFull !== false && this.full) )
|
||||||
body = this.full;
|
body = this.full;
|
||||||
else if ( this.forceMid === true || (this.forceMid !== false && this.mid) )
|
else if ( this.forceMid || (this.forceMid !== false && this.mid) )
|
||||||
body = this.mid;
|
body = this.mid;
|
||||||
else
|
else
|
||||||
body = this.short;
|
body = this.short;
|
||||||
|
|
|
@ -2316,6 +2316,8 @@ export default class Chat extends Module {
|
||||||
if ( ! tokenizer )
|
if ( ! tokenizer )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
delete this.tokenizers[type];
|
||||||
|
|
||||||
if ( tokenizer.tooltip )
|
if ( tokenizer.tooltip )
|
||||||
delete this.tooltips.types[type];
|
delete this.tooltips.types[type];
|
||||||
|
|
||||||
|
@ -2354,6 +2356,8 @@ export default class Chat extends Module {
|
||||||
if ( ! provider )
|
if ( ! provider )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
delete this.link_providers[type];
|
||||||
|
|
||||||
const idx = this.__link_providers.indexOf(provider);
|
const idx = this.__link_providers.indexOf(provider);
|
||||||
if ( idx !== -1 )
|
if ( idx !== -1 )
|
||||||
this.__link_providers.splice(idx, 1);
|
this.__link_providers.splice(idx, 1);
|
||||||
|
@ -2389,6 +2393,8 @@ export default class Chat extends Module {
|
||||||
if ( ! provider )
|
if ( ! provider )
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
delete this.rich_providers[type];
|
||||||
|
|
||||||
const idx = this.__rich_providers.indexOf(provider);
|
const idx = this.__rich_providers.indexOf(provider);
|
||||||
if ( idx !== -1 )
|
if ( idx !== -1 )
|
||||||
this.__rich_providers.splice(idx, 1);
|
this.__rich_providers.splice(idx, 1);
|
||||||
|
|
|
@ -6,16 +6,19 @@
|
||||||
<chat-rich
|
<chat-rich
|
||||||
:data="data"
|
:data="data"
|
||||||
:url="url"
|
:url="url"
|
||||||
|
:force-short="true"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
import { maybe_call } from 'utilities/object';
|
||||||
|
|
||||||
const VIDEOS = [
|
const VIDEOS = [
|
||||||
'https://www.twitch.tv/dansalvato',
|
'https://www.twitch.tv/dansalvato',
|
||||||
'https://www.twitch.tv/sirstendec',
|
'https://www.twitch.tv/sirstendec',
|
||||||
//'https://www.youtube.com/watch?v=BFSWlDpA6C4'
|
'https://www.youtube.com/watch?v=BFSWlDpA6C4'
|
||||||
];
|
];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -29,14 +32,18 @@ export default {
|
||||||
props: ['context', 'item'],
|
props: ['context', 'item'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
const url = VIDEOS[Math.floor(Math.random() * VIDEOS.length)],
|
let url = maybe_call(this.item.extra.url, this, this.item, this.context);
|
||||||
token = {
|
if ( ! url )
|
||||||
|
url = VIDEOS[Math.floor(Math.random() * VIDEOS.length)];
|
||||||
|
|
||||||
|
const token = {
|
||||||
type: 'link',
|
type: 'link',
|
||||||
force_rich: true,
|
force_rich: true,
|
||||||
is_mail: false,
|
is_mail: false,
|
||||||
url,
|
url,
|
||||||
text: url
|
text: url
|
||||||
},
|
},
|
||||||
|
|
||||||
chat = this.item.extra.getChat();
|
chat = this.item.extra.getChat();
|
||||||
|
|
||||||
let data = null;
|
let data = null;
|
||||||
|
|
|
@ -483,6 +483,8 @@ export default class Channel extends Module {
|
||||||
|
|
||||||
this.updateSubscription(props.channelID, props.channelLogin);
|
this.updateSubscription(props.channelID, props.channelLogin);
|
||||||
this.updateMetadata(el);
|
this.updateMetadata(el);
|
||||||
|
|
||||||
|
this.emit(':update-bar', el, props, channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeBar(el) {
|
removeBar(el) {
|
||||||
|
|
|
@ -909,6 +909,7 @@ export default class ChatHook extends Module {
|
||||||
|
|
||||||
this.css_tweaks.toggle('chat-font', size !== 13 || font !== 'inherit');
|
this.css_tweaks.toggle('chat-font', size !== 13 || font !== 'inherit');
|
||||||
this.css_tweaks.toggle('chat-width', this.settings.get('chat.use-width'));
|
this.css_tweaks.toggle('chat-width', this.settings.get('chat.use-width'));
|
||||||
|
this.css_tweaks.toggle('chat-fix--watch-party', this.settings.get('context.isWatchParty'));
|
||||||
|
|
||||||
this.css_tweaks.toggle('emote-alignment-padded', emote_alignment === 1);
|
this.css_tweaks.toggle('emote-alignment-padded', emote_alignment === 1);
|
||||||
this.css_tweaks.toggle('emote-alignment-baseline', emote_alignment === 2);
|
this.css_tweaks.toggle('emote-alignment-baseline', emote_alignment === 2);
|
||||||
|
|
|
@ -72,6 +72,8 @@ export default class CSSTweaks extends Module {
|
||||||
this.chunks = {};
|
this.chunks = {};
|
||||||
this.chunks_loaded = false;
|
this.chunks_loaded = false;
|
||||||
|
|
||||||
|
this._state = {};
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
|
|
||||||
this.settings.add('metadata.modview.hide-info', {
|
this.settings.add('metadata.modview.hide-info', {
|
||||||
|
@ -574,17 +576,33 @@ export default class CSSTweaks extends Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async toggle(key, val) {
|
toggle(key, val) {
|
||||||
|
val = !! val;
|
||||||
|
if ( (this._state[key] ?? false) === val )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._state[key] = val;
|
||||||
|
this._apply(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
_apply(key) {
|
||||||
|
const val = this._state[key];
|
||||||
if ( ! val ) {
|
if ( ! val ) {
|
||||||
|
if ( this.style )
|
||||||
this.style.delete(key);
|
this.style.delete(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! this.chunks_loaded )
|
if ( this.style.has(key) )
|
||||||
await this.populate();
|
return;
|
||||||
|
|
||||||
if ( ! has(this.chunks, key) )
|
if ( ! this.chunks_loaded )
|
||||||
throw new Error(`cannot find chunk "${key}"`);
|
return this.populate().then(() => this._apply(key));
|
||||||
|
|
||||||
|
if ( ! has(this.chunks, key) ) {
|
||||||
|
this.log.warn(`Unknown chunk name "${key}" for toggle()`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.style.set(key, this.chunks[key]);
|
this.style.set(key, this.chunks[key]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
.channel-root__right-column--host-player-above-chat {
|
||||||
|
transition: none !important;
|
||||||
|
transform: none !important;
|
||||||
|
position: initial !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-visibility__right-column--expanded {
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body .channel-root--hold-chat + .persistent-player,
|
||||||
|
body .channel-root--watch-chat + .persistent-player {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.channel-root__info--with-chat .channel-info-content,
|
||||||
|
.channel-root__player--with-chat {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
|
@ -20,6 +20,9 @@ body .channel-root__right-column*/ {
|
||||||
transform: none !important;
|
transform: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.channel-root--hold-chat+.persistent-player, .channel-root--watch-chat+.persistent-player, .channel-root__info--with-chat .channel-info-content, .channel-root__player--with-chat {
|
.channel-root--hold-chat+.persistent-player,
|
||||||
|
.channel-root--watch-chat+.persistent-player,
|
||||||
|
.channel-root__info--with-chat .channel-info-content,
|
||||||
|
.channel-root__player--with-chat {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
|
@ -14,7 +14,7 @@ body .whispers--theatre-mode.whispers--right-column-expanded-beside {
|
||||||
right: var(--ffz-chat-width);
|
right: var(--ffz-chat-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
body .persistent-player--theatre:not([style*="width: 100%"]),
|
body .persistent-player--theatre:not([style*="width: 100%"]):not([style*="width: 100vw"]),
|
||||||
body .channel-page__video-player--theatre-mode {
|
body .channel-page__video-player--theatre-mode {
|
||||||
width: calc(100% - var(--ffz-chat-width)) !important;
|
width: calc(100% - var(--ffz-chat-width)) !important;
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,7 @@ export default class Layout extends Module {
|
||||||
description: 'When enabled, this minimizes the chat header and places the chat input box in line with the chat buttons in order to present a more compact chat able to display more lines with limited vertical space.',
|
description: 'When enabled, this minimizes the chat header and places the chat input box in line with the chat buttons in order to present a more compact chat able to display more lines with limited vertical space.',
|
||||||
component: 'setting-check-box'
|
component: 'setting-check-box'
|
||||||
},
|
},
|
||||||
changed: val => this.css_tweaks.toggle('portrait-chat', val)
|
//changed: val => this.css_tweaks.toggle('portrait-chat', val)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.settings.add('layout.use-portrait', {
|
this.settings.add('layout.use-portrait', {
|
||||||
|
@ -135,7 +135,7 @@ export default class Layout extends Module {
|
||||||
process(ctx) {
|
process(ctx) {
|
||||||
return ctx.get('layout.use-portrait') && ctx.get('context.ui.rightColumnExpanded');
|
return ctx.get('layout.use-portrait') && ctx.get('context.ui.rightColumnExpanded');
|
||||||
},
|
},
|
||||||
changed: val => this.css_tweaks.toggle('portrait', val)
|
//changed: val => this.css_tweaks.toggle('portrait', val)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('layout.use-portrait-swapped', {
|
this.settings.add('layout.use-portrait-swapped', {
|
||||||
|
@ -143,7 +143,7 @@ export default class Layout extends Module {
|
||||||
process(ctx) {
|
process(ctx) {
|
||||||
return ctx.get('layout.inject-portrait') && ctx.get('layout.swap-sidebars')
|
return ctx.get('layout.inject-portrait') && ctx.get('layout.swap-sidebars')
|
||||||
},
|
},
|
||||||
changed: val => this.css_tweaks.toggle('portrait-swapped', val)
|
//changed: val => this.css_tweaks.toggle('portrait-swapped', val)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('layout.use-portrait-meta', {
|
this.settings.add('layout.use-portrait-meta', {
|
||||||
|
@ -151,7 +151,7 @@ export default class Layout extends Module {
|
||||||
process(ctx) {
|
process(ctx) {
|
||||||
return ctx.get('layout.inject-portrait') && ctx.get('player.theatre.metadata')
|
return ctx.get('layout.inject-portrait') && ctx.get('player.theatre.metadata')
|
||||||
},
|
},
|
||||||
changed: val => this.css_tweaks.toggle('portrait-metadata', val)
|
//changed: val => this.css_tweaks.toggle('portrait-metadata', val)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('layout.use-portrait-meta-top', {
|
this.settings.add('layout.use-portrait-meta-top', {
|
||||||
|
@ -159,7 +159,7 @@ export default class Layout extends Module {
|
||||||
process(ctx) {
|
process(ctx) {
|
||||||
return ctx.get('layout.use-portrait-meta') && ! ctx.get('layout.portrait-invert')
|
return ctx.get('layout.use-portrait-meta') && ! ctx.get('layout.portrait-invert')
|
||||||
},
|
},
|
||||||
changed: val => this.css_tweaks.toggle('portrait-metadata-top', val)
|
//changed: val => this.css_tweaks.toggle('portrait-metadata-top', val)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('layout.is-theater-mode', {
|
this.settings.add('layout.is-theater-mode', {
|
||||||
|
@ -208,7 +208,7 @@ export default class Layout extends Module {
|
||||||
return height;
|
return height;
|
||||||
},
|
},
|
||||||
|
|
||||||
changed: val => this.css_tweaks.setVariable('portrait-extra-height', `${val}rem`)
|
//changed: val => this.css_tweaks.setVariable('portrait-extra-height', `${val}rem`)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.settings.add('layout.portrait-extra-width', {
|
this.settings.add('layout.portrait-extra-width', {
|
||||||
|
@ -220,7 +220,7 @@ export default class Layout extends Module {
|
||||||
return ctx.get('context.ui.sideNavExpanded') ? 24 : 5
|
return ctx.get('context.ui.sideNavExpanded') ? 24 : 5
|
||||||
},
|
},
|
||||||
|
|
||||||
changed: val => this.css_tweaks.setVariable('portrait-extra-width', `${val}rem`)
|
//changed: val => this.css_tweaks.setVariable('portrait-extra-width', `${val}rem`)
|
||||||
});
|
});
|
||||||
|
|
||||||
this.settings.add('layout.is-minimal', {
|
this.settings.add('layout.is-minimal', {
|
||||||
|
@ -237,13 +237,22 @@ export default class Layout extends Module {
|
||||||
this.on(':update-nav', this.updateNavLinks, this);
|
this.on(':update-nav', this.updateNavLinks, this);
|
||||||
this.on(':resize', this.handleResize, this);
|
this.on(':resize', this.handleResize, this);
|
||||||
|
|
||||||
this.css_tweaks.toggle('portrait-chat', this.settings.get('layout.portrait-min-chat'));
|
this.settings.getChanges('layout.portrait-min-chat', val => this.css_tweaks.toggle('portrait-chat', val));
|
||||||
|
this.settings.getChanges('layout.inject-portrait', val => this.css_tweaks.toggle('portrait', val));
|
||||||
|
this.settings.getChanges('layout.use-portrait-swapped', val => this.css_tweaks.toggle('portrait-swapped', val));
|
||||||
|
this.settings.getChanges('layout.use-portrait-meta', val => this.css_tweaks.toggle('portrait-metadata', val));
|
||||||
|
this.settings.getChanges('layout.use-portrait-meta-top', val => this.css_tweaks.toggle('portrait-metadata-top', val));
|
||||||
|
|
||||||
|
this.settings.getChanges('layout.portrait-extra-width', val => this.css_tweaks.setVariable('portrait-extra-width', `${val}rem`));
|
||||||
|
this.settings.getChanges('layout.portrait-extra-height', val => this.css_tweaks.setVariable('portrait-extra-height', `${val}rem`));
|
||||||
|
|
||||||
|
/*this.css_tweaks.toggle('portrait-chat', this.settings.get('layout.portrait-min-chat'));
|
||||||
this.css_tweaks.toggle('portrait', this.settings.get('layout.inject-portrait'));
|
this.css_tweaks.toggle('portrait', this.settings.get('layout.inject-portrait'));
|
||||||
this.css_tweaks.toggle('portrait-swapped', this.settings.get('layout.use-portrait-swapped'));
|
this.css_tweaks.toggle('portrait-swapped', this.settings.get('layout.use-portrait-swapped'));
|
||||||
this.css_tweaks.toggle('portrait-metadata', this.settings.get('layout.use-portrait-meta'));
|
this.css_tweaks.toggle('portrait-metadata', this.settings.get('layout.use-portrait-meta'));
|
||||||
this.css_tweaks.toggle('portrait-metadata-top', this.settings.get('layout.use-portrait-meta-top'));
|
this.css_tweaks.toggle('portrait-metadata-top', this.settings.get('layout.use-portrait-meta-top'));
|
||||||
this.css_tweaks.setVariable('portrait-extra-width', `${this.settings.get('layout.portrait-extra-width')}rem`);
|
this.css_tweaks.setVariable('portrait-extra-width', `${this.settings.get('layout.portrait-extra-width')}rem`);
|
||||||
this.css_tweaks.setVariable('portrait-extra-height', `${this.settings.get('layout.portrait-extra-height')}rem`);
|
this.css_tweaks.setVariable('portrait-extra-height', `${this.settings.get('layout.portrait-extra-height')}rem`);*/
|
||||||
|
|
||||||
this.on('site.directory:update-cards', () => {
|
this.on('site.directory:update-cards', () => {
|
||||||
this.SideBar.each(el => this._updateSidebar(el));
|
this.SideBar.each(el => this._updateSidebar(el));
|
||||||
|
|
|
@ -19,6 +19,8 @@ export default class CSSTweaks extends Module {
|
||||||
this.chunks = {};
|
this.chunks = {};
|
||||||
this.chunks_loaded = false;
|
this.chunks_loaded = false;
|
||||||
|
|
||||||
|
this._state = {};
|
||||||
|
|
||||||
this.populate = once(this.populate);
|
this.populate = once(this.populate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,18 +45,32 @@ export default class CSSTweaks extends Module {
|
||||||
this.style.set(k, `${this.rules[key]}{display:none !important}`);
|
this.style.set(k, `${this.rules[key]}{display:none !important}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
async toggle(key, val) {
|
toggle(key, val) {
|
||||||
|
if ( this._state[key] == val )
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._state[key] = val;
|
||||||
|
this._apply(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
_apply(key) {
|
||||||
|
const val = this._state[key];
|
||||||
if ( ! val ) {
|
if ( ! val ) {
|
||||||
if ( this._style )
|
if ( this._style )
|
||||||
this._style.delete(key);
|
this._style.delete(key);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( ! this.chunks_loaded )
|
if ( this.style.has(key) )
|
||||||
await this.populate();
|
return;
|
||||||
|
|
||||||
if ( ! has(this.chunks, key) )
|
if ( ! this.chunks_loaded )
|
||||||
throw new Error(`unknown chunk "${key}" for toggle`);
|
return this.populate().then(() => this._apply(key));
|
||||||
|
|
||||||
|
if ( ! has(this.chunks, key) ) {
|
||||||
|
this.log.warn(`Unknown chunk name "${key}" for toggle()`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.style.set(key, this.chunks[key]);
|
this.style.set(key, this.chunks[key]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -276,6 +276,15 @@ export class EventEmitter {
|
||||||
item[2] = ttl - 1;
|
item[2] = ttl - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Automatically wait for a promise, if the return value is a promise
|
||||||
|
// and we're dealing with a waitable event.
|
||||||
|
if ( ret instanceof Promise ) {
|
||||||
|
if ( (args[0] instanceof FFZWaitableEvent) )
|
||||||
|
args[0].waitFor(ret);
|
||||||
|
else if ( this.log )
|
||||||
|
this.log.error(`handler for event "${event}" returned a Promise but the event is not an FFZWaitableEvent`);
|
||||||
|
}
|
||||||
|
|
||||||
if ( (args[0] instanceof FFZEvent && args[0].propagationStopped) || ret === StopPropagation )
|
if ( (args[0] instanceof FFZEvent && args[0].propagationStopped) || ret === StopPropagation )
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -306,88 +315,6 @@ export class EventEmitter {
|
||||||
this.__running.delete(event);
|
this.__running.delete(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
async emitAsync(event, ...args) {
|
|
||||||
let list = this.__listeners[event];
|
|
||||||
if ( ! list )
|
|
||||||
return [];
|
|
||||||
|
|
||||||
if ( this.__running.has(event) )
|
|
||||||
throw new Error(`concurrent access: tried to emit event while event is running`);
|
|
||||||
|
|
||||||
// Track removals separately to make iteration over the event list
|
|
||||||
// much, much simpler.
|
|
||||||
const removed = new Set,
|
|
||||||
promises = [];
|
|
||||||
|
|
||||||
// Set the current list of listeners to null because we don't want
|
|
||||||
// to enter some kind of loop if a new listener is added as the result
|
|
||||||
// of an existing listener.
|
|
||||||
this.__listeners[event] = null;
|
|
||||||
this.__running.add(event);
|
|
||||||
|
|
||||||
for(const item of list) {
|
|
||||||
const [fn, ctx] = item;
|
|
||||||
let ret;
|
|
||||||
try {
|
|
||||||
ret = fn.apply(ctx, args);
|
|
||||||
} catch(err) {
|
|
||||||
if ( this.log )
|
|
||||||
this.log.capture(err, {tags: {event}, extra: {args}});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !(ret instanceof Promise) )
|
|
||||||
ret = Promise.resolve(ret);
|
|
||||||
|
|
||||||
promises.push(ret.then(r => {
|
|
||||||
const new_ttl = item[2];
|
|
||||||
if ( r === Detach )
|
|
||||||
removed.add(item);
|
|
||||||
else if ( new_ttl !== false ) {
|
|
||||||
if ( new_ttl <= 1 )
|
|
||||||
removed.add(item);
|
|
||||||
else
|
|
||||||
item[2] = new_ttl - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( ret !== Detach )
|
|
||||||
return ret;
|
|
||||||
}).catch(err => {
|
|
||||||
if ( this.log )
|
|
||||||
this.log.capture(err, {event, args});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const out = await Promise.all(promises);
|
|
||||||
|
|
||||||
// Remove any dead listeners from the list.
|
|
||||||
if ( removed.size ) {
|
|
||||||
for(const item of removed) {
|
|
||||||
const idx = list.indexOf(item);
|
|
||||||
if ( idx !== -1 )
|
|
||||||
list.splice(idx, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Were more listeners added while we were running? Just combine
|
|
||||||
// the two lists if so.
|
|
||||||
if ( this.__listeners[event] )
|
|
||||||
list = list.concat(this.__listeners[event]);
|
|
||||||
|
|
||||||
// If we have items, store the list back. Otherwise, mark that we
|
|
||||||
// have a dead listener.
|
|
||||||
if ( list.length )
|
|
||||||
this.__listeners[event] = list;
|
|
||||||
else {
|
|
||||||
this.__listeners[event] = null;
|
|
||||||
this.__dead_events++;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.__running.delete(event);
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EventEmitter.Detach = Detach;
|
EventEmitter.Detach = Detach;
|
||||||
|
@ -417,6 +344,39 @@ export class FFZEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class FFZWaitableEvent extends FFZEvent {
|
||||||
|
|
||||||
|
_wait() {
|
||||||
|
if ( this.__waiter )
|
||||||
|
return this.__waiter;
|
||||||
|
|
||||||
|
if ( ! this.__promises )
|
||||||
|
return;
|
||||||
|
|
||||||
|
const promises = this.__promises;
|
||||||
|
this.__promises = null;
|
||||||
|
|
||||||
|
return this.__waiter = Promise.all(promises).finally(() => {
|
||||||
|
this.__waiter = null;
|
||||||
|
return this._wait();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_reset() {
|
||||||
|
super._reset();
|
||||||
|
this.__waiter = null;
|
||||||
|
this.__promises = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
waitFor(promise) {
|
||||||
|
if ( ! this.__promises )
|
||||||
|
this.__promises = [promise];
|
||||||
|
else
|
||||||
|
this.__promises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export class HierarchicalEventEmitter extends EventEmitter {
|
export class HierarchicalEventEmitter extends EventEmitter {
|
||||||
constructor(name, parent) {
|
constructor(name, parent) {
|
||||||
|
@ -507,7 +467,6 @@ export class HierarchicalEventEmitter extends EventEmitter {
|
||||||
|
|
||||||
emit(event, ...args) { return super.emit(this.abs_path(event), ...args) }
|
emit(event, ...args) { return super.emit(this.abs_path(event), ...args) }
|
||||||
emitUnsafe(event, ...args) { return super.emitUnsafe(this.abs_path(event), ...args) }
|
emitUnsafe(event, ...args) { return super.emitUnsafe(this.abs_path(event), ...args) }
|
||||||
emitAsync(event, ...args) { return super.emitAsync(this.abs_path(event), ...args) }
|
|
||||||
|
|
||||||
events(include_children) {
|
events(include_children) {
|
||||||
this.__cleanListeners();
|
this.__cleanListeners();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue