add twitch chat mode

This commit is contained in:
gempir 2020-11-14 11:05:25 +01:00
parent 9d0e832c6d
commit 905006d5d0
8 changed files with 226 additions and 139 deletions

File diff suppressed because one or more lines are too long

View 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>
}

View file

@ -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`
`;

View file

@ -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}

View 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>
}

View 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>
}

View file

@ -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)
});
}
}

View file

@ -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"),