diff --git a/src/main.js b/src/main.js index 05a4bd48..4973678e 100644 --- a/src/main.js +++ b/src/main.js @@ -100,7 +100,7 @@ class FrankerFaceZ extends Module { FrankerFaceZ.Logger = Logger; const VER = FrankerFaceZ.version_info = { - major: 4, minor: 0, revision: 0, extra: '-rc8.6.1', + major: 4, minor: 0, revision: 0, extra: '-rc9', commit: __git_commit__, build: __webpack_hash__, toString: () => diff --git a/src/modules/chat/actions/types.jsx b/src/modules/chat/actions/types.jsx index d7f2296e..163a99de 100644 --- a/src/modules/chat/actions/types.jsx +++ b/src/modules/chat/actions/types.jsx @@ -46,8 +46,10 @@ export const open_url = { const url = process(data.options.url, data, this.i18n.locale); const win = window.open(); - win.opener = null; - win.location = url; + if ( win ) { + win.opener = null; + win.location = url; + } } }; diff --git a/src/modules/chat/emotes.js b/src/modules/chat/emotes.js index 8cdc4b8e..28c3eb7c 100644 --- a/src/modules/chat/emotes.js +++ b/src/modules/chat/emotes.js @@ -225,8 +225,10 @@ export default class Emotes extends Module { if ( url ) { const win = window.open(); - win.opener = null; - win.location = url; + if ( win ) { + win.opener = null; + win.location = url; + } } return true; @@ -723,14 +725,50 @@ export default class Emotes extends Module { } - getTwitchSetChannel(set_id, callback) { - const tes = this.__twitch_set_to_channel; + async awaitTwitchSetChannel(set_id, perform_lookup = true) { + const tes = this.__twitch_set_to_channel, + inv = this.twitch_inventory_sets; + if ( isNaN(set_id) || ! isFinite(set_id) ) return null; if ( tes.has(set_id) ) return tes.get(set_id); + if ( inv.has(set_id) ) + return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'} + + if ( ! perform_lookup ) + return null; + + tes.set(set_id, null); + try { + const data = await timeout(this.socket.call('get_emote_set', set_id), 1000); + tes.set(set_id, data); + return data; + + } catch(err) { + tes.delete(set_id); + } + } + + + getTwitchSetChannel(set_id, callback, perform_lookup = true) { + const tes = this.__twitch_set_to_channel, + inv = this.twitch_inventory_sets; + + if ( isNaN(set_id) || ! isFinite(set_id) ) + return null; + + if ( tes.has(set_id) ) + return tes.get(set_id); + + if ( inv.has(set_id) ) + return {s_id: set_id, c_id: null, c_name: 'twitch-inventory'} + + if ( ! perform_lookup ) + return null; + tes.set(set_id, null); timeout(this.socket.call('get_emote_set', set_id), 1000).then(data => { tes.set(set_id, data); diff --git a/src/raven.js b/src/raven.js index 89a55cf6..5725478f 100644 --- a/src/raven.js +++ b/src/raven.js @@ -130,7 +130,9 @@ export default class RavenLogger extends Module { captureUnhandledRejections: false, ignoreErrors: [ 'InvalidAccessError', - 'out of memory' + 'out of memory', + 'Access is denied.', + 'Zugriff verweigert' ], whitelistUrls: [ /cdn\.frankerfacez\.com/ diff --git a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx index cd2698a5..681708d9 100644 --- a/src/sites/twitch-twilight/modules/chat/emote_menu.jsx +++ b/src/sites/twitch-twilight/modules/chat/emote_menu.jsx @@ -5,13 +5,23 @@ // ============================================================================ import {has, get, once, maybe_call, set_equals} from 'utilities/object'; -import {IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; +import {WEBKIT_CSS as WEBKIT, IS_OSX, KNOWN_CODES, TWITCH_EMOTE_BASE, REPLACEMENT_BASE, REPLACEMENTS} from 'utilities/constants'; +import {ClickOutside} from 'utilities/dom'; import Twilight from 'site'; import Module from 'utilities/module'; import SUB_STATUS from './sub_status.gql'; +const TONE_EMOJI = [ + 'the_horns', + 'raised_back_of_hand', + 'ok_hand', + '+1', + 'clap', + 'fist' +]; + function maybe_date(val) { if ( ! val ) return val; @@ -162,7 +172,8 @@ export default class EmoteMenu extends Module { data: [ {value: 'fav', title: 'Favorites'}, {value: 'channel', title: 'Channel'}, - {value: 'all', title: 'My Emotes'} + {value: 'all', title: 'My Emotes'}, + {value: 'emoji', title: 'Emoji'} ] } }); @@ -230,11 +241,19 @@ export default class EmoteMenu extends Module { this.chat.context.on('changed:chat.emote-menu.show-search', fup); this.chat.context.on('changed:chat.emote-menu.reduced-padding', fup); + this.chat.context.on('changed:chat.emoji.style', this.updateEmojiVariables, this); + this.chat.context.on('changed:chat.emote-menu.icon', val => this.css_tweaks.toggle('emote-menu', val)); this.css_tweaks.toggle('emote-menu', this.chat.context.get('chat.emote-menu.icon')); + this.updateEmojiVariables(); + + this.css_tweaks.setVariable('emoji-menu--sheet', `//cdn.frankerfacez.com/static/emoji/sheet_twitter_32.png`); + this.css_tweaks.setVariable('emoji-menu--count', 52); + this.css_tweaks.setVariable('emoji-menu--size', 20); + const t = this, React = await this.web_munch.findModule('react'), createElement = React && React.createElement; @@ -269,6 +288,28 @@ export default class EmoteMenu extends Module { }) } + updateEmojiVariables() { + const style = this.chat.context.get('chat.emoji.style') || 'twitter', + base = `//cdn.frankerfacez.com/static/emoji/sheet_${style}_`; + + const emoji_size = this.emoji_size = 20, + sheet_count = this.emoji_sheet_count = 52, + sheet_size = this.emoji_sheet_size = sheet_count * (emoji_size + 2), + sheet_pct = this.emoji_sheet_pct = 100 * sheet_size / emoji_size; + + this.emoji_sheet_remain = sheet_size - emoji_size; + + this.css_tweaks.set('emoji-menu', `.ffz--emoji-tone-picker__emoji,.emote-picker__emoji .emote-picker__emote-figure { + background-size: ${sheet_pct}% ${sheet_pct}%; + background-image: url("${base}20.png"); + background-image: ${WEBKIT}image-set( + url("${base}20.png") 1x, + url("${base}32.png") 1.6x, + url("${base}64.png") 3.2x + ); +}`); + } + maybeUpdate() { if ( this.chat.context.get('chat.emote-menu.enabled') ) this.EmoteMenu.forceUpdate(); @@ -289,49 +330,112 @@ export default class EmoteMenu extends Module { React = this.web_munch.getModule('react'), createElement = React && React.createElement; - this.MenuEmote = function({source, data, lock, locked, all_locked, onClickEmote}) { - const handle_click = e => { - if ( ! t.emotes.handleClick(e) ) - onClickEmote(data.name); - }; + this.EmojiTonePicker = class FFZEmojiTonePicker extends React.Component { + constructor(props) { + super(props); - const sellout = lock ? - all_locked ? - t.i18n.t('emote-menu.emote-sub', 'Subscribe for %{price} to unlock this emote.', lock) : - t.i18n.t('emote-menu.emote-up', 'Upgrade your sub to %{price} to unlock this emote.', lock) - : null; + this.onClick = () => this.setState({open: ! this.state.open}); + this.onMouseEnter = () => this.state.open || this.setState({emoji: this.pickRandomEmoji()}); + this.onClickOutside = () => this.state.open && this.setState({open: false}); - return () + this.clickTone = event => { + this.props.pickTone(event.currentTarget.dataset.tone); + this.setState({open: false}); + } + + this.element = null; + this.saveRef = element => this.element = element; + + this.state = { + open: false, + emoji: this.pickRandomEmoji(), + tone: null + } + } + + componentDidMount() { + if ( this.element ) + this._clicker = new ClickOutside(this.element, this.onClickOutside); + } + + componentWillUnmount() { + if ( this._clicker ) { + this._clicker.destroy(); + this._clicker = null; + } + } + + pickRandomEmoji() { // eslint-disable-line class-methods-use-this + const possibilities = this.props.choices, + pick = Math.floor(Math.random() * possibilities.length); + + return possibilities[pick]; + } + + renderTone(data, tone) { + return () + } + + renderToneMenu() { + if ( ! this.state.open ) + return null; + + const emoji = this.state.emoji, + tones = Object.entries(emoji.variants).map(([tone, emoji]) => this.renderTone(emoji, tone)); + + return (