mirror of
https://github.com/Terncode/pixel.horse.git
synced 2026-09-24 13:55:04 +02:00
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 <[email protected]>
This commit is contained in:
committed by
Stubenhocker1399
co-authored by
Stubenhocker1399
parent
cba4e94da2
commit
596fd33126
@@ -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")
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, IndexEntry>();
|
||||
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) ||
|
||||
|
||||
Reference in New Issue
Block a user