From 596fd3312682f2569c178305ebd2361fb40665ab Mon Sep 17 00:00:00 2001 From: terncode <52113450+terncode@users.noreply.github.com> Date: Sat, 31 Aug 2019 16:52:45 +0200 Subject: [PATCH] Add chat log colors, timestamp, filter & emoji box Chat log names are now based off the ponies coat and mane. Added a timestamp infront of each message. There now is a filter on the top right of the chat box, with it you can filter the past log. Use no prefix for normal non-case sensitive search, use # for case sensitive search and finally use / for RegExp search. When party or whisper messages are received, the respective tab is highlighted. Added an emoji picker, it is displayed when the chat bar is activated. Co-authored-by: Stubenhocker1399 --- .../components/shared/chat-box/chat-box.pug | 8 + .../components/shared/chat-box/chat-box.scss | 191 +++++++++----- src/ts/components/shared/chat-box/chat-box.ts | 24 +- .../components/shared/chat-log/chat-log.pug | 1 + .../components/shared/chat-log/chat-log.scss | 216 ++++++++-------- src/ts/components/shared/chat-log/chat-log.ts | 240 +++++++++++++++++- 6 files changed, 509 insertions(+), 171 deletions(-) diff --git a/src/ts/components/shared/chat-box/chat-box.pug b/src/ts/components/shared/chat-box/chat-box.pug index d14a4f4..d8cca68 100644 --- a/src/ts/components/shared/chat-box/chat-box.pug +++ b/src/ts/components/shared/chat-box/chat-box.pug @@ -5,6 +5,14 @@ aria-label="Chat message") .chat-box-type(#typeBox (click)="toggleChatType()") | #[span(#typePrefix)][#[span.chat-box-type-name(#typeName)]]: + .emotes.emoji-box([style.display]="emojiBoxState") + ul + li(*ngFor="let e of emotes" (click)="addEmoji(e.symbol)" + ) + span.emote-sample.mr-2 + emote-box([emote]="e.names[0]") + + emote-box.game-button.chat-emoji-button([emote]="btnEmoji" (click)="openBox()" (mouseenter)="onMouseEnter($event)") button.game-button.chat-send-button((click)="send($event)" title="Send message" aria-label="Send message") fa-icon([icon]="sendIcon") diff --git a/src/ts/components/shared/chat-box/chat-box.scss b/src/ts/components/shared/chat-box/chat-box.scss index 611fb1e..3cb5212 100644 --- a/src/ts/components/shared/chat-box/chat-box.scss +++ b/src/ts/components/shared/chat-box/chat-box.scss @@ -1,88 +1,155 @@ @import '../../../../styles/partials/variables'; .chat-box { - max-width: 500px; - height: 37px; - position: absolute; - bottom: 0; - left: 0; - right: 0; + max-width: 500px; + height: 37px; + position: absolute; + bottom: 0; + left: 0; + right: 0; } .chat-open-button { - display: block; - position: absolute; - left: 0; - bottom: 0; - z-index: 2; - width: 42px; - height: 40px; + display: block; + position: absolute; + left: 0; + bottom: 0; + z-index: 2; + width: 42px; + height: 40px; } .chat-send-button { - position: absolute; - right: 0; - top: 0; - padding: 0 8px; + position: absolute; + right: 0; + top: 0; + padding: 0 8px; } .chat-input { - opacity: 0.6; - background: black; - color: white; - border: none; - z-index: 1; - padding: 8px; - font-size: 14px; - padding-right: 35px; - padding-left: 90px; - border-radius: $border-radius-base; - outline: none; - width: 100%; + opacity: 0.6; + background: black; + color: white; + border: none; + z-index: 1; + padding: 8px; + font-size: 14px; + padding-right: 35px; + padding-left: 90px; + border-radius: $border-radius-base; + outline: none; + width: 100%; } .chat-box-type { - position: absolute; - left: 42px; - top: 8px; - font-size: 14px; - user-select: none; + position: absolute; + left: 42px; + top: 8px; + font-size: 14px; + user-select: none; cursor: pointer; - - .chat-party > &, .chat-party-think > & { - color: $chat-party; + + .chat-party > &, .chat-party-think > & { + color: $chat-party; } - - .chat-say > &, .chat-think > & { - color: $chat-color; + + .chat-say > &, .chat-think > & { + color: $chat-color; } - - .chat-sup > & { - color: $supporter-1; + + .chat-sup > & { + color: $supporter-1; } - - .chat-sup1 > & { - color: $supporter-1; + + .chat-sup1 > & { + color: $supporter-1; } - - .chat-sup2 > & { - color: $supporter-2; + + .chat-sup2 > & { + color: $supporter-2; } - - .chat-sup3 > & { - color: $supporter-3; - } - - .chat-whisper > & { - color: $chat-whisper; + + .chat-sup3 > & { + color: $supporter-3; } + + .chat-whisper > & { + color: $chat-whisper; + } } .chat-box-type-name { - display: inline-block; - max-width: 120px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - vertical-align: bottom; + display: inline-block; + max-width: 120px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: bottom; +} + +.chat-emoji-button { + position: absolute; + right: 30px; + bottom: 3px; + padding: 0; + margin: 0; + -webkit-filter: grayscale(100%); + filter: grayscale(100%); + + &:hover { + bottom: 4px; + -webkit-filter: grayscale(0); + filter: grayscale(0%); + } +} + +.emoji-box { + display: flex; + padding: 10px; + position: absolute; + background-color: #000000; + opacity: 0.95; + bottom: 40px; + left: 0; + right: 0; + width: 100%; + height: 190px; + overflow-y: auto; + + &::-webkit-scrollbar { + width: 7px; + } + + &::-webkit-scrollbar-thumb { + background: rgb(77, 77, 77); + border-radius: 14px; + } +} + +.emoji-box ul { + margin-bottom: 0; +} + +.emoji-box li { + display: inline; + list-style: none; + column-width: 180px; +} + +.emote-sample { + user-select: none; + display: inline-block; + width: 29px; + height: 29px; + text-align: center; + margin-bottom: 3px; + cursor: pointer; + &:hover { + background: rgba(0, 255, 255, 0.2); + border-radius: 2px; + } +} + +.emote-sample emote-box { + line-height: 2; } diff --git a/src/ts/components/shared/chat-box/chat-box.ts b/src/ts/components/shared/chat-box/chat-box.ts index 909afc7..2cdacac 100644 --- a/src/ts/components/shared/chat-box/chat-box.ts +++ b/src/ts/components/shared/chat-box/chat-box.ts @@ -9,7 +9,7 @@ import { faComment, faAngleDoubleRight } from '../../../client/icons'; import { isInParty } from '../../../client/partyUtils'; import { handleActionCommand } from '../../../client/playerActions'; import { hasHeadAnimation } from '../../../common/pony'; -import { AutocompleteState, autocompleteMesssage, replaceEmojis } from '../../../client/emoji'; +import { AutocompleteState, autocompleteMesssage, replaceEmojis, emojis } from '../../../client/emoji'; import { replaceNodes } from '../../../client/htmlUtils'; import { invalidEnumReturn } from '../../../common/utils'; import { findMatchingEntityNames, findEntityOrMockByAnyMeans, findBestEntityByName } from '../../../client/handlers'; @@ -45,6 +45,8 @@ export class ChatBox implements AfterViewInit, OnDestroy { readonly maxSayLength = SAY_MAX_LENGTH; readonly commentIcon = faComment; readonly sendIcon = faAngleDoubleRight; + readonly emotes = emojis.map(e => e); + emojiBoxState = 'none'; @ViewChild('inputElement', { static: true }) inputElement!: ElementRef; @ViewChild('typeBox', { static: true }) typeBox!: ElementRef; @ViewChild('typePrefix', { static: true }) typePrefix!: ElementRef; @@ -54,6 +56,7 @@ export class ChatBox implements AfterViewInit, OnDestroy { isOpen = false; message: string | undefined = ''; chatType = ChatType.Say; + btnEmoji = emojis[0].names[0]; private pasted = false; private lastMessages: string[] = []; private state: AutocompleteState = {}; @@ -92,6 +95,18 @@ export class ChatBox implements AfterViewInit, OnDestroy { ngOnDestroy() { this.subscriptions.forEach(s => s.unsubscribe()); } + addEmoji(emoji: string) { + this.emojiBoxState = 'none'; + if (!this.message) this.message = emoji; + else if (this.input.maxLength > this.message.length) { + this.message += emoji; + } + } + openBox() { + if (this.emojiBoxState === 'none') + this.emojiBoxState = 'inline-block'; + else this.emojiBoxState = 'none'; + } send(_event: Event | undefined) { let chatType = this.chatType; let message = replaceEmojis(cleanMessage(this.message || '')).substr(0, SAY_MAX_LENGTH); @@ -136,7 +151,6 @@ export class ChatBox implements AfterViewInit, OnDestroy { this.lastMessages.shift(); } } - this.close(); } } @@ -259,6 +273,7 @@ export class ChatBox implements AfterViewInit, OnDestroy { } private close() { if (this.isOpen) { + this.emojiBoxState = 'none'; this.input.blur(); this.isOpen = false; this.chatBox.nativeElement.hidden = true; @@ -273,6 +288,11 @@ export class ChatBox implements AfterViewInit, OnDestroy { this.open(); } } + onMouseEnter(event: MouseEvent) { + if (!event.target) return; + const value = emojis[Math.floor(Math.random() * emojis.length)]; + this.btnEmoji = value.names[0]; + } toggleChatType() { const chatTypes = getChatTypes(this.game); this.chatType = chatTypes[(chatTypes.indexOf(this.chatType) + 1) % chatTypes.length]; diff --git a/src/ts/components/shared/chat-log/chat-log.pug b/src/ts/components/shared/chat-log/chat-log.pug index 3bc0af1..2d1aa0e 100644 --- a/src/ts/components/shared/chat-log/chat-log.pug +++ b/src/ts/components/shared/chat-log/chat-log.pug @@ -13,6 +13,7 @@ | Party a.link-plain.chat-log-tab(#whisperTab tabindex (click)="switchTab('whisper')") | Whisper + input.link-plain.chat-log-tab.chat-log-filter(#filterTab type="text" [style.background-color]="filterColor" (keyup.enter)="(unFocus())" (keyup.esc)="(unFocus())" [placeholder]="'Filter'" (keyup)="filterChat()") .chat-log-scroll-outer() .chat-log-scroll-inner(#scroll) .chat-log-scroll-inner-inner(#lines) diff --git a/src/ts/components/shared/chat-log/chat-log.scss b/src/ts/components/shared/chat-log/chat-log.scss index 62796ae..f7dbcc4 100644 --- a/src/ts/components/shared/chat-log/chat-log.scss +++ b/src/ts/components/shared/chat-log/chat-log.scss @@ -10,150 +10,166 @@ $resizer-size: 12px; $resizer-offset: $resizer-size / 2; .chat-log { - display: flex; - position: absolute; - bottom: 0; - left: 0; - right: 0; - min-width: 200px; - max-width: 100%; - max-height: calc(100vh - 220px); - min-height: 120px; + display: flex; + position: absolute; + bottom: 0; + left: 0; + right: 0; + min-width: 200px; + max-width: 100%; + max-height: calc(100vh - 220px); + min-height: 120px; } .chat-log-buttons { - flex-direction: column-reverse; - width: $chat-log-buttons-width; - padding: 5px 3px; - padding-bottom: 30px; - z-index: 2; + flex-direction: column-reverse; + width: $chat-log-buttons-width; + padding: 5px 3px; + padding-bottom: 30px; + z-index: 2; } .chat-log-toggle { - position: absolute; - bottom: 5px; - left: 4px; - z-index: 3; + position: absolute; + bottom: 5px; + left: 4px; + z-index: 3; } .has-unread .main-bubble { - color: $chat-whisper; + color: $chat-whisper; } .whispers-count { - color: white; - font-weight: bold; - text-align: center; - position: absolute; - left: 6px; - top: 9px; - width: 16px; - font-size: 10px; - display: flex; - justify-content: center; - align-items: center; - line-height: 1rem; + color: white; + font-weight: bold; + text-align: center; + position: absolute; + left: 6px; + top: 9px; + width: 16px; + font-size: 10px; + display: flex; + justify-content: center; + align-items: center; + line-height: 1rem; } .whisper-icon { - position: absolute; - top: 0; - left: 0; - font-size: 14px; - color: $chat-whisper !important; + position: absolute; + top: 0; + left: 0; + font-size: 14px; + color: $chat-whisper !important; } .chat-log-content { - position: relative; - display: flex; - background: $chat-log-bg; - padding: 5px 0; - padding-left: 40px; - margin-left: -$chat-log-buttons-width; - border-radius: $border-radius-base; - word-break: break-word; - white-space: pre-line; - line-height: 20px; - height: 100%; - width: 100%; + position: relative; + display: flex; + background: $chat-log-bg; + padding: 5px 0; + padding-left: 40px; + margin-left: -$chat-log-buttons-width; + border-radius: $border-radius-base; + word-break: break-word; + white-space: pre-line; + line-height: 20px; + height: 100%; + width: 100%; } .chat-log-tabs { - position: absolute; - top: -$chat-log-tabs-height; - left: 30px; - display: flex; - user-select: none; + position: absolute; + top: -$chat-log-tabs-height; + left: 30px; + display: flex; + user-select: none; + text-shadow: 0px 0px 4px #000000; } .chat-log-tab { - background: $chat-log-bg-faded; - padding: 5px 10px 0; - margin-left: 10px; - height: $chat-log-tabs-height; - border-top-left-radius: $border-radius-base; - border-top-right-radius: $border-radius-base; - color: white; + background: $chat-log-bg-faded; + padding: 5px 10px 0; + margin-left: 10px; + height: $chat-log-tabs-height; + border-top-left-radius: $border-radius-base; + border-top-right-radius: $border-radius-base; + color: white; overflow: hidden; - - &.active { - background: $chat-log-bg; - } + + &.active { + background: $chat-log-bg; + } } .chat-log-scroll-outer { - overflow: hidden; - width: 100%; + overflow: hidden; + width: 100%; } .chat-log-scroll-inner { - width: calc(100% + 50px); - height: 100%; - overflow-x: hidden; - overflow-y: scroll; - -webkit-overflow-scrolling: touch; + width: calc(100% + 50px); + height: 100%; + overflow-x: hidden; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; } .chat-log-scroll-inner-inner { - display: flex; - flex-direction: column; - justify-content: flex-end; - min-height: 100%; - padding: 0 5px; + display: flex; + flex-direction: column; + justify-content: flex-end; + min-height: 100%; + padding: 0 5px; } .chat-log-resize-top { - position: absolute; - top: -$resizer-offset; - right: $resizer-offset; - height: $resizer-size; - left: 250px; - cursor: ns-resize; + position: absolute; + top: -$resizer-offset; + right: $resizer-offset; + height: $resizer-size; + left: 250px; + cursor: ns-resize; } .chat-log-resize-top-right { - position: absolute; - top: -($resizer-offset + 1); - right: -($resizer-offset + 1); - width: $resizer-size + 4; - height: $resizer-size + 4; - cursor: nesw-resize; + position: absolute; + top: -($resizer-offset + 1); + right: -($resizer-offset + 1); + width: $resizer-size + 4; + height: $resizer-size + 4; + cursor: nesw-resize; } .chat-log-resize-right { - position: absolute; - top: $resizer-offset; - right: -$resizer-offset; - width: $resizer-size; - bottom: $resizer-offset; - cursor: ew-resize; + position: absolute; + top: $resizer-offset; + right: -$resizer-offset; + width: $resizer-size; + bottom: $resizer-offset; + cursor: ew-resize; } .resize-icon { - font-size: 18px; - position: absolute; - top: -3px; - right: 1px; - transform: rotate(45deg); - color: rgba(255, 255, 255, 0.2); + font-size: 18px; + position: absolute; + top: -3px; + right: 1px; + transform: rotate(45deg); + color: rgba(255, 255, 255, 0.2); +} + +.chat-log-filter { + border: none; + width: 100px; +} + +@media only screen and (max-width: 360px) { + .chat-log-filter { + position: absolute; + left: 0; + bottom: 35px; + width: 100%; + border-radius: 2px; + } } diff --git a/src/ts/components/shared/chat-log/chat-log.ts b/src/ts/components/shared/chat-log/chat-log.ts index 1ac6223..6b1264d 100644 --- a/src/ts/components/shared/chat-log/chat-log.ts +++ b/src/ts/components/shared/chat-log/chat-log.ts @@ -8,9 +8,10 @@ import { MessageType, isPartyMessage, ChatMessage, Pony, FakeEntity, isWhisper, import { SettingsService } from '../../services/settingsService'; import { AgDragEvent } from '../directives/agDrag'; import { element, textNode, removeAllNodes, replaceNodes } from '../../../client/htmlUtils'; -import { DEFAULT_CHATLOG_OPACITY, PONY_TYPE } from '../../../common/constants'; +import { DEFAULT_CHATLOG_OPACITY, PONY_TYPE, SECOND } from '../../../common/constants'; import { faCaretUp, faArrowDown } from '../../../client/icons'; import { sampleMessages } from '../../../common/debugData'; +import { findEntityById } from '../../../common/worldMap'; interface IndexEntryUser { id: number; @@ -26,6 +27,8 @@ interface ChatLogLineDOM { entry: ChatLogMessage; root: HTMLElement; label: HTMLElement; + time: HTMLElement; + timeContent: HTMLElement; labelText: Text; name: HTMLElement; nameContent: HTMLElement; @@ -98,11 +101,19 @@ export function createChatLogLineDOM(clickLabel: ClickHandler, clickName: ClickH line.root = element('div', 'chat-line', [ element('span', 'chat-line-lead'), + line.time = element('span', 'chat-line-name', [ + textNode(`[`), + line.timeContent = element( + 'span', 'chat-line-time-content', [textNode('')], undefined), + line.index = element('span', 'chat-line-time-index', [line.indexText = textNode('')]), + textNode('] '), + ]), line.label = element( 'span', 'chat-line-label mr-1', [line.labelText = textNode('')], undefined, { click: () => clickLabel(line.entry) }), + line.prefixText = textNode(''), line.name = element('span', 'chat-line-name', [ - textNode('['), + textNode(`[`), line.nameContent = element( 'span', 'chat-line-name-content', [textNode('')], undefined, { click: () => clickName(line.entry) }), line.index = element('span', 'chat-line-name-index', [line.indexText = textNode('')], { title: 'duplicate name' }), @@ -125,12 +136,32 @@ export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage) { line.labelText.nodeValue = label ? `[${label}]` : ''; updateChatLogName(line, entry); + updateTime(line); line.prefixText.nodeValue = prefix || ''; line.suffixText.nodeValue = suffix ? ` ${suffix}: ` : ': '; replaceNodes(line.message, message); } +function updateTime(line: ChatLogLineDOM) { + line.time.style.display = 'inline'; + replaceNodes(line.timeContent, getCurrentTime()); +} + +function getCurrentTime() { + const date = new Date(); + const hours = date.getHours().toString(); + const minutes = date.getMinutes().toString(); + const seconds = date.getSeconds().toString(); + return `${hours.length === 1 ? `0${hours}` : hours}:${minutes.length === 1 ? `0${minutes}` : minutes}:${seconds.length === 1 ? `0${seconds}` : seconds}`; +} + +function setNameColors(line: ChatLogLineDOM | undefined, colors?: string[] | null) { + if (!colors || !line) return; + if (colors[1]) line.name.style.color = colors[1]; + if (colors[0]) line.nameContent.style.color = colors[0]; +} + function updateChatLogName(line: ChatLogLineDOM, { name, index }: ChatLogMessage) { if (name) { line.name.style.display = 'inline'; @@ -171,7 +202,6 @@ function findUserIndex(users: IndexEntryUser[], id: number, crc: number | undefi return i; } } - return -1; } @@ -189,6 +219,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { @ViewChild('localTab', { static: true }) localTab!: ElementRef; @ViewChild('partyTab', { static: true }) partyTab!: ElementRef; @ViewChild('whisperTab', { static: true }) whisperTab!: ElementRef; + @ViewChild('filterTab', { static: true }) filterTab!: ElementRef; @ViewChild('toggleButton', { static: true }) toggleButton!: ElementRef; @ViewChild('count', { static: true }) countElement!: ElementRef; @ViewChild('content', { static: true }) contentElement!: ElementRef; @@ -210,6 +241,9 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { private indexes = new Map(); private messageCounter = 0; private lastOpacity = 0; + private autoClear: null | NodeJS.Timeout = null; + private autoUnfocus: null | NodeJS.Timeout = null; + filterColor = this.inactiveBg; constructor( private game: PonyTownGame, private settingsService: SettingsService, @@ -277,6 +311,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } } ngAfterViewInit() { + const canvas = document.getElementById('canvas') as HTMLCanvasElement; + canvas.addEventListener('click', () => this.unFocus()); this.game.findEntityFromChatLog = this.findEntityFromMessages; this.game.findEntityFromChatLogByName = this.findEntityFromMessagesByName; @@ -414,6 +450,165 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { return index; } + + filterChat() { + this.clearTimeOutAutoClear(); + this.clearTimeOutAutoUnfocus(); + let value = this.filterTab.nativeElement.value; + if (!value) { + this.clearTimeOutAutoClear(); + this.unFocus(); + this.showHideChatLogLines('', false); + return; + } + else this.filterColor = this.bg; + + this.autoClear = setTimeout(() => { + this.filterTab.nativeElement.value = ''; + this.filterChat(); + this.unFocus(); + }, SECOND * 30); + + this.autoUnfocus = setTimeout(() => { + this.unFocus(); + }, SECOND * 3); + + let toLowerCase = false; + + if (value.startsWith('#')) value = value.slice(1); + else if (value.startsWith('/')) { + value = value.slice(1); + try { + const regExp = new RegExp(value); + this.showHideChatLogLines(regExp, toLowerCase); + this.filterTab.nativeElement.style.color = ''; + } catch (err) { + this.filterTab.nativeElement.style.color = '#ff6666'; + } + return; + } + else { + value = value.toLowerCase(); + toLowerCase = true; + } + this.showHideChatLogLines(value, toLowerCase); + } + + showHideChatLogLines(content: string | RegExp, caseSensitive: boolean) { + const lines = this.linesElement.getElementsByTagName('div'); + + for (let i = 0; i < lines.length; i++) { + let textContent = lines[i].textContent; + + if (textContent) { + textContent = textContent.slice(10); + + if (typeof content === 'string') { + if (caseSensitive) + textContent.toLowerCase().includes(content) ? lines[i].hidden = false : lines[i].hidden = true; + else + textContent.includes(content) ? lines[i].hidden = false : lines[i].hidden = true; + } else { + textContent.match(content) ? lines[i].hidden = false : lines[i].hidden = true; + } + } + } + } + + unhideAllLines() { + const lines = this.linesElement.getElementsByTagName('div'); + for (let i = 0; i < lines.length; i++) { + lines[i].hidden = false; + } + } + + private getRGB(hex: string) { + const bigint = parseInt(hex, 16); + const r = (bigint >> 16) & 255; + const g = (bigint >> 8) & 255; + const b = bigint & 255; + return [r, g, b]; + } + + private RGBToHSL(rgb: number[]) { + let r = rgb[0] / 255; + let g = rgb[1] / 255; + let b = rgb[2] / 255; + let max = Math.max(r, g, b); + let min = Math.min(r, g, b); + let delta = max - min; + let h = 0; + let s; + let l; + + if (max === min) h = 0; + else if (r === max) h = (g - b) / delta; + else if (g === max) h = 2 + (b - r) / delta; + else if (b === max) h = 4 + (r - g) / delta; + + h = Math.min(h * 60, 360); + + if (h < 0) h += 360; + + l = (min + max) / 2; + + if (max === min) s = 0; + else if (l <= 0.5) s = delta / (max + min); + else s = delta / (2 - max - min); + h = Math.floor(h); + s = Math.floor(s * 100); + l = Math.floor(l * 100); + + if (l < 45) l = 45; + //if (s > 60) s = 60; + return `hsl(${h}, ${s}%, ${l}%)`; + } + + fixColor(color: string) { + if (color === 'ff') color = '000000'; + if (color === '0') color = 'ffffff'; + return color; + } + + private getCharaterColors(id: number | undefined) { + if (!id) return null; + const entity = findEntityById(this.game.map, id) as Pony; + if (!entity || !entity.palettePonyInfo) return null; + let colors = []; + let body = entity.palettePonyInfo.body; + let mane = entity.palettePonyInfo.mane; + if (body && body.palette && body.palette.colors[1]) { + let bodyColor = this.fixColor(body.palette.colors[1].toString(16).substr(0, 6)); + const RGB = this.getRGB(bodyColor); + colors.push(this.RGBToHSL(RGB)); + } + if (mane && mane.palette && mane.palette.colors[1]) { + let maneColor = this.fixColor(mane.palette.colors[1].toString(16).substr(0, 6)); + const RGB = this.getRGB(maneColor); + colors.push(this.RGBToHSL(RGB)); + } else if (colors && colors[0]) { + colors.push((colors[0])); + } + return colors; + } + + clearTimeOutAutoClear() { + if (this.autoClear) clearTimeout(this.autoClear); + this.autoClear = null; + this.filterColor = this.inactiveBg; + } + + clearTimeOutAutoUnfocus() { + if (this.autoUnfocus) clearTimeout(this.autoUnfocus); + this.autoUnfocus = null; + } + + unFocus() { + this.clearTimeOutAutoUnfocus(); + if (this.filterTab.nativeElement) + this.filterTab.nativeElement.blur(); + } + addMessage(message: ChatMessage) { if (message.name && message.message) { const entry = this.createEntry(message); @@ -422,14 +617,19 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { const open = this.open; const scrolledToEnd = open ? this.scrolledToEnd : false; const tab = this.activeTab; + let colors: string[] | null = null; + colors = this.getCharaterColors(message.id); + this.addEntryToList(this.local, GENERAL_CHAT_LIMIT, open && tab === 'local', entry); + setNameColors(entry.dom, colors); if (party || whisper) { const partyEntry = { ...entry }; partyEntry.dom = undefined; partyEntry.label = whisper ? partyEntry.label : undefined; this.addEntryToList(this.party, PARTY_CHAT_LIMIT, open && tab === 'party', partyEntry); + setNameColors(partyEntry.dom, colors); } if (whisper) { @@ -437,6 +637,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { whisperEntry.dom = undefined; whisperEntry.label = undefined; this.addEntryToList(this.whisper, WHISPER_CHAT_LIMIT, open && tab === 'whisper', whisperEntry); + setNameColors(whisperEntry.dom, colors); } if (message.type === MessageType.Whisper && !this.open) { @@ -447,12 +648,21 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.scrollToEnd(); } + if (tab !== 'whisper' && isWhisper(message.type)) { + this.whisperTab.nativeElement.classList.add('unread'); + this.whisperTab.nativeElement.style.backgroundColor = `rgba(225, 161, 223, ${this.opacity / 100})`; + } + if (tab !== 'party' && party) { + this.partyTab.nativeElement.classList.add('unread'); + this.partyTab.nativeElement.style.backgroundColor = `rgba(184, 227, 255, ${this.opacity / 100})`; + } + this.filterChat(); this.messageCounter++; } } + private addEntryToList(list: ChatLogMessage[], limit: number, isOpen: boolean, entry: ChatLogMessage) { let removedDom: ChatLogLineDOM | undefined; - while (list.length >= limit) { const removed = list.shift(); @@ -465,7 +675,6 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { removed.dom = undefined; } } - list.push(entry); if (isOpen) { @@ -474,11 +683,13 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.linesElement.appendChild(entry.dom.root); } } + toggle() { this.settings.chatlogClosed = !this.settings.chatlogClosed; this.settingsService.saveBrowserSettings(); this.updateOpen(); } + switchTab(tab: Tab) { if (this.activeTab !== tab) { this.settings.chatlogTab = tab; @@ -488,31 +699,39 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.updateTabs(); } } + private updateTabs() { this.setActiveTab(this.localTab.nativeElement, this.activeTab === 'local'); this.setActiveTab(this.partyTab.nativeElement, this.activeTab === 'party'); this.setActiveTab(this.whisperTab.nativeElement, this.activeTab === 'whisper'); } + private setActiveTab(tab: HTMLElement, active: boolean) { if (active) { tab.classList.add('active'); + tab.classList.remove('unread'); tab.style.backgroundColor = this.bg; } else { tab.classList.remove('active'); - tab.style.backgroundColor = this.inactiveBg; + if (!tab.classList.contains('unread')) + tab.style.backgroundColor = this.inactiveBg; } } + scrollToEnd() { // requestAnimationFrame(this.scrollHandler); this.scrollToEndAtFrame = true; } + scrollHandler = () => { this.scrollingToEnd = true; this.scroll.nativeElement.scrollTop = 99999; } + clickNameHandler = (message: ChatLogMessage) => { this.zone.run(() => this.nameClick.emit(message)); } + clickLabel = (message: ChatLogMessage) => { this.zone.run(() => { if (message.label) { @@ -520,9 +739,11 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } }); } + private clearList() { removeAllNodes(this.linesElement); } + private regenerateList() { this.clearList(); @@ -531,12 +752,14 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.messages.forEach(entry => { if (!entry.dom) { entry.dom = createChatLogLineDOM(this.clickLabel, this.clickNameHandler); + setNameColors(entry.dom, this.getCharaterColors(entry.entityId)); updateChatLogLine(entry.dom, entry); } lines.appendChild(entry.dom.root); }); } + drag({ x, y, type, event }: AgDragEvent, resizeY: boolean, resizeX: boolean) { event.preventDefault(); @@ -548,7 +771,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } if (resizeX) { - this.settings.chatlogWidth = clamp(x - this.startX, 200, 2000); + this.settings.chatlogWidth = clamp(x - this.startX, 200 + 74.7, 2000); } if (resizeY) { @@ -566,6 +789,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.scrollToEnd(); } } + private setUnread(value: number) { if (this.unread !== value) { this.unread = value; @@ -581,11 +805,13 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } } } + private findEntityFromMessages = (id: number): FakeEntity | undefined => { return findEntityFromMessages(id, this.whisper) || findEntityFromMessages(id, this.party) || findEntityFromMessages(id, this.local); } + private findEntityFromMessagesByName = (name: string): FakeEntity | undefined => { return findEntityFromMessagesByName(name, this.game.playerId, this.whisper) || findEntityFromMessagesByName(name, this.game.playerId, this.party) ||