diff --git a/assets/chat/css/chat/_index.scss b/assets/chat/css/chat/_index.scss index 9c202515..82467ce2 100644 --- a/assets/chat/css/chat/_index.scss +++ b/assets/chat/css/chat/_index.scss @@ -9,6 +9,7 @@ @use 'scrollbar-theme'; @use 'toolbar'; @use 'window-select'; +@use 'event-bar'; #chat { min-width: a.$chat-min-width; diff --git a/assets/chat/css/chat/event-bar/_event-bar-event.scss b/assets/chat/css/chat/event-bar/_event-bar-event.scss new file mode 100644 index 00000000..a672258d --- /dev/null +++ b/assets/chat/css/chat/event-bar/_event-bar-event.scss @@ -0,0 +1,143 @@ +@use '../../abstracts/' as a; +@use '../../messages/event/_donation' as donation; + +.event-bar-event { + position: relative; + cursor: pointer; + transition: transform 100ms; + animation: event-bar-appear 500ms linear; + + font-size: 1.1em; + border-radius: 10px; + border-style: solid; + border-color: unset; + border-width: 2px; + margin: a.$gutter-sm; + + .event-contents { + display: flex; + padding: 0 a.$gutter-xs 0 0.4em; + background-color: a.$color-chat-emphasize; + justify-content: space-between; + align-items: center; + border-radius: 10px; + } + + .event-info { + margin-right: a.$gutter-xs; + max-width: 12ch; + overflow: hidden; + } + + .event-icon { + width: 2em; + height: 2em; + border: 0.25em solid transparent; + opacity: 0.75; + + &.subscription { + @include a.icon-background('../img/sub-regular.svg'); + } + + &.giftsub { + filter: invert(100%); + + @include a.icon-background('../img/sub-gift.png'); + } + + &.massgift { + filter: invert(100%); + + @include a.icon-background('../img/sub-mass-gift.png'); + } + } + + .user { + font-weight: 500; + color: a.$color-label-user; + display: inline-block; + animation: scrolling-event-username 12s linear 3s infinite; + word-wrap: normal; + + &::before { + content: unset; + } + + &:hover { + text-decoration: none; + } + } + + &.amount-0 { + border-color: #1c5cdb; + } + + @each $amount, $color in donation.$amount-color-map { + &.amount-#{$amount} { + border-color: $color; + + .event-icon { + filter: invert(100%); + + @include a.icon-background('../img/donation-amount-#{$amount}.png'); + } + } + } + + &.rainbow-border { + background-clip: padding-box; + border: solid 2px transparent; + + &:before { + content: ''; + position: absolute; + inset: 0; + z-index: -1; + margin: -2px; + border-radius: inherit; + background: var(--rainbow-gradient); + } + } + + &:hover { + transform: scale(1.05); + } + + &.removed { + animation: event-bar-disappear 500ms linear; + } +} + +@keyframes event-bar-appear { + 0% { + transform: scale(0.1); + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes event-bar-disappear { + 0% { + opacity: 1; + } + + 100% { + transform: scale(0.1); + opacity: 0; + } +} + +@keyframes scrolling-event-username { + 25%, + 50% { + transform: translateX(calc(-1 * max(0px, 100% - 12ch))); + } + + 75%, + 100% { + transform: translateX(0%); + } +} diff --git a/assets/chat/css/chat/event-bar/_index.scss b/assets/chat/css/chat/event-bar/_index.scss new file mode 100644 index 00000000..4a6951a8 --- /dev/null +++ b/assets/chat/css/chat/event-bar/_index.scss @@ -0,0 +1,45 @@ +@use '../../abstracts/' as a; + +@use 'event-bar-event'; + +#chat-event-bar { + &:empty { + display: none; + } + + display: inline-flex; + overflow-x: scroll; + background: #111113; + z-index: 6; + + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +} + +#highlighted-message-wrapper { + position: absolute; + width: 100%; + z-index: 210; +} + +#chat-event-selected { + .event-bar-selected-message { + margin: a.$gutter-sm; + + .focus:not(.watching-focus) & { + opacity: 1; + } + } +} + +.onstreamchat { + #chat-event-bar { + display: none; + } + + #highlighted-message-wrapper { + display: none; + } +} diff --git a/assets/chat/css/messages/pinned/_frame.scss b/assets/chat/css/messages/pinned/_frame.scss index 68bab783..b7c5644d 100644 --- a/assets/chat/css/messages/pinned/_frame.scss +++ b/assets/chat/css/messages/pinned/_frame.scss @@ -1,6 +1,6 @@ @use '../../abstracts/' as a; -#chat-pinned-frame { +#chat-pinned-message { display: none; padding: 4px; position: absolute; @@ -38,7 +38,7 @@ } .onstreamchat { - #chat-pinned-frame { + #chat-pinned-message { display: none !important; } } diff --git a/assets/chat/js/chat.js b/assets/chat/js/chat.js index c0f2e020..fe71483b 100644 --- a/assets/chat/js/chat.js +++ b/assets/chat/js/chat.js @@ -32,6 +32,7 @@ import { ChatSettingsMenu, ChatUserInfoMenu, } from './menus'; +import ChatEventBar from './event-bar/EventBar'; import ChatAutoComplete from './autocomplete'; import ChatInputHistory from './history'; import ChatUserFocus from './focus'; @@ -56,6 +57,7 @@ import makeSafeForRegex, { import { HashLinkConverter, MISSING_ARG_ERROR } from './hashlinkconverter'; import ChatCommands from './commands'; import MessageTemplateHTML from '../../views/templates.html'; +import EventBarEvent from './event-bar/EventBarEvent'; class Chat { constructor(config) { @@ -150,6 +152,7 @@ class Chat { this.source.on('ADDPHRASE', (data) => this.onADDPHRASE(data)); this.source.on('REMOVEPHRASE', (data) => this.onREMOVEPHRASE(data)); this.source.on('DEATH', (data) => this.onDEATH(data)); + this.source.on('PAIDEVENTS', (data) => this.onPAIDEVENTS(data)); this.control.on('SEND', (data) => this.cmdSEND(data)); this.control.on('HINT', (data) => this.cmdHINT(data)); @@ -317,6 +320,11 @@ class Chat { this.mainwindow = new ChatWindow('main').into(this); this.mutedtimer = new MutedTimer(this); this.chatpoll = new ChatPoll(this); + + this.eventBar = new ChatEventBar(); + this.eventBar.on('eventSelected', () => this.onEVENTSELECTED()); + this.eventBar.on('eventUnselected', () => this.onEVENTUNSELECTED()); + this.pinnedMessage = null; this.windowToFront('main'); @@ -1329,18 +1337,52 @@ class Chat { onSUBSCRIPTION(data) { MessageBuilder.subscription(data).into(this); + const eventBarEvent = new EventBarEvent( + this, + MessageTypes.SUBSCRIPTION, + data, + ); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } } onGIFTSUB(data) { MessageBuilder.gift(data).into(this); + const eventBarEvent = new EventBarEvent(this, MessageTypes.GIFTSUB, data); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } } onMASSGIFT(data) { MessageBuilder.massgift(data).into(this); + const eventBarEvent = new EventBarEvent(this, MessageTypes.MASSGIFT, data); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } } onDONATION(data) { MessageBuilder.donation(data).into(this); + const eventBarEvent = new EventBarEvent(this, MessageTypes.DONATION, data); + this.eventBar.add(eventBarEvent); + if (this.eventBar.length === 1) { + this.mainwindow.update(); + } + } + + onPAIDEVENTS(lines) { + lines.forEach((line) => { + const { eventname, data } = this.source.parse({ data: line }); + const eventBarEvent = new EventBarEvent(this, eventname, data); + this.eventBar.add(eventBarEvent); + }); + this.mainwindow.update(); + this.eventBar.sort(); } onADDPHRASE(data) { @@ -1522,6 +1564,16 @@ class Chat { MessageBuilder.death(data.data, user, data.timestamp).into(this); } + onEVENTSELECTED() { + this.userfocus.toggleFocus('', false, true); + // Hide full pinned message interface to make everything look nice + if (this.pinnedMessage) this.pinnedMessage.hidden = true; + } + + onEVENTUNSELECTED() { + if (this.pinnedMessage) this.pinnedMessage.hidden = false; + } + cmdSHOWPOLL() { if (this.chatpoll.poll) { this.chatpoll.show(); diff --git a/assets/chat/js/event-bar/EventBar.js b/assets/chat/js/event-bar/EventBar.js new file mode 100644 index 00000000..94221350 --- /dev/null +++ b/assets/chat/js/event-bar/EventBar.js @@ -0,0 +1,122 @@ +import EventEmitter from '../emitter'; + +/** + * @typedef {import('../messages/ChatEventMessage').default & {expirationTimestamp: number}} ExpiringEvent + */ + +export default class ChatEventBar extends EventEmitter { + constructor() { + super(); + /** @type HTMLDivElement */ + this.eventBarUI = document.getElementById('chat-event-bar'); + /** @type HTMLDivElement */ + this.eventSelectUI = document.getElementById('chat-event-selected'); + + this.eventBarUI.addEventListener('wheel', (event) => { + if (event.deltaX === 0) { + event.preventDefault(); + this.eventBarUI.scrollBy({ + left: event.deltaY > 0 ? 30 : -30, + }); + } + }); + } + + /** + * Adds the event to the event bar. + * @param {EventBarEvent} event + */ + add(event) { + if (!this.shouldEventBeDisplayed(event.data)) { + return; + } + + event.element.addEventListener('click', () => { + this.select(event.selectedElement); + }); + + this.eventBarUI.prepend(event.element); + + // // Update chat window to fix the scroll position + // this.chat.mainwindow.update(); + // + event.startExpiry(); + } + + /** + * Unselects the currently highlighted event. + */ + unselect() { + if (this.eventSelectUI.hasChildNodes()) { + this.eventSelectUI.replaceChildren(); + this.emit('eventUnselected'); + } + } + + /** + * Selects the specified event. + * @param {HTMLDivElement} event + */ + select(event) { + /** @type HTMLDivElement */ + event.classList.add('event-bar-selected-message'); + + this.eventSelectUI.replaceChildren(); + this.eventSelectUI.append(event); + + this.emit('eventSelected'); + } + + /** + * Checks if the specified event is already in the event bar. + * @param {string} uuid + * @returns {boolean} + */ + contains(uuid) { + return !!this.eventBarUI.querySelector( + `.event-bar-event[data-uuid="${uuid}"]`, + ); + } + + /** + * Sorts the events in the event bar in descending order by time received. + */ + sort() { + [...this.eventBarUI.children] + .sort((a, b) => + Number(a.dataset.unixtimestamp) < Number(b.dataset.unixtimestamp) + ? 1 + : -1, + ) + .forEach((node) => this.eventBarUI.appendChild(node)); + } + + /** + * Checks if the specified event should appear in the event bar. + * @param {ExpiringEvent} event + * @returns {boolean} + * @private + */ + shouldEventBeDisplayed(event) { + if (this.contains(event.uuid)) { + return false; + } + + const currentTimestamp = Date.now(); + if (event.expirationTimestamp < currentTimestamp) { + return false; + } + + // subscriptions from a mass gift event don't appear in the event bar + // to avoid overcrowding it (one event showing how many gifts a person bought is enough) + if (event.fromMassGift) { + return false; + } + + return true; + } + + get length() { + return this.eventBarUI.querySelectorAll(`.event-bar-event`).length; + } +} diff --git a/assets/chat/js/event-bar/EventBarEvent.js b/assets/chat/js/event-bar/EventBarEvent.js new file mode 100644 index 00000000..a8e84655 --- /dev/null +++ b/assets/chat/js/event-bar/EventBarEvent.js @@ -0,0 +1,143 @@ +import { usernameColorFlair } from '../messages/ChatUserMessage'; +import { selectDonationTier } from '../messages/ChatDonationMessage'; +import { getTierStyles } from '../messages/subscriptions/ChatSubscriptionMessage'; +import { MessageBuilder, MessageTypes } from '../messages'; +import ChatUser from '../user'; + +export default class EventBarEvent { + /** + * @param {*} chat + * @param {string} type + * @param {import('./EventBar').ExpiringEvent} data + */ + constructor(chat, type, data) { + this.type = type; + this.data = data; + + this.element = this.buildElement(chat); + } + + /** + * @param {*} chat + * @private + */ + buildElement(chat) { + /** @type HTMLDivElement */ + const eventTemplate = document + .querySelector('#event-bar-event-template') + ?.content.cloneNode(true).firstElementChild; + + eventTemplate.classList.add(this.type.toLowerCase()); + eventTemplate.dataset.uuid = this.data.uuid; + eventTemplate.dataset.unixtimestamp = this.data.timestamp; + + const user = new ChatUser(this.data.user); + const userTemplate = eventTemplate.querySelector('.user'); + const colorFlair = usernameColorFlair(chat.flairs, user); + if (colorFlair) { + userTemplate.classList.add(colorFlair.name); + } + userTemplate.textContent = user.displayName; + + const iconTemplate = eventTemplate.querySelector('.event-icon'); + iconTemplate.classList.add(this.type.toLowerCase()); + + switch (this.type) { + case MessageTypes.SUBSCRIPTION: + case MessageTypes.GIFTSUB: + case MessageTypes.MASSGIFT: { + const { rainbowColor, tierColor } = getTierStyles( + this.data.tier, + chat.flairs, + ); + + if (tierColor) eventTemplate.style.borderColor = tierColor; + if (rainbowColor) eventTemplate.classList.add('rainbow-border'); + break; + } + case MessageTypes.DONATION: { + const donationTier = selectDonationTier(this.data.amount); + eventTemplate.classList.add(donationTier[0]); + break; + } + default: + break; + } + + this.selectedElement = this.buildSelected().html(chat); + + return eventTemplate; + } + + startExpiry() { + this.expiryPercentage = this.calculateExpiryPercentage(); + + this.intervalID = setInterval(() => { + const percentageLeft = this.calculateExpiryPercentage(); + + if (percentageLeft <= 0) { + this.remove(); + return; + } + + this.expiryPercentage = percentageLeft; + }, 1000); + } + + /** + * Calculates percentage left until the event expires. + * @returns {number} + */ + calculateExpiryPercentage() { + const currentTimestamp = Date.now(); + const eventTimeLeft = this.data.expirationTimestamp - currentTimestamp; + const eventFullDuration = + this.data.expirationTimestamp - this.data.timestamp; + + return (eventTimeLeft * 100) / eventFullDuration; + } + + get expiryPercentage() { + return Number(this.element.dataset.percentageLeft); + } + + /** + * Sets the progress gradient of the event. + * @param {number} percentageLeft + */ + set expiryPercentage(percentageLeft) { + this.element.dataset.percentageLeft = percentageLeft; + this.element.querySelector('.event-contents').style.background = + `linear-gradient(90deg, #282828, #282828 ${percentageLeft}%, #151515 ${percentageLeft}%, #151515)`; + } + + buildSelected() { + switch (this.type) { + case MessageTypes.SUBSCRIPTION: { + return MessageBuilder.subscription(this.data); + } + case MessageTypes.GIFTSUB: { + return MessageBuilder.gift(this.data); + } + case MessageTypes.MASSGIFT: { + return MessageBuilder.massgift(this.data); + } + case MessageTypes.DONATION: { + return MessageBuilder.donation(this.data); + } + default: + return undefined; + } + } + + /** + * @private + */ + remove() { + this.element.addEventListener('animationend', () => { + this.element.remove(); + if (this.intervalID) clearInterval(this.intervalID); + }); + this.element.classList.add('removed'); + } +} diff --git a/assets/chat/js/focus.js b/assets/chat/js/focus.js index f00997c9..3e4d6bbd 100644 --- a/assets/chat/js/focus.js +++ b/assets/chat/js/focus.js @@ -9,7 +9,10 @@ class ChatUserFocus { this.chat = chat; this.css = css; this.focused = []; - this.chat.output.on('click', (e) => this.toggleElement(e.target)); + this.chat.output.on('click', (e) => { + this.toggleElement(e.target); + this.chat.eventBar.unselect(); + }); } toggleElement(target) { diff --git a/assets/chat/js/messages/ChatDonationMessage.js b/assets/chat/js/messages/ChatDonationMessage.js index f3de6e76..45148b0e 100644 --- a/assets/chat/js/messages/ChatDonationMessage.js +++ b/assets/chat/js/messages/ChatDonationMessage.js @@ -4,22 +4,23 @@ import MessageTypes from './MessageTypes'; const DONATION_TIERS = [0, 5, 10, 25, 50, 100]; +/** + * Toggles the correct classes for a specific donation amount. + * @param {number} amount + * @returns {array} + */ +export function selectDonationTier(amount) { + const tier = DONATION_TIERS.findIndex((value) => amount < value * 100); + return [`amount-${tier !== -1 ? DONATION_TIERS[tier - 1] : '100'}`]; +} + export default class ChatDonationMessage extends ChatEventMessage { - constructor(message, user, amount, timestamp) { - super(message, timestamp); + constructor(message, user, amount, timestamp, expirationTimestamp, uuid) { + super(message, timestamp, uuid); this.user = user; this.type = MessageTypes.DONATION; this.amount = amount; - } - - /** - * Toggles the correct classes for a specific donation amount. - * @param {number} amount - * @returns {array} - */ - selectDonationTier(amount) { - const tier = DONATION_TIERS.findIndex((value) => amount < value * 100); - return [`amount-${tier !== -1 ? DONATION_TIERS[tier - 1] : '100'}`]; + this.expirationTimestamp = expirationTimestamp; } html(chat = null) { @@ -44,7 +45,7 @@ export default class ChatDonationMessage extends ChatEventMessage { })}`, ); - const donationTier = this.selectDonationTier(this.amount); + const donationTier = selectDonationTier(this.amount); eventTemplate.classList.add(donationTier[0]); eventTemplate .querySelector('.event-icon') diff --git a/assets/chat/js/messages/ChatEventMessage.js b/assets/chat/js/messages/ChatEventMessage.js index 06834cad..c41f9f31 100644 --- a/assets/chat/js/messages/ChatEventMessage.js +++ b/assets/chat/js/messages/ChatEventMessage.js @@ -1,13 +1,14 @@ import ChatMessage from './ChatMessage'; export default class ChatEventMessage extends ChatMessage { - constructor(message, timestamp) { + constructor(message, timestamp, uuid) { super(message, timestamp); this.tag = null; this.title = ''; this.slashme = false; this.isown = false; this.mentioned = []; + this.uuid = uuid; } html(chat = null) { diff --git a/assets/chat/js/messages/MessageBuilder.js b/assets/chat/js/messages/MessageBuilder.js index 53739c20..5349d760 100644 --- a/assets/chat/js/messages/MessageBuilder.js +++ b/assets/chat/js/messages/MessageBuilder.js @@ -68,8 +68,10 @@ export default class MessageBuilder { new ChatUser(data.user), data.tier, data.tierLabel, + data.amount, data.streak, data.timestamp, + data.expirationTimestamp, data.uuid, ); } @@ -80,8 +82,11 @@ export default class MessageBuilder { new ChatUser(data.user), data.tier, data.tierLabel, + data.amount, new ChatUser(data.recipient), + data.fromMassGift, data.timestamp, + data.expirationTimestamp, data.uuid, ); } @@ -92,8 +97,10 @@ export default class MessageBuilder { new ChatUser(data.user), data.tier, data.tierLabel, + data.amount, data.quantity, data.timestamp, + data.expirationTimestamp, data.uuid, ); } @@ -104,6 +111,7 @@ export default class MessageBuilder { new ChatUser(data.user), data.amount, data.timestamp, + data.expirationTimestamp, data.uuid, ); } diff --git a/assets/chat/js/messages/PinnedMessage.js b/assets/chat/js/messages/PinnedMessage.js index cd83c2a1..707dcd31 100644 --- a/assets/chat/js/messages/PinnedMessage.js +++ b/assets/chat/js/messages/PinnedMessage.js @@ -45,15 +45,23 @@ export default class PinnedMessage extends ChatUserMessage { /** * Shows/hides the current message. * @param {boolean} state - * @returns {null} null */ - set visible(state) { + set displayed(state) { this.ui.classList.toggle('hidden', !state); document .getElementById('chat-pinned-show-btn') ?.classList.toggle('active', !state); } + /** + * Shows/hides the full pinned message frame. + * @param {boolean} state + */ + set hidden(state) { + const frame = document.getElementById('chat-pinned-message'); + frame.classList.toggle('active', !state); + } + /** * Unpins the current message. * @returns {null} null @@ -61,8 +69,8 @@ export default class PinnedMessage extends ChatUserMessage { unpin() { dismissPin(this.uuid); - const frame = document.getElementById('chat-pinned-frame'); - frame.classList.toggle('active', false); + this.hidden = true; + const frame = document.getElementById('chat-pinned-message'); frame.replaceChildren(); return null; @@ -77,7 +85,7 @@ export default class PinnedMessage extends ChatUserMessage { pin(chat = null, visibility = true) { this.ui.id = 'msg-pinned'; this.ui.classList.toggle('msg-pinned', true); - this.visible = visibility; + this.displayed = visibility; this.ui.querySelector('span.features').classList.toggle('hidden', true); chat.mainwindow.update(); @@ -108,7 +116,7 @@ export default class PinnedMessage extends ChatUserMessage { showPin.title = 'Show Pinned Message'; showPin.addEventListener('click', () => { - this.visible = true; + this.displayed = true; }); const closePin = document.createElement('a'); @@ -122,12 +130,12 @@ export default class PinnedMessage extends ChatUserMessage { closePin.addEventListener('click', () => { dismissPin(this.uuid); - this.visible = false; + this.displayed = false; }); this.ui.prepend(closePin); - const pinnedFrame = document.getElementById('chat-pinned-frame'); + const pinnedFrame = document.getElementById('chat-pinned-message'); pinnedFrame.classList.toggle('active', true); pinnedFrame.prepend(this.ui); pinnedFrame.prepend(showPin); diff --git a/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js index b0e39ea6..96a0166c 100644 --- a/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatGiftedSubscriptionMessage.js @@ -3,10 +3,22 @@ import ChatSubscriptionMessage from './ChatSubscriptionMessage'; import MessageTypes from '../MessageTypes'; export default class ChatGiftedSubscriptionMessage extends ChatSubscriptionMessage { - constructor(message, user, tier, tierLabel, giftee, timestamp) { - super(message, user, tier, tierLabel, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + giftee, + fromMassGift, + timestamp, + expiry, + uuid, + ) { + super(message, user, tier, tierLabel, amount, timestamp, expiry, uuid); this.type = MessageTypes.GIFTSUB; this.giftee = giftee; + this.fromMassGift = fromMassGift; } html(chat = null) { diff --git a/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js index a2e9ed6b..9bb82712 100644 --- a/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatMassSubscriptionMessage.js @@ -2,8 +2,18 @@ import ChatSubscriptionMessage from './ChatSubscriptionMessage'; import MessageTypes from '../MessageTypes'; export default class ChatMassSubscriptionMessage extends ChatSubscriptionMessage { - constructor(message, user, tier, tierLabel, quantity, timestamp) { - super(message, user, tier, tierLabel, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + quantity, + timestamp, + expiry, + uuid, + ) { + super(message, user, tier, tierLabel, amount, timestamp, expiry, uuid); this.type = MessageTypes.MASSGIFT; this.quantity = quantity; } diff --git a/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js index 797a4f98..ba29a5db 100644 --- a/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatRegularSubscriptionMessage.js @@ -2,8 +2,18 @@ import ChatSubscriptionMessage from './ChatSubscriptionMessage'; import MessageTypes from '../MessageTypes'; export default class ChatRegularSubscriptionMessage extends ChatSubscriptionMessage { - constructor(message, user, tier, tierLabel, streak, timestamp) { - super(message, user, tier, tierLabel, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + streak, + timestamp, + expiry, + uuid, + ) { + super(message, user, tier, tierLabel, amount, timestamp, expiry, uuid); this.type = MessageTypes.SUBSCRIPTION; this.streak = streak; } diff --git a/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js b/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js index 7a076d3b..71bcf089 100644 --- a/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js +++ b/assets/chat/js/messages/subscriptions/ChatSubscriptionMessage.js @@ -2,32 +2,46 @@ import { usernameColorFlair } from '../ChatUserMessage'; import ChatEventMessage from '../ChatEventMessage'; import features from '../../features'; +export function getTierStyles(tier, flairs) { + const tierFlair = features[`SUB_TIER_${tier}`]; + const tierInfo = flairs.find((el) => el.name === tierFlair); + const tierColor = tierInfo?.color; + + const tierClass = tierInfo?.rainbowColor ? `user ${tierFlair}` : ''; + + return { + rainbowColor: tierInfo?.rainbowColor, + tierClass, + tierColor: tierInfo?.rainbowColor ? '' : tierColor, + }; +} + export default class ChatSubscriptionMessage extends ChatEventMessage { - constructor(message, user, tier, tierLabel, timestamp) { - super(message, timestamp); + constructor( + message, + user, + tier, + tierLabel, + amount, + timestamp, + expirationTimestamp, + uuid, + ) { + super(message, timestamp, uuid); this.user = user; this.tier = tier; this.tierLabel = tierLabel; - } - - getTierStyles(chat = null) { - const tierFlair = features[`SUB_TIER_${this.tier}`]; - const tierInfo = chat.flairs.find((el) => el.name === tierFlair); - const tierColor = tierInfo?.color; - - const tierClass = tierInfo?.rainbowColor ? `user ${tierFlair}` : ''; - - return { - rainbowColor: tierInfo?.rainbowColor, - tierClass, - tierColor: tierInfo?.rainbowColor ? '' : tierColor, - }; + this.amount = amount; + this.expirationTimestamp = expirationTimestamp; } html(chat = null) { const eventTemplate = super.html(chat); - const { rainbowColor, tierClass, tierColor } = this.getTierStyles(chat); + const { rainbowColor, tierClass, tierColor } = getTierStyles( + this.tier, + chat.flairs, + ); if (tierColor) eventTemplate.style.borderColor = tierColor; if (rainbowColor) eventTemplate.classList.add('rainbow-border'); diff --git a/assets/chat/js/source.js b/assets/chat/js/source.js index 8f815e5e..e49cde90 100644 --- a/assets/chat/js/source.js +++ b/assets/chat/js/source.js @@ -97,6 +97,12 @@ class ChatSource extends EventEmitter { } parseAndDispatch(event) { + const { eventname, data } = this.parse(event); + this.emit('DISPATCH', { data, event: eventname }); // Event is used to hook into all dispatched events + this.emit(eventname, data); + } + + parse(event) { const eventname = event.data.split(' ', 1)[0].toUpperCase(); const payload = event.data.substring(eventname.length + 1); let data = null; @@ -105,8 +111,11 @@ class ChatSource extends EventEmitter { } catch (ignored) { data = payload; } - this.emit('DISPATCH', { data, event: eventname }); // Event is used to hook into all dispatched events - this.emit(eventname, data); + + return { + eventname, + data, + }; } send(eventname, data) { diff --git a/assets/views/embed.html b/assets/views/embed.html index 2d604df9..2fe68d05 100644 --- a/assets/views/embed.html +++ b/assets/views/embed.html @@ -1,6 +1,8 @@