mirror of
https://github.com/owncast/owncast.git
synced 2024-11-27 09:45:36 +03:00
Merge pull request #14 from owncast/gw/20210109-chat-singleeupdate
Toggle one chat message at a time
This commit is contained in:
commit
10387c3bb3
4 changed files with 142 additions and 8 deletions
|
@ -1,6 +1,6 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Table, Typography, Tooltip, Button } from "antd";
|
||||
import { CheckCircleFilled, ExclamationCircleFilled, StopOutlined } from "@ant-design/icons";
|
||||
import { CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons";
|
||||
import classNames from 'classnames';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import format from 'date-fns/format'
|
||||
|
@ -8,6 +8,7 @@ import format from 'date-fns/format'
|
|||
import { CHAT_HISTORY, fetchData, FETCH_INTERVAL, UPDATE_CHAT_MESSGAE_VIZ } from "../utils/apis";
|
||||
import { MessageType } from '../types/chat';
|
||||
import { isEmptyObject } from "../utils/format";
|
||||
import MessageVisiblityToggle from "./components/message-visiblity-toggle";
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
|
@ -80,6 +81,11 @@ export default function Chat() {
|
|||
},
|
||||
};
|
||||
|
||||
const updateMessage = message => {
|
||||
const messageIndex = messages.findIndex(m => m.id === message.id);
|
||||
messages.splice(messageIndex, 1, message)
|
||||
setMessages([...messages]);
|
||||
};
|
||||
|
||||
const resetBulkOutcome = () => {
|
||||
outcomeTimeout = setTimeout(() => {
|
||||
|
@ -179,7 +185,13 @@ export default function Chat() {
|
|||
className: 'toggle-col',
|
||||
filters: [{ text: 'Visible messages', value: true }, { text: 'Hidden messages', value: false }],
|
||||
onFilter: (value, record) => record.visible === value,
|
||||
render: visible => visible ? null : <StopOutlined title="This message is hidden" />,
|
||||
render: (visible, record) => (
|
||||
<MessageVisiblityToggle
|
||||
isVisible={visible}
|
||||
message={record}
|
||||
setMessage={updateMessage}
|
||||
/>
|
||||
),
|
||||
width: 30,
|
||||
},
|
||||
];
|
||||
|
@ -235,4 +247,3 @@ export default function Chat() {
|
|||
</div>)
|
||||
}
|
||||
|
||||
|
||||
|
|
83
web/pages/components/message-visiblity-toggle.tsx
Normal file
83
web/pages/components/message-visiblity-toggle.tsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
// Custom component for AntDesign Button that makes an api call, then displays a confirmation icon upon
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { Button, Tooltip } from "antd";
|
||||
import { EyeOutlined, EyeInvisibleOutlined, CheckCircleFilled, ExclamationCircleFilled } from "@ant-design/icons";
|
||||
import { fetchData, UPDATE_CHAT_MESSGAE_VIZ } from "../../utils/apis";
|
||||
import { MessageType } from '../../types/chat';
|
||||
import { OUTCOME_TIMEOUT } from "../chat";
|
||||
import { isEmptyObject } from "../../utils/format";
|
||||
|
||||
interface MessageToggleProps {
|
||||
isVisible: boolean;
|
||||
message: MessageType;
|
||||
setMessage: (message: MessageType) => void,
|
||||
};
|
||||
|
||||
|
||||
export default function MessageVisiblityToggle({ isVisible, message, setMessage }: MessageToggleProps) {
|
||||
if (!message || isEmptyObject(message)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let outcomeTimeout = null;
|
||||
const [outcome, setOutcome] = useState(0);
|
||||
|
||||
const { id: messageId } = message || {};
|
||||
|
||||
const resetOutcome = () => {
|
||||
outcomeTimeout = setTimeout(() => { setOutcome(0)}, OUTCOME_TIMEOUT);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
clearTimeout(outcomeTimeout);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
const updateChatMessage = async () => {
|
||||
clearTimeout(outcomeTimeout);
|
||||
setOutcome(0);
|
||||
const result = await fetchData(UPDATE_CHAT_MESSGAE_VIZ, {
|
||||
auth: true,
|
||||
method: 'POST',
|
||||
data: {
|
||||
visible: !isVisible,
|
||||
idArray: [messageId],
|
||||
},
|
||||
});
|
||||
|
||||
if (result.success && result.message === "changed") {
|
||||
setMessage({ ...message, visible: !isVisible });
|
||||
setOutcome(1);
|
||||
} else {
|
||||
setMessage({ ...message, visible: isVisible });
|
||||
setOutcome(-1);
|
||||
}
|
||||
resetOutcome();
|
||||
}
|
||||
|
||||
|
||||
let outcomeIcon = <CheckCircleFilled style={{ color: 'transparent' }} />;
|
||||
if (outcome) {
|
||||
outcomeIcon = outcome > 0 ?
|
||||
<CheckCircleFilled style={{ color: 'var(--ant-success)' }} /> :
|
||||
<ExclamationCircleFilled style={{ color: 'var(--ant-warning)' }} />;
|
||||
}
|
||||
|
||||
const toolTipMessage = `Click to ${isVisible ? 'hide' : 'show'} this message`;
|
||||
return (
|
||||
<div className={`toggle-switch ${isVisible ? '' : 'hidden'}`}>
|
||||
<span className="outcome-icon">{outcomeIcon}</span>
|
||||
<Tooltip title={toolTipMessage} placement="topRight">
|
||||
<Button
|
||||
shape="circle"
|
||||
size="small"
|
||||
type="text"
|
||||
icon={ isVisible ? <EyeOutlined /> : <EyeInvisibleOutlined /> }
|
||||
onClick={updateChatMessage}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -4,13 +4,17 @@
|
|||
min-width: 20px;
|
||||
}
|
||||
.ant-table-tbody > tr > td {
|
||||
transition: background 0.15s;
|
||||
transition: background-color 0.15s;
|
||||
}
|
||||
.ant-table-row.hidden {
|
||||
.ant-table-cell {
|
||||
color: #444450;
|
||||
color: rgba(0,0,0,.25)
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.ant-table-cell {
|
||||
color: rgba(255,255,255,.25)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.ant-table-cell {
|
||||
font-size: 12px;
|
||||
|
@ -43,16 +47,23 @@
|
|||
.bulk-editor {
|
||||
margin: .5rem 0;
|
||||
padding: .5rem;
|
||||
border: 1px solid #333;
|
||||
border: 1px solid #ccc;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
border-radius: 4px;
|
||||
opacity: .5;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
.label {
|
||||
color: #ccc;
|
||||
color: #000;
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.label {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -79,8 +90,33 @@
|
|||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: flex-end;
|
||||
transition: opacity .15s;
|
||||
|
||||
.outcome-icon {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
&.hidden {
|
||||
opacity: .25;
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.ant-btn {
|
||||
.anticon {
|
||||
opacity: .5;
|
||||
}
|
||||
&:hover {
|
||||
.anticon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ant-btn-text:hover {
|
||||
background-color: rgba(0,0,0,.1)
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.ant-btn-text:hover {
|
||||
background-color: rgba(255,255,255,.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,10 @@ pre {
|
|||
.ant-card {
|
||||
border-radius: .5em;
|
||||
}
|
||||
.ant-btn {
|
||||
transition-duration: .15s;
|
||||
transition-delay: 0s;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
@import "~antd/dist/antd.dark";
|
||||
|
|
Loading…
Reference in a new issue