add twitch chat mode
This commit is contained in:
parent
9d0e832c6d
commit
905006d5d0
8 changed files with 226 additions and 139 deletions
File diff suppressed because one or more lines are too long
79
web/src/components/ContentLog.tsx
Normal file
79
web/src/components/ContentLog.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { InputAdornment, TextField } from "@material-ui/core";
|
||||
import { Search } from "@material-ui/icons";
|
||||
import React, { useContext, useState, CSSProperties, useRef, useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useLog } from "../hooks/useLog";
|
||||
import { store } from "../store";
|
||||
import { LogLine } from "./LogLine";
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
|
||||
const ContentLogContainer = styled.ul`
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
top: -52px;
|
||||
width: 320px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.logLine {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list {
|
||||
scrollbar-color: dark;
|
||||
}
|
||||
`;
|
||||
|
||||
export function ContentLog({ year, month }: { year: string, month: string }) {
|
||||
const { state, setState } = useContext(store);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const logs = useLog(state.currentChannel ?? "", state.currentUsername ?? "", year, month)
|
||||
.filter(log => log.text.toLowerCase().includes(searchText.toLowerCase()));
|
||||
|
||||
const Row = ({ index, style }: { index: number, style: CSSProperties }) => (
|
||||
<div style={style}><LogLine key={logs[index].id ? logs[index].id : index} message={logs[index]} /></div>
|
||||
);
|
||||
|
||||
const search = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setState({ ...state, activeSearchField: search.current })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setState({ ...state, activeSearchField: search.current })
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
||||
return <ContentLogContainer onMouseEnter={handleMouseEnter}>
|
||||
<TextField
|
||||
className="search"
|
||||
label="Search"
|
||||
inputRef={search}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
size="small"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<List
|
||||
className="list"
|
||||
height={600}
|
||||
itemCount={logs.length}
|
||||
itemSize={20}
|
||||
width={"100%"}
|
||||
>
|
||||
{Row}
|
||||
</List>
|
||||
</ContentLogContainer>
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
import { Button, InputAdornment, TextField } from "@material-ui/core";
|
||||
import React, { CSSProperties, useContext, useEffect, useRef, useState } from "react";
|
||||
import { Button } from "@material-ui/core";
|
||||
import React, { useContext, useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useLog } from "../hooks/useLog";
|
||||
import { Txt } from "../icons/Txt";
|
||||
import { store } from "../store";
|
||||
import { LogLine } from "./LogLine";
|
||||
import { FixedSizeList as List } from 'react-window';
|
||||
import { Search } from "@material-ui/icons";
|
||||
import { ContentLog } from "./ContentLog";
|
||||
import { TwitchChatContentLog } from "./TwitchChatLogContainer";
|
||||
|
||||
const LogContainer = styled.div`
|
||||
position: relative;
|
||||
|
@ -41,99 +39,11 @@ export function Log({ year, month, initialLoad = false }: { year: string, month:
|
|||
|
||||
return <LogContainer>
|
||||
<a className="txt" target="__blank" href={`${state.apiBaseUrl}/channel/${state.currentChannel}/user/${state.currentUsername}/${year}/${month}?reverse`} rel="noopener noreferrer"><Txt /></a>
|
||||
{/* <ContentLog year={year} month={month} /> */}
|
||||
{state.settings.performanceMode.value && <PerformanceContentLog year={year} month={month} />}
|
||||
{!state.settings.performanceMode.value && <ContentLog year={year} month={month} />}
|
||||
{!state.settings.twitchChatMode.value && <ContentLog year={year} month={month} />}
|
||||
{state.settings.twitchChatMode.value && <TwitchChatContentLog year={year} month={month} />}
|
||||
</LogContainer>
|
||||
}
|
||||
|
||||
const PerformanceContentLogContainer = styled.ul`
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
.search {
|
||||
position: absolute;
|
||||
top: -52px;
|
||||
width: 320px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.logLine {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.list {
|
||||
scrollbar-color: dark;
|
||||
}
|
||||
`;
|
||||
|
||||
function PerformanceContentLog({ year, month }: { year: string, month: string }) {
|
||||
const { state, setState } = useContext(store);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const logs = useLog(state.currentChannel ?? "", state.currentUsername ?? "", year, month)
|
||||
.filter(log => log.text.toLowerCase().includes(searchText.toLowerCase()));
|
||||
|
||||
const Row = ({ index, style }: { index: number, style: CSSProperties }) => (
|
||||
<div style={style}><LogLine key={logs[index].id ? logs[index].id : index} message={logs[index]} /></div>
|
||||
);
|
||||
|
||||
const search = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleMouseEnter = () => {
|
||||
setState({ ...state, activeSearchField: search.current })
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setState({ ...state, activeSearchField: search.current })
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
|
||||
return <PerformanceContentLogContainer onMouseEnter={handleMouseEnter}>
|
||||
<TextField
|
||||
className="search"
|
||||
label="Search"
|
||||
inputRef={search}
|
||||
onChange={e => setSearchText(e.target.value)}
|
||||
size="small"
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Search />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<List
|
||||
className="list"
|
||||
height={600}
|
||||
itemCount={logs.length}
|
||||
itemSize={20}
|
||||
width={"100%"}
|
||||
>
|
||||
{Row}
|
||||
</List>
|
||||
</PerformanceContentLogContainer>
|
||||
}
|
||||
|
||||
const ContentLogContainer = styled.ul`
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
`;
|
||||
|
||||
function ContentLog({ year, month }: { year: string, month: string }) {
|
||||
const { state } = useContext(store);
|
||||
|
||||
const logs = useLog(state.currentChannel ?? "", state.currentUsername ?? "", year, month)
|
||||
|
||||
return <ContentLogContainer>
|
||||
{logs.map((log, index) => <LogLine key={log.id ? log.id : index} message={log} />)}
|
||||
</ContentLogContainer>
|
||||
}
|
||||
|
||||
const LoadableLogContainer = styled.div`
|
||||
|
||||
`;
|
||||
|
|
|
@ -42,6 +42,7 @@ export function Message({ message, thirdPartyEmotes }: { message: LogMessage, th
|
|||
if (emote.startIndex === x) {
|
||||
replaced = true;
|
||||
renderMessage.push(<Emote
|
||||
className="emote"
|
||||
key={x}
|
||||
alt={emote.code}
|
||||
src={`https://static-cdn.jtvnw.net/emoticons/v1/${emote.id}/1.0`}
|
||||
|
@ -62,6 +63,7 @@ export function Message({ message, thirdPartyEmotes }: { message: LogMessage, th
|
|||
for (const emote of thirdPartyEmotes) {
|
||||
if (buffer.trim() === emote.code) {
|
||||
renderMessage.push(<Emote
|
||||
className="emote"
|
||||
key={x}
|
||||
alt={emote.code}
|
||||
src={emote.urls.small}
|
||||
|
|
22
web/src/components/TwitchChatLogContainer.tsx
Normal file
22
web/src/components/TwitchChatLogContainer.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React, { useContext } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useLog } from "../hooks/useLog";
|
||||
import { store } from "../store";
|
||||
import { TwitchChatLogLine } from "./TwitchChatLogLine";
|
||||
|
||||
const ContentLogContainer = styled.ul`
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 340px;
|
||||
`;
|
||||
|
||||
export function TwitchChatContentLog({ year, month }: { year: string, month: string }) {
|
||||
const { state } = useContext(store);
|
||||
|
||||
const logs = useLog(state.currentChannel ?? "", state.currentUsername ?? "", year, month)
|
||||
|
||||
return <ContentLogContainer>
|
||||
{logs.map((log, index) => <TwitchChatLogLine key={log.id ? log.id : index} message={log} />)}
|
||||
</ContentLogContainer>
|
||||
}
|
74
web/src/components/TwitchChatLogLine.tsx
Normal file
74
web/src/components/TwitchChatLogLine.tsx
Normal file
|
@ -0,0 +1,74 @@
|
|||
import dayjs from "dayjs";
|
||||
import React, { useContext } from "react";
|
||||
import styled from "styled-components";
|
||||
import { useThirdPartyEmotes } from "../hooks/useThirdPartyEmotes";
|
||||
import { store } from "../store";
|
||||
import { LogMessage } from "../types/log";
|
||||
import { Message } from "./Message";
|
||||
import { User } from "./User";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.tz.guess()
|
||||
|
||||
const TwitchChatLogLineContainer = styled.li`
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1px;
|
||||
padding: 5px 20px;
|
||||
|
||||
.timestamp {
|
||||
color: var(--text-dark);
|
||||
user-select: none;
|
||||
font-family: monospace;
|
||||
white-space: nowrap;
|
||||
margin-right: 5px;
|
||||
line-height: 1.1rem;
|
||||
}
|
||||
|
||||
.user {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
user-select: none;
|
||||
font-weight: bold;
|
||||
line-height: 1.1rem;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: inline;
|
||||
line-height: 20px;
|
||||
|
||||
.emote {
|
||||
max-height: 28px;
|
||||
margin: 0 2px;
|
||||
margin-bottom: -10px;
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function TwitchChatLogLine({ message }: { message: LogMessage }) {
|
||||
const { state } = useContext(store);
|
||||
|
||||
if (state.settings.showEmotes.value) {
|
||||
return <LogLineWithEmotes message={message} />;
|
||||
}
|
||||
|
||||
return <TwitchChatLogLineContainer className="logLine">
|
||||
{state.settings.showTimestamp.value && <span className="timestamp">{dayjs(message.timestamp).format("YYYY-MM-DD HH:mm:ss")}</span>}
|
||||
{state.settings.showName.value && <User displayName={message.displayName} color={message.tags["color"]} />}
|
||||
<Message message={message} thirdPartyEmotes={[]} />
|
||||
</TwitchChatLogLineContainer>
|
||||
}
|
||||
|
||||
function LogLineWithEmotes({ message }: { message: LogMessage }) {
|
||||
const thirdPartyEmotes = useThirdPartyEmotes(message.tags["room-id"])
|
||||
const { state } = useContext(store);
|
||||
|
||||
return <TwitchChatLogLineContainer className="logLine">
|
||||
{state.settings.showTimestamp.value && <span className="timestamp">{dayjs(message.timestamp).format("YYYY-MM-DD HH:mm:ss")}</span>}
|
||||
{state.settings.showName.value && <User displayName={message.displayName} color={message.tags["color"]} />}
|
||||
<Message message={message} thirdPartyEmotes={thirdPartyEmotes} />
|
||||
</TwitchChatLogLineContainer>
|
||||
}
|
|
@ -68,7 +68,7 @@ function parseEmotes(messageText: string, emotes: string | undefined): Array<Emo
|
|||
id,
|
||||
startIndex: startIndex,
|
||||
endIndex: endIndex,
|
||||
code: messageText.substr(startIndex, startIndex + endIndex)
|
||||
code: messageText.substr(startIndex, endIndex - startIndex)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ export interface Settings {
|
|||
showEmotes: Setting,
|
||||
showName: Setting,
|
||||
showTimestamp: Setting,
|
||||
performanceMode: Setting,
|
||||
twitchChatMode: Setting,
|
||||
}
|
||||
|
||||
export interface Setting {
|
||||
|
@ -44,9 +44,9 @@ const defaultContext = {
|
|||
displayName: "Show Timestamp",
|
||||
value: true,
|
||||
},
|
||||
performanceMode: {
|
||||
displayName: "Performace Mode",
|
||||
value: true,
|
||||
twitchChatMode: {
|
||||
displayName: "Twitch Chat Mode",
|
||||
value: false,
|
||||
}
|
||||
},
|
||||
currentChannel: url.searchParams.get("channel"),
|
||||
|
|
Loading…
Reference in a new issue