diff --git a/src/ts/common/color.ts b/src/ts/common/color.ts index f6b7040..a0b5944 100644 --- a/src/ts/common/color.ts +++ b/src/ts/common/color.ts @@ -157,6 +157,12 @@ export interface HSVA { a: number; } +export interface HSL { + h: number; + s: number; + l: number; +} + export interface RGB { r: number; g: number; @@ -493,3 +499,37 @@ export function h2rgb(h: number): RGB { b: Math.round(b * 255) }; } + +export function rgb2hsl(rgb: RGB): HSL { + const r = rgb.r / 255; + const g = rgb.g / 255; + const b = rgb.b / 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); + + return { h, s, l }; +} + +export function hsl2CSS(hsl: HSL) { + return `hsl(${hsl.h},${hsl.s}%,${hsl.l}%)`; +} diff --git a/src/ts/common/interfaces.ts b/src/ts/common/interfaces.ts index 4fa653d..e9ab8b8 100644 --- a/src/ts/common/interfaces.ts +++ b/src/ts/common/interfaces.ts @@ -683,6 +683,7 @@ export interface BrowserSettings { scale?: number; walkByDefault?: boolean; brightNight?: boolean; + timestamp?: '24' | '12'; } export interface EntityTypeName { diff --git a/src/ts/components/shared/chat-box/chat-box.pug b/src/ts/components/shared/chat-box/chat-box.pug index d14a4f4..e4ba3b3 100644 --- a/src/ts/components/shared/chat-box/chat-box.pug +++ b/src/ts/components/shared/chat-box/chat-box.pug @@ -5,10 +5,18 @@ 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)="toggleEmojiBox()" (mouseenter)="onMouseEnter($event)") button.game-button.chat-send-button((click)="send($event)" title="Send message" aria-label="Send message") fa-icon([icon]="sendIcon") button.game-button.chat-open-button( (click)="toggle()" (mousedown)="$event.preventDefault()" [disabled]="disabled" title="Toggle chat" aria-label="Toggle message") - fa-icon([icon]="commentIcon" [fixedWidth]="true") + fa-icon([icon]="commentIcon" [fixedWidth]="true") \ No newline at end of file diff --git a/src/ts/components/shared/chat-box/chat-box.scss b/src/ts/components/shared/chat-box/chat-box.scss index 611fb1e..dd74296 100644 --- a/src/ts/components/shared/chat-box/chat-box.scss +++ b/src/ts/components/shared/chat-box/chat-box.scss @@ -1,88 +1,148 @@ @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; - cursor: pointer; - - .chat-party > &, .chat-party-think > & { - color: $chat-party; - } - - .chat-say > &, .chat-think > & { - color: $chat-color; - } - - .chat-sup > & { - color: $supporter-1; - } - - .chat-sup1 > & { - color: $supporter-1; - } - - .chat-sup2 > & { - color: $supporter-2; - } - - .chat-sup3 > & { - color: $supporter-3; - } - - .chat-whisper > & { - color: $chat-whisper; - } + position: absolute; + left: 42px; + top: 8px; + font-size: 14px; + user-select: none; + cursor: pointer; + .chat-party>&, + .chat-party-think>& { + color: $chat-party; + } + .chat-say>&, + .chat-think>& { + color: $chat-color; + } + .chat-sup>& { + color: $supporter-1; + } + .chat-sup1>& { + color: $supporter-1; + } + .chat-sup2>& { + color: $supporter-2; + } + .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 { + user-select: none; + 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; + } + /* Add a thumb */ + &::-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; +} \ No newline at end of file diff --git a/src/ts/components/shared/chat-box/chat-box.ts b/src/ts/components/shared/chat-box/chat-box.ts index 909afc7..85c479a 100644 --- a/src/ts/components/shared/chat-box/chat-box.ts +++ b/src/ts/components/shared/chat-box/chat-box.ts @@ -9,10 +9,11 @@ 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'; +import * as _ from 'lodash'; const chatTypeNames: string[] = []; const chatTypeClasses: string[] = []; @@ -33,7 +34,7 @@ setupChatType(ChatType.Think, 'think'); setupChatType(ChatType.PartyThink, 'party think'); function isActionCommand(message: string) { - return /^\/(yawn|sneeze|achoo|laugh|lol|haha|хаха|jaja)/i.test(message); + return /^\/(yawn|sneeze|excite|tada|wink|achoo|laugh|lol|haha|хаха|jaja|hug)/i.test(message); } @Component({ @@ -45,6 +46,8 @@ export class ChatBox implements AfterViewInit, OnDestroy { readonly maxSayLength = SAY_MAX_LENGTH; readonly commentIcon = faComment; readonly sendIcon = faAngleDoubleRight; + readonly emotes = emojis; + emojiBoxState = 'none'; @ViewChild('inputElement', { static: true }) inputElement!: ElementRef; @ViewChild('typeBox', { static: true }) typeBox!: ElementRef; @ViewChild('typePrefix', { static: true }) typePrefix!: ElementRef; @@ -54,12 +57,14 @@ 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 = {}; private subscriptions: Subscription[]; private _disabled = false; constructor(private game: PonyTownGame, zone: NgZone) { + this.subscriptions = [ this.game.onChat.subscribe(() => zone.run(() => this.chat(undefined))), this.game.onToggleChat.subscribe(() => zone.run(() => this.toggle())), @@ -92,6 +97,18 @@ export class ChatBox implements AfterViewInit, OnDestroy { ngOnDestroy() { this.subscriptions.forEach(s => s.unsubscribe()); } + addEmoji(emoji: string) { + this.toggleEmojiBox(); + if (!this.message) this.message = emoji; + else if (this.input.maxLength > this.message.length) { + this.message += emoji; + } + } + toggleEmojiBox() { + 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 +153,6 @@ export class ChatBox implements AfterViewInit, OnDestroy { this.lastMessages.shift(); } } - this.close(); } } @@ -259,6 +275,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 +290,11 @@ export class ChatBox implements AfterViewInit, OnDestroy { this.open(); } } + onMouseEnter(event: MouseEvent) { + if (!event.target) return; + const emoji = _.sample(emojis) || emojis[0]; + this.btnEmoji = emoji.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..6a3b75a 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) @@ -25,3 +26,4 @@ button.game-button.chat-log-toggle(#toggleButton (click)="toggle()" title="Toggl path.main-bubble(fill="currentColor" d="M416 192c0-88.4-93.1-160-208-160S0 103.6 0 192c0 34.3 14.1 65.9 38 92-13.4 30.2-35.5 54.2-35.8 54.5-2.2 2.3-2.8 5.7-1.5 8.7S4.8 352 8 352c36.6 0 66.9-12.3 88.7-25 32.2 15.7 70.3 25 111.3 25 114.9 0 208-71.6 208-160z") path(fill="currentColor" d="M538 412c23.9-26 38-57.7 38-92 0-66.9-53.5-124.2-129.3-148.1.9 6.6 1.3 13.3 1.3 20.1 0 105.9-107.7 192-240 192-10.8 0-21.3-.8-31.7-1.9C207.8 439.6 281.8 480 368 480c41 0 79.1-9.2 111.3-25 21.8 12.7 52.1 25 88.7 25 3.2 0 6.1-1.9 7.3-4.8 1.3-2.9.7-6.3-1.5-8.7-.3-.3-22.4-24.2-35.8-54.5z") .whispers-count(#count) + \ No newline at end of file diff --git a/src/ts/components/shared/chat-log/chat-log.scss b/src/ts/components/shared/chat-log/chat-log.scss index 62796ae..d6e6195 100644 --- a/src/ts/components/shared/chat-log/chat-log.scss +++ b/src/ts/components/shared/chat-log/chat-log.scss @@ -1,159 +1,198 @@ @import '../../../../styles/partials/variables'; - $chat-log-bg: rgba(0, 0, 0, 0.2); $chat-log-bg-faded: rgba(0, 0, 0, 0.1); $chat-log-bg-hover: rgba(0, 0, 0, 0.5); $chat-log-tabs-height: 30px; $chat-log-buttons-width: 40px; - $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; - overflow: hidden; - - &.active { - background: $chat-log-bg; - } + 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; + } } .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 { + padding-bottom: 5px; + border: none; + width: 100px; +} + +.chat-log-filter::-webkit-input-placeholder { + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; +} + +.chat-log-filter:-moz-placeholder { + /* Firefox 18- */ + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; +} + +.chat-log-filter::-moz-placeholder { + /* Firefox 19+ */ + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; +} + +.chat-log-filter:-ms-input-placeholder { + opacity: 1; + color: white; + text-shadow: 0px 0px 4px #000000; +} + +@media only screen and (max-width: 360px) { + .chat-log-filter { + position: absolute; + left: 0; + bottom: 35px; + width: 100%; + border-radius: 2px; + } +} \ No newline at end of file diff --git a/src/ts/components/shared/chat-log/chat-log.ts b/src/ts/components/shared/chat-log/chat-log.ts index 9367fb3..c60175a 100644 --- a/src/ts/components/shared/chat-log/chat-log.ts +++ b/src/ts/components/shared/chat-log/chat-log.ts @@ -12,6 +12,8 @@ import { DEFAULT_CHATLOG_OPACITY, PONY_TYPE, SECOND } from '../../../common/cons import { faCaretUp, faArrowDown } from '../../../client/icons'; import { sampleMessages } from '../../../common/debugData'; import { findEntityById } from '../../../common/worldMap'; +import { colorToRGBA, rgb2hsl, HSL, hsl2CSS } from '../../../common/color'; +import * as moment from 'moment'; interface IndexEntryUser { id: number; @@ -103,11 +105,9 @@ 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) }), @@ -127,7 +127,7 @@ export function createChatLogLineDOM(clickLabel: ClickHandler, clickName: ClickH return line; } -export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage) { +export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage, hourMode?: '12' | '24') { const { classes, label, message, prefix, suffix } = entry; const hasSpace = message.indexOf(' ') !== -1; @@ -137,27 +137,23 @@ export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage) { line.labelText.nodeValue = label ? `[${label}]` : ''; updateChatLogName(line, entry); - updateTime(line); + updateTime(line, hourMode); line.prefixText.nodeValue = prefix || ''; line.suffixText.nodeValue = suffix ? ` ${suffix}: ` : ': '; replaceNodes(line.message, message); } -function updateTime(line: ChatLogLineDOM) { +function updateTime(line: ChatLogLineDOM, hourMode?: '12' | '24') { line.time.style.display = 'inline'; - replaceNodes(line.timeContent, getCurrentTime()); + if (!hourMode) return; + if (hourMode === '24') + replaceNodes(line.timeContent, `[${moment().format('HH:mm:ss')}] `); + if (hourMode === '12') + replaceNodes(line.timeContent, `[${moment().format('LTS')}] `); } -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) { +function setNameColors(line: ChatLogLineDOM | undefined, colors?: string[]) { if (!colors || !line) return; if (colors[1]) line.name.style.color = colors[1]; if (colors[0]) line.nameContent.style.color = colors[0]; @@ -232,6 +228,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { local: ChatLogMessage[] = []; party: ChatLogMessage[] = []; whisper: ChatLogMessage[] = []; + filterColor = this.inactiveBg; unread = 0; private subscriptions: Subscription[] = []; private startX = 0; @@ -243,9 +240,8 @@ 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; + private autoClear?: NodeJS.Timeout; + private autoUnfocus?: NodeJS.Timeout; constructor( private game: PonyTownGame, private settingsService: SettingsService, @@ -460,7 +456,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { if (!value) { this.clearTimeOutAutoClear(); this.unFocus(); - this.showHideChatLogLines('', false); + this.filterChatLogLines('', false); return; } else this.filterColor = this.bg; @@ -482,9 +478,10 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { value = value.slice(1); try { const regExp = new RegExp(value); - this.showHideChatLogLines(regExp, toLowerCase); + this.filterChatLogLines(regExp, toLowerCase); this.filterTab.nativeElement.style.color = ''; - } catch (err) { + } catch (err) {// If the user inputs invalid regExp we just notify him by changing text color to red + if (DEVELOPMENT) console.error(err); this.filterTab.nativeElement.style.color = '#ff6666'; } return; @@ -493,10 +490,10 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { value = value.toLowerCase(); toLowerCase = true; } - this.showHideChatLogLines(value, toLowerCase); + this.filterChatLogLines(value, toLowerCase); } - showHideChatLogLines(content: string | RegExp, caseSensitive: boolean) { + filterChatLogLines(content: string | RegExp, caseSensitive: boolean) { const lines = this.linesElement.getElementsByTagName('div'); for (let i = 0; i < lines.length; i++) { @@ -507,11 +504,12 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { if (typeof content === 'string') { if (caseSensitive) - textContent.toLowerCase().includes(content) ? lines[i].hidden = false : lines[i].hidden = true; + lines[i].hidden = !textContent.toLowerCase().includes(content); else - textContent.includes(content) ? lines[i].hidden = false : lines[i].hidden = true; + lines[i].hidden = !textContent.includes(content); + } else { - textContent.match(content) ? lines[i].hidden = false : lines[i].hidden = true; + lines[i].hidden = !textContent.match(content); } } } @@ -523,79 +521,45 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { 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 < 40) { - if (l > 20 && l < 40) { - l += 20; - if (s > 11) s -= 11; + brightenDarkColors(hsl: HSL) { + if (hsl.l < 40) { + if (hsl.l > 20 && hsl.l < 40) { + hsl.l += 20; + if (hsl.s > 11) hsl.s -= 11; } else { - l = 40; - if (s > 11) s = 0; + hsl.l = 40; + if (hsl.s > 11) hsl.s = 0; } } - - return `hsl(${h}, ${s}%, ${l}%)`; + return hsl; } + fixColor(color: string) { while (color.length <= 7) { color = `0${color}`; } - color = color.replace(/0/g, '1'); return color.substr(0, 6); } - private getCharaterColors(id: number | undefined) { - if (!id) return null; + + private getCharacterColors(id: number | undefined) { + if (!id) return; const entity = findEntityById(this.game.map, id) as Pony; - if (!entity || !entity.palettePonyInfo) return null; + if (!entity || !entity.palettePonyInfo) return; let colors = []; - let body = entity.palettePonyInfo.body; - let mane = entity.palettePonyInfo.mane; + let { body, mane } = entity.palettePonyInfo; if (body && body.palette && body.palette.colors[1]) { - let bodyColor = this.fixColor(body.palette.colors[1].toString(16)); - const RGB = this.getRGB(bodyColor); - colors.push(this.RGBToHSL(RGB)); + const rgb = colorToRGBA(body.palette.colors[1]); + const hsl = rgb2hsl(rgb); + this.brightenDarkColors(hsl); + colors.push(hsl2CSS(hsl)); } if (mane && mane.palette && mane.palette.colors[1]) { - let maneColor = this.fixColor(mane.palette.colors[1].toString(16)); - const RGB = this.getRGB(maneColor); - colors.push(this.RGBToHSL(RGB)); + const rgb = colorToRGBA(mane.palette.colors[1]); + const hsl = rgb2hsl(rgb); + this.brightenDarkColors(hsl); + colors.push(hsl2CSS(hsl)); } else if (colors && colors[0]) { colors.push((colors[0])); } @@ -604,12 +568,12 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } clearTimeOutAutoClear() { if (this.autoClear) clearTimeout(this.autoClear); - this.autoClear = null; + this.autoClear = undefined; this.filterColor = this.inactiveBg; } clearTimeOutAutoUnfocus() { if (this.autoUnfocus) clearTimeout(this.autoUnfocus); - this.autoUnfocus = null; + this.autoUnfocus = undefined; } unFocus() { this.clearTimeOutAutoUnfocus(); @@ -624,9 +588,7 @@ 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); - + const colors = this.getCharacterColors(message.id); this.addEntryToList(this.local, GENERAL_CHAT_LIMIT, open && tab === 'local', entry); setNameColors(entry.dom, colors); @@ -686,7 +648,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { if (isOpen) { entry.dom = removedDom || createChatLogLineDOM(this.clickLabel, this.clickNameHandler); - updateChatLogLine(entry.dom, entry); + updateChatLogLine(entry.dom, entry, this.settings.timestamp); this.linesElement.appendChild(entry.dom.root); } } @@ -702,6 +664,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { this.regenerateList(); this.scrollToEnd(); this.updateTabs(); + this.filterChat(); } } private updateTabs() { @@ -749,8 +712,8 @@ 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); + setNameColors(entry.dom, this.getCharacterColors(entry.entityId)); + updateChatLogLine(entry.dom, entry, this.settings.timestamp); } lines.appendChild(entry.dom.root); @@ -767,7 +730,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { } if (resizeX) { - this.settings.chatlogWidth = clamp(x - this.startX, 200 + 74.7, 2000); + const filterBoxWidth = 80; + this.settings.chatlogWidth = clamp(x - this.startX, 200 + filterBoxWidth, 2000); } if (resizeY) { diff --git a/src/ts/components/shared/settings-modal/settings-modal.pug b/src/ts/components/shared/settings-modal/settings-modal.pug index db4c1b1..6633b88 100644 --- a/src/ts/components/shared/settings-modal/settings-modal.pug +++ b/src/ts/components/shared/settings-modal/settings-modal.pug @@ -71,6 +71,17 @@ [(checked)]="account.ignoreNonFriendWhispers" help="Don't show any whispers from players who are not on your friends list") | Only allow whispers from friends + .form-group + .form-group + label#chatlog-opacity-label Message timestamp + div + .btn-group.ml-2(btnCheckbox) + button.btn.btn-outline-secondary.d-none.d-sm-block([class.active]='off' (click)='switchTimestamp()') Off + button.btn.btn-outline-secondary.d-none.d-sm-block([class.active]='twelve' (click)='switchTimestamp("12")') 12 + button.btn.btn-outline-secondary.d-none.d-sm-block([class.active]='twentyFour' (click)='switchTimestamp("24")') 24 + span.ml-2 {{timestampText}} + //small.form-text.text-muted.mt-0 Shows message time + .form-group label#chatlog-opacity-label Chatlog background opacity div @@ -78,7 +89,6 @@ [(value)]="account.chatlogOpacity" style="max-width: 300px" labelledBy="chatlog-opacity-label") | {{(account.chatlogOpacity / 100) | percent}} small.form-text.text-muted.mt-0 Changes chatlog background to make it more readable or less obtrusive. - .form-group label#chatlog-range-label Chatlog range div slider-bar.mr-2( diff --git a/src/ts/components/shared/settings-modal/settings-modal.ts b/src/ts/components/shared/settings-modal/settings-modal.ts index fe5a292..e4bcb84 100644 --- a/src/ts/components/shared/settings-modal/settings-modal.ts +++ b/src/ts/components/shared/settings-modal/settings-modal.ts @@ -26,6 +26,10 @@ export class SettingsModal implements OnInit, OnDestroy { readonly graphicsIcon = faImage; readonly exportIcon = faDownload; readonly importIcon = faUpload; + off = false; + twelve = false; + twentyFour = false; + timestampText: 'Disabled' | '24 Hour mode' | '12 Hour mode' = 'Disabled'; @Output() close = new EventEmitter(); account: AccountSettings = {}; browser: BrowserSettings = {}; @@ -59,6 +63,7 @@ export class SettingsModal implements OnInit, OnDestroy { this.browser = this.settingsService.browser; this.setupDefaults(); this.subscription = this.game.onLeft.subscribe(() => this.cancel()); + this.updateTimestampRadios(); } ngOnDestroy() { this.finishChatlogRange(); @@ -100,6 +105,37 @@ export class SettingsModal implements OnInit, OnDestroy { this.settingsService.saveBrowserSettings(this.browser); this.close.emit(); } + switchTimestamp(state?: string) { + if (!state) this.browser.timestamp = undefined; + else if (state === '12') this.browser.timestamp = '12'; + else if (state === '24') this.browser.timestamp = '24'; + this.updateTimestampRadios(); + } + turnOffTimestampRadioButtons() { + this.off = false; + this.twelve = false; + this.twentyFour = false; + } + + updateTimestampRadios() { + this.turnOffTimestampRadioButtons(); + switch (this.browser.timestamp) { + case '12': + this.twelve = true; + this.timestampText = '12 Hour mode'; + break; + case '24': + this.twentyFour = true; + this.timestampText = '24 Hour mode'; + break; + default: + this.off = true; + this.timestampText = 'Disabled'; + break; + } + } + + updateChatlogRange(range: number | undefined) { document.body.classList.add('translucent-modals'); updateRangeIndicator(range, this.game);