Merge pull request #85 from Stubenhocker1399/improved-chatlog

Add chat log colors, timestamp, filter & emoji box
This commit is contained in:
Eliot Partridge
2019-09-11 21:14:39 -05:00
committed by GitHub
10 changed files with 407 additions and 10 deletions
+40
View File
@@ -157,6 +157,12 @@ export interface HSVA {
a: number; a: number;
} }
export interface HSL {
h: number;
s: number;
l: number;
}
export interface RGB { export interface RGB {
r: number; r: number;
g: number; g: number;
@@ -493,3 +499,37 @@ export function h2rgb(h: number): RGB {
b: Math.round(b * 255) 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}%)`;
}
+1
View File
@@ -683,6 +683,7 @@ export interface BrowserSettings {
scale?: number; scale?: number;
walkByDefault?: boolean; walkByDefault?: boolean;
brightNight?: boolean; brightNight?: boolean;
timestamp?: '24' | '12';
} }
export interface EntityTypeName { export interface EntityTypeName {
@@ -5,6 +5,14 @@
aria-label="Chat message") aria-label="Chat message")
.chat-box-type(#typeBox (click)="toggleChatType()") .chat-box-type(#typeBox (click)="toggleChatType()")
| #[span(#typePrefix)][#[span.chat-box-type-name(#typeName)]]: | #[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)="onMouseEnterEmojiButton($event)")
button.game-button.chat-send-button((click)="send($event)" title="Send message" aria-label="Send message") button.game-button.chat-send-button((click)="send($event)" title="Send message" aria-label="Send message")
fa-icon([icon]="sendIcon") fa-icon([icon]="sendIcon")
@@ -34,7 +34,7 @@
z-index: 1; z-index: 1;
padding: 8px; padding: 8px;
font-size: 14px; font-size: 14px;
padding-right: 35px; padding-right: 65px;
padding-left: 90px; padding-left: 90px;
border-radius: $border-radius-base; border-radius: $border-radius-base;
outline: none; outline: none;
@@ -86,3 +86,71 @@
white-space: nowrap; white-space: nowrap;
vertical-align: bottom; 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;
}
&::-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;
}
+26 -1
View File
@@ -9,10 +9,11 @@ import { faComment, faAngleDoubleRight } from '../../../client/icons';
import { isInParty } from '../../../client/partyUtils'; import { isInParty } from '../../../client/partyUtils';
import { handleActionCommand } from '../../../client/playerActions'; import { handleActionCommand } from '../../../client/playerActions';
import { hasHeadAnimation } from '../../../common/pony'; 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 { replaceNodes } from '../../../client/htmlUtils';
import { invalidEnumReturn } from '../../../common/utils'; import { invalidEnumReturn } from '../../../common/utils';
import { findMatchingEntityNames, findEntityOrMockByAnyMeans, findBestEntityByName } from '../../../client/handlers'; import { findMatchingEntityNames, findEntityOrMockByAnyMeans, findBestEntityByName } from '../../../client/handlers';
import { sample } from 'lodash';
const chatTypeNames: string[] = []; const chatTypeNames: string[] = [];
const chatTypeClasses: string[] = []; const chatTypeClasses: string[] = [];
@@ -45,6 +46,8 @@ export class ChatBox implements AfterViewInit, OnDestroy {
readonly maxSayLength = SAY_MAX_LENGTH; readonly maxSayLength = SAY_MAX_LENGTH;
readonly commentIcon = faComment; readonly commentIcon = faComment;
readonly sendIcon = faAngleDoubleRight; readonly sendIcon = faAngleDoubleRight;
readonly emotes = emojis;
emojiBoxState = 'none';
@ViewChild('inputElement', { static: true }) inputElement!: ElementRef; @ViewChild('inputElement', { static: true }) inputElement!: ElementRef;
@ViewChild('typeBox', { static: true }) typeBox!: ElementRef; @ViewChild('typeBox', { static: true }) typeBox!: ElementRef;
@ViewChild('typePrefix', { static: true }) typePrefix!: ElementRef; @ViewChild('typePrefix', { static: true }) typePrefix!: ElementRef;
@@ -54,6 +57,7 @@ export class ChatBox implements AfterViewInit, OnDestroy {
isOpen = false; isOpen = false;
message: string | undefined = ''; message: string | undefined = '';
chatType = ChatType.Say; chatType = ChatType.Say;
btnEmoji = emojis[0].names[0];
private pasted = false; private pasted = false;
private lastMessages: string[] = []; private lastMessages: string[] = [];
private state: AutocompleteState = {}; private state: AutocompleteState = {};
@@ -92,6 +96,21 @@ export class ChatBox implements AfterViewInit, OnDestroy {
ngOnDestroy() { ngOnDestroy() {
this.subscriptions.forEach(s => s.unsubscribe()); 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) { send(_event: Event | undefined) {
let chatType = this.chatType; let chatType = this.chatType;
let message = replaceEmojis(cleanMessage(this.message || '')).substr(0, SAY_MAX_LENGTH); let message = replaceEmojis(cleanMessage(this.message || '')).substr(0, SAY_MAX_LENGTH);
@@ -259,6 +278,7 @@ export class ChatBox implements AfterViewInit, OnDestroy {
} }
private close() { private close() {
if (this.isOpen) { if (this.isOpen) {
this.emojiBoxState = 'none';
this.input.blur(); this.input.blur();
this.isOpen = false; this.isOpen = false;
this.chatBox.nativeElement.hidden = true; this.chatBox.nativeElement.hidden = true;
@@ -273,6 +293,11 @@ export class ChatBox implements AfterViewInit, OnDestroy {
this.open(); this.open();
} }
} }
onMouseEnterEmojiButton(event: MouseEvent) {
if (!event.target) return;
const emoji = sample(emojis)!;
this.btnEmoji = emoji.names[0];
}
toggleChatType() { toggleChatType() {
const chatTypes = getChatTypes(this.game); const chatTypes = getChatTypes(this.game);
this.chatType = chatTypes[(chatTypes.indexOf(this.chatType) + 1) % chatTypes.length]; this.chatType = chatTypes[(chatTypes.indexOf(this.chatType) + 1) % chatTypes.length];
@@ -13,6 +13,9 @@
| Party | Party
a.link-plain.chat-log-tab(#whisperTab tabindex (click)="switchTab('whisper')") a.link-plain.chat-log-tab(#whisperTab tabindex (click)="switchTab('whisper')")
| Whisper | Whisper
a.link-plain.chat-log-tab.chat-log-filter-tab([style.background-color]="filterColor" (click)="focus()")
fa-icon.search-icon([icon]="searchIcon")
input.chat-log-filter(#filterInput type="text" (keyup.enter)="(unFocus())" (keyup.esc)="(unFocus())" [placeholder]="'Filter'" (keyup)="filterChat()")
.chat-log-scroll-outer() .chat-log-scroll-outer()
.chat-log-scroll-inner(#scroll) .chat-log-scroll-inner(#scroll)
.chat-log-scroll-inner-inner(#lines) .chat-log-scroll-inner-inner(#lines)
@@ -84,6 +84,7 @@ $resizer-offset: $resizer-size / 2;
left: 30px; left: 30px;
display: flex; display: flex;
user-select: none; user-select: none;
text-shadow: 0px 0px 4px #000000;
} }
.chat-log-tab { .chat-log-tab {
@@ -157,3 +158,64 @@ $resizer-offset: $resizer-size / 2;
transform: rotate(45deg); transform: rotate(45deg);
color: rgba(255, 255, 255, 0.2); color: rgba(255, 255, 255, 0.2);
} }
.search-icon {
position: relative;
float: left;
width: 0;
bottom: -1px;
left: 0;
z-index: 2;
}
.chat-log-filter-tab {
width: 100px;
}
.chat-log-filter {
padding-bottom: 5px;
border: none;
background-color: rgba(0, 0, 0, 0);
border: none;
outline: none;
color: white;
padding-left: 20px;
width: 100%;
}
.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-tab {
position: absolute;
left: 0;
bottom: 35px;
width: 100%;
width: calc(100% - 10px);
border-radius: 2px;
}
}
+182 -7
View File
@@ -8,9 +8,12 @@ import { MessageType, isPartyMessage, ChatMessage, Pony, FakeEntity, isWhisper,
import { SettingsService } from '../../services/settingsService'; import { SettingsService } from '../../services/settingsService';
import { AgDragEvent } from '../directives/agDrag'; import { AgDragEvent } from '../directives/agDrag';
import { element, textNode, removeAllNodes, replaceNodes } from '../../../client/htmlUtils'; 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 { faCaretUp, faArrowDown, faSearch } from '../../../client/icons';
import { sampleMessages } from '../../../common/debugData'; 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 { interface IndexEntryUser {
id: number; id: number;
@@ -26,6 +29,8 @@ interface ChatLogLineDOM {
entry: ChatLogMessage; entry: ChatLogMessage;
root: HTMLElement; root: HTMLElement;
label: HTMLElement; label: HTMLElement;
time: HTMLElement;
timeContent: HTMLElement;
labelText: Text; labelText: Text;
name: HTMLElement; name: HTMLElement;
nameContent: HTMLElement; nameContent: HTMLElement;
@@ -98,6 +103,11 @@ export function createChatLogLineDOM(clickLabel: ClickHandler, clickName: ClickH
line.root = element('div', 'chat-line', [ line.root = element('div', 'chat-line', [
element('span', 'chat-line-lead'), element('span', 'chat-line-lead'),
line.time = element('span', 'chat-line-name', [
line.timeContent = element(
'span', 'chat-line-time-content', [textNode('')], undefined),
line.index = element('span', 'chat-line-time-index', [line.indexText = textNode('')]),
]),
line.label = element( line.label = element(
'span', 'chat-line-label mr-1', [line.labelText = textNode('')], undefined, { click: () => clickLabel(line.entry) }), 'span', 'chat-line-label mr-1', [line.labelText = textNode('')], undefined, { click: () => clickLabel(line.entry) }),
line.prefixText = textNode(''), line.prefixText = textNode(''),
@@ -115,7 +125,7 @@ export function createChatLogLineDOM(clickLabel: ClickHandler, clickName: ClickH
return line; 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 { classes, label, message, prefix, suffix } = entry;
const hasSpace = message.indexOf(' ') !== -1; const hasSpace = message.indexOf(' ') !== -1;
@@ -125,12 +135,28 @@ export function updateChatLogLine(line: ChatLogLineDOM, entry: ChatLogMessage) {
line.labelText.nodeValue = label ? `[${label}]` : ''; line.labelText.nodeValue = label ? `[${label}]` : '';
updateChatLogName(line, entry); updateChatLogName(line, entry);
updateTime(line, hourMode);
line.prefixText.nodeValue = prefix || ''; line.prefixText.nodeValue = prefix || '';
line.suffixText.nodeValue = suffix ? ` ${suffix}: ` : ': '; line.suffixText.nodeValue = suffix ? ` ${suffix}: ` : ': ';
replaceNodes(line.message, message); replaceNodes(line.message, message);
} }
function updateTime(line: ChatLogLineDOM, hourMode?: '12' | '24') {
line.time.style.display = 'inline';
if (!hourMode) return;
if (hourMode === '24')
replaceNodes(line.timeContent, `[${moment().format('HH:mm:ss')}] `);
if (hourMode === '12')
replaceNodes(line.timeContent, `[${moment().format('LTS')}] `);
}
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];
}
function updateChatLogName(line: ChatLogLineDOM, { name, index }: ChatLogMessage) { function updateChatLogName(line: ChatLogLineDOM, { name, index }: ChatLogMessage) {
if (name) { if (name) {
line.name.style.display = 'inline'; line.name.style.display = 'inline';
@@ -183,12 +209,14 @@ function findUserIndex(users: IndexEntryUser[], id: number, crc: number | undefi
export class ChatLog implements AfterViewInit, OnDestroy, DoCheck { export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
readonly toBottomIcon = faArrowDown; readonly toBottomIcon = faArrowDown;
readonly resizeIcon = faCaretUp; readonly resizeIcon = faCaretUp;
readonly searchIcon = faSearch;
@ViewChild('chatLog', { static: true }) chatLog!: ElementRef; @ViewChild('chatLog', { static: true }) chatLog!: ElementRef;
@ViewChild('scroll', { static: true }) scroll!: ElementRef; @ViewChild('scroll', { static: true }) scroll!: ElementRef;
@ViewChild('lines', { static: true }) lines!: ElementRef; @ViewChild('lines', { static: true }) lines!: ElementRef;
@ViewChild('localTab', { static: true }) localTab!: ElementRef; @ViewChild('localTab', { static: true }) localTab!: ElementRef;
@ViewChild('partyTab', { static: true }) partyTab!: ElementRef; @ViewChild('partyTab', { static: true }) partyTab!: ElementRef;
@ViewChild('whisperTab', { static: true }) whisperTab!: ElementRef; @ViewChild('whisperTab', { static: true }) whisperTab!: ElementRef;
@ViewChild('filterInput', { static: true }) filterInput!: ElementRef;
@ViewChild('toggleButton', { static: true }) toggleButton!: ElementRef; @ViewChild('toggleButton', { static: true }) toggleButton!: ElementRef;
@ViewChild('count', { static: true }) countElement!: ElementRef; @ViewChild('count', { static: true }) countElement!: ElementRef;
@ViewChild('content', { static: true }) contentElement!: ElementRef; @ViewChild('content', { static: true }) contentElement!: ElementRef;
@@ -199,6 +227,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
local: ChatLogMessage[] = []; local: ChatLogMessage[] = [];
party: ChatLogMessage[] = []; party: ChatLogMessage[] = [];
whisper: ChatLogMessage[] = []; whisper: ChatLogMessage[] = [];
filterColor = this.inactiveBg;
unread = 0; unread = 0;
private subscriptions: Subscription[] = []; private subscriptions: Subscription[] = [];
private startX = 0; private startX = 0;
@@ -210,6 +239,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
private indexes = new Map<string, IndexEntry>(); private indexes = new Map<string, IndexEntry>();
private messageCounter = 0; private messageCounter = 0;
private lastOpacity = 0; private lastOpacity = 0;
private autoClear?: NodeJS.Timeout;
private autoUnfocus?: NodeJS.Timeout;
constructor( constructor(
private game: PonyTownGame, private game: PonyTownGame,
private settingsService: SettingsService, private settingsService: SettingsService,
@@ -277,6 +308,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
} }
} }
ngAfterViewInit() { ngAfterViewInit() {
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
canvas.addEventListener('click', () => this.unFocus());
this.game.findEntityFromChatLog = this.findEntityFromMessages; this.game.findEntityFromChatLog = this.findEntityFromMessages;
this.game.findEntityFromChatLogByName = this.findEntityFromMessagesByName; this.game.findEntityFromChatLogByName = this.findEntityFromMessagesByName;
@@ -414,6 +447,130 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
return index; return index;
} }
filterChat() {
this.clearTimeOutAutoClear();
this.clearTimeOutAutoUnfocus();
let value = this.filterInput.nativeElement.value;
if (!value) {
this.clearTimeOutAutoClear();
this.unFocus();
this.filterChatLogLines('', false);
return;
} else {
this.filterColor = this.bg;
}
this.autoClear = setTimeout(() => {
this.filterInput.nativeElement.value = '';
this.filterChat();
this.unFocus();
}, SECOND * 30);
this.autoUnfocus = setTimeout(() => {
this.unFocus();
}, SECOND * 3);
let toLowerCase = false;
this.filterInput.nativeElement.style.color = '';
if (value.startsWith('#')) {
value = value.slice(1);
} else if (value.startsWith('/')) {
value = value.slice(1);
try {
const regExp = new RegExp(value);
this.filterChatLogLines(regExp, toLowerCase);
} catch (err) {// If the user inputs invalid regExp we just notify him by changing text color to red
if (DEVELOPMENT) console.error(err);
this.filterInput.nativeElement.style.color = '#ff6666';
}
return;
} else {
value = value.toLowerCase();
toLowerCase = true;
}
this.filterChatLogLines(value, toLowerCase);
}
filterChatLogLines(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) {
lines[i].hidden = !textContent.toLowerCase().includes(content);
} else {
lines[i].hidden = !textContent.includes(content);
}
} else {
lines[i].hidden = !textContent.match(content);
}
}
}
}
unhideAllLines() {
const lines = this.linesElement.getElementsByTagName('div');
for (let i = 0; i < lines.length; i++) {
lines[i].hidden = false;
}
}
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 {
hsl.l = 40;
if (hsl.s > 11) hsl.s = 0;
}
}
return hsl;
}
private getCharacterColors(id: number | undefined) {
if (!id) return;
const entity = findEntityById(this.game.map, id) as Pony;
if (!entity || !entity.palettePonyInfo) return;
let colors = [];
let { body, mane } = entity.palettePonyInfo;
if (body && body.palette && body.palette.colors[1]) {
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]) {
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]));
}
return colors;
}
clearTimeOutAutoClear() {
if (this.autoClear) clearTimeout(this.autoClear);
this.autoClear = undefined;
this.filterColor = this.inactiveBg;
}
clearTimeOutAutoUnfocus() {
if (this.autoUnfocus) clearTimeout(this.autoUnfocus);
this.autoUnfocus = undefined;
}
focus() {
this.clearTimeOutAutoUnfocus();
if (this.filterInput.nativeElement)
this.filterInput.nativeElement.focus();
}
unFocus() {
this.clearTimeOutAutoUnfocus();
if (this.filterInput.nativeElement)
this.filterInput.nativeElement.blur();
}
addMessage(message: ChatMessage) { addMessage(message: ChatMessage) {
if (message.name && message.message) { if (message.name && message.message) {
const entry = this.createEntry(message); const entry = this.createEntry(message);
@@ -422,14 +579,17 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
const open = this.open; const open = this.open;
const scrolledToEnd = open ? this.scrolledToEnd : false; const scrolledToEnd = open ? this.scrolledToEnd : false;
const tab = this.activeTab; const tab = this.activeTab;
const colors = this.getCharacterColors(message.id);
this.addEntryToList(this.local, GENERAL_CHAT_LIMIT, open && tab === 'local', entry); this.addEntryToList(this.local, GENERAL_CHAT_LIMIT, open && tab === 'local', entry);
setNameColors(entry.dom, colors);
if (party || whisper) { if (party || whisper) {
const partyEntry = { ...entry }; const partyEntry = { ...entry };
partyEntry.dom = undefined; partyEntry.dom = undefined;
partyEntry.label = whisper ? partyEntry.label : undefined; partyEntry.label = whisper ? partyEntry.label : undefined;
this.addEntryToList(this.party, PARTY_CHAT_LIMIT, open && tab === 'party', partyEntry); this.addEntryToList(this.party, PARTY_CHAT_LIMIT, open && tab === 'party', partyEntry);
setNameColors(partyEntry.dom, colors);
} }
if (whisper) { if (whisper) {
@@ -437,6 +597,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
whisperEntry.dom = undefined; whisperEntry.dom = undefined;
whisperEntry.label = undefined; whisperEntry.label = undefined;
this.addEntryToList(this.whisper, WHISPER_CHAT_LIMIT, open && tab === 'whisper', whisperEntry); this.addEntryToList(this.whisper, WHISPER_CHAT_LIMIT, open && tab === 'whisper', whisperEntry);
setNameColors(whisperEntry.dom, colors);
} }
if (message.type === MessageType.Whisper && !this.open) { if (message.type === MessageType.Whisper && !this.open) {
@@ -447,6 +608,15 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
this.scrollToEnd(); 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++; this.messageCounter++;
} }
} }
@@ -470,7 +640,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
if (isOpen) { if (isOpen) {
entry.dom = removedDom || createChatLogLineDOM(this.clickLabel, this.clickNameHandler); 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); this.linesElement.appendChild(entry.dom.root);
} }
} }
@@ -486,6 +656,7 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
this.regenerateList(); this.regenerateList();
this.scrollToEnd(); this.scrollToEnd();
this.updateTabs(); this.updateTabs();
this.filterChat();
} }
} }
private updateTabs() { private updateTabs() {
@@ -496,10 +667,12 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
private setActiveTab(tab: HTMLElement, active: boolean) { private setActiveTab(tab: HTMLElement, active: boolean) {
if (active) { if (active) {
tab.classList.add('active'); tab.classList.add('active');
tab.classList.remove('unread');
tab.style.backgroundColor = this.bg; tab.style.backgroundColor = this.bg;
} else { } else {
tab.classList.remove('active'); tab.classList.remove('active');
tab.style.backgroundColor = this.inactiveBg; if (!tab.classList.contains('unread'))
tab.style.backgroundColor = this.inactiveBg;
} }
} }
scrollToEnd() { scrollToEnd() {
@@ -531,7 +704,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
this.messages.forEach(entry => { this.messages.forEach(entry => {
if (!entry.dom) { if (!entry.dom) {
entry.dom = createChatLogLineDOM(this.clickLabel, this.clickNameHandler); entry.dom = createChatLogLineDOM(this.clickLabel, this.clickNameHandler);
updateChatLogLine(entry.dom, entry); setNameColors(entry.dom, this.getCharacterColors(entry.entityId));
updateChatLogLine(entry.dom, entry, this.settings.timestamp);
} }
lines.appendChild(entry.dom.root); lines.appendChild(entry.dom.root);
@@ -548,7 +722,8 @@ export class ChatLog implements AfterViewInit, OnDestroy, DoCheck {
} }
if (resizeX) { if (resizeX) {
this.settings.chatlogWidth = clamp(x - this.startX, 200, 2000); const filterBoxWidth = 80;
this.settings.chatlogWidth = clamp(x - this.startX, 200 + filterBoxWidth, 2000);
} }
if (resizeY) { if (resizeY) {
@@ -71,6 +71,16 @@
[(checked)]="account.ignoreNonFriendWhispers" [(checked)]="account.ignoreNonFriendWhispers"
help="Don't show any whispers from players who are not on your friends list") help="Don't show any whispers from players who are not on your friends list")
| Only allow whispers from friends | Only allow whispers from friends
.form-group
.form-group
label#chatlog-opacity-label Timestamp
div
.btn-group.mb-2(btnCheckbox)
button.btn.btn-outline-secondary.d-sm-block([class.active]='!browser.timestamp' (click)='switchTimestamp()') None
button.btn.btn-outline-secondary.d-sm-block([class.active]='browser.timestamp === "12"' (click)='switchTimestamp("12")') 12-hour format
button.btn.btn-outline-secondary.d-sm-block([class.active]='browser.timestamp === "24"' (click)='switchTimestamp("24")') 24-hour format
small.form-text.text-muted.mt-0 Displays a timestamp in front of chatlog messages
.form-group .form-group
label#chatlog-opacity-label Chatlog background opacity label#chatlog-opacity-label Chatlog background opacity
div div
@@ -100,6 +100,11 @@ export class SettingsModal implements OnInit, OnDestroy {
this.settingsService.saveBrowserSettings(this.browser); this.settingsService.saveBrowserSettings(this.browser);
this.close.emit(); 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';
}
updateChatlogRange(range: number | undefined) { updateChatlogRange(range: number | undefined) {
document.body.classList.add('translucent-modals'); document.body.classList.add('translucent-modals');
updateRangeIndicator(range, this.game); updateRangeIndicator(range, this.game);