1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
|
import React from "react";
import axios from "axios";
import { ChatMessage } from "../database.d";
import ChatReplay from "./ChatReplay";
import { IconChevronDown, IconFilter } from "../icons";
import { useDebounce } from "../hooks/useDebounce";
import { parseChatReplay } from "./parser";
export type ChatReplayPanelProps = {
src: string;
currentTimeSeconds: number;
onChatToggle?: (isVisible: boolean) => any;
};
const ChatReplayPanel = (props: ChatReplayPanelProps) => {
const [replayData, setReplayData] = React.useState<ChatMessage[]>(null);
const [filteredReplayData, setFilteredReplayData] = React.useState<
ChatMessage[]
>(null);
const [downloadProgress, setDownloadProgress] = React.useState(-1);
const [isFilterVisible, setIsFilterVisible] = React.useState(false);
const [chatFilter, setChatFilter] = React.useState("");
const [isChatVisible, setIsChatVisible] = React.useState(false);
const [isErrored, setIsErrored] = React.useState(false);
const refChatScrollDiv = React.useRef<HTMLDivElement>(null);
const activeChatFilter = useDebounce(chatFilter, 250);
const downloadChatData = async () => {
setDownloadProgress(0);
setReplayData(null);
setIsErrored(false);
try {
const data = await axios.get(props.src, {
onDownloadProgress: ({ loaded }) => setDownloadProgress(loaded),
transformResponse: (res) => res,
});
setReplayData(parseChatReplay(data.data));
setIsChatVisible(true);
} catch (ex) {
console.log("[chat] error parsing chat:", ex);
setIsErrored(true);
}
};
React.useEffect(() => {
if (!replayData) return;
if (!activeChatFilter) return setFilteredReplayData(replayData);
setFilteredReplayData(
replayData.filter(
(message) =>
!!message.message &&
message.message.toLowerCase().includes(activeChatFilter.toLowerCase())
)
);
}, [activeChatFilter, replayData]);
/**
* Automatically download chat replay
*/
React.useEffect(() => {
downloadChatData();
}, [props.src]);
React.useEffect(() => {
if (refChatScrollDiv.current)
refChatScrollDiv.current.scrollTo({
top: refChatScrollDiv.current.scrollHeight,
behavior: "smooth",
});
}, [props.currentTimeSeconds]);
React.useEffect(() => {
props.onChatToggle?.(isChatVisible);
}, [isChatVisible]);
if (replayData === null)
return (
<div
className="border border-gray-800 rounded p-4 text-center cursor-pointer"
onClick={() => {
if (isErrored || downloadProgress < 0) downloadChatData();
}}
>
{isErrored ? (
<p>Error loading chat, click to retry</p>
) : downloadProgress < 0 ? (
<>
<p>Chat replay available!</p>
<p>Click to Enable</p>
</>
) : downloadProgress === 0 ? (
<p>Loading chat replay...</p>
) : (
<p>Loaded {(downloadProgress / 1024 / 1024).toFixed(2)}MB</p>
)}
</div>
);
return (
<div className="h-full flex flex-col">
<div className="flex flex-col text-white bg-gray-900">
<div className="flex">
<div className="flex-1 flex items-center px-4">Chat replay</div>
<div>
<button
type="button"
onClick={() => setIsFilterVisible((now) => !now)}
className="px-4 py-2"
>
<IconFilter width="1em" height="1em" />
</button>
<button
type="button"
onClick={() => setIsChatVisible((now) => !now)}
className="text-lg px-4 py-2"
>
<IconChevronDown
width="1em"
height="1em"
style={{ transform: isChatVisible ? "rotate(180deg)" : "" }}
/>
</button>
</div>
</div>
{isFilterVisible && (
<div className="flex flex-col">
<div className="flex">
<input
type="text"
placeholder="Filter text"
className="
w-full rounded px-4 py-1 md:mx-2
bg-gray-800 hover:bg-gray-700 focus:outline-none focus:ring
transition duration-100 z-20
"
value={chatFilter}
onChange={(e) => setChatFilter(e.target.value)}
/>
</div>
</div>
)}
</div>
{isChatVisible && (
<div className="relative flex-1">
<div
className={[
"px-2 border border-gray-800 rounded",
"overflow-y-scroll absolute inset-0",
"transition-all duration-200",
].join(" ")}
style={{
overscrollBehavior: "contain",
}}
ref={refChatScrollDiv}
>
<ChatReplay
currentTimeSeconds={props.currentTimeSeconds}
replayData={filteredReplayData}
/>
</div>
</div>
)}
</div>
);
};
export default ChatReplayPanel;
|