+
Chat Messages
+
Manage the messages from viewers that show up on your stream.
+
+ Check multiple messages to change their visibility to:
+
+
+
+
+
!record.visible ? 'hidden' : ''}
+ dataSource={messages}
+ columns={chatColumns}
+ rowKey={(row) => row.id}
+ rowSelection={rowSelection}
+ />
+ )
+}
+
+
diff --git a/web/pages/components/chart.tsx b/web/pages/components/chart.tsx
index 019411d1f..e6cbeab3b 100644
--- a/web/pages/components/chart.tsx
+++ b/web/pages/components/chart.tsx
@@ -1,7 +1,7 @@
import { LineChart } from 'react-chartkick'
-import styles from '../../styles/styles.module.scss';
import 'chart.js';
-import format from 'date-fns/format'
+import format from 'date-fns/format';
+import styles from '../../styles/styles.module.scss';
interface TimedValue {
time: Date;
diff --git a/web/pages/components/main-layout.tsx b/web/pages/components/main-layout.tsx
index 1139d679c..1e9aae6a8 100644
--- a/web/pages/components/main-layout.tsx
+++ b/web/pages/components/main-layout.tsx
@@ -13,7 +13,8 @@ import {
ToolOutlined,
PlayCircleFilled,
MinusSquareFilled,
- QuestionCircleOutlined
+ QuestionCircleOutlined,
+ MessageOutlined
} from '@ant-design/icons';
import classNames from 'classnames';
import { upgradeVersionAvailable } from "../../utils/apis";
@@ -108,6 +109,14 @@ export default function MainLayout(props) {
Viewers
+ }
+ title="Chat utilities"
+ >
+ Chat
+
+
formatDistanceToNow(new Date(time)),
+ sorter: (a, b) => new Date(a.connectedAt).getTime() - new Date(b.connectedAt).getTime(),
+ sortDirections: ["descend", "ascend"] as SortOrder[],
},
{
title: "User Agent",
diff --git a/web/styles/chat.scss b/web/styles/chat.scss
new file mode 100644
index 000000000..57d1b9cc3
--- /dev/null
+++ b/web/styles/chat.scss
@@ -0,0 +1,83 @@
+.chat-messages {
+ .ant-table-small .ant-table-selection-column {
+ width: 20px;
+ min-width: 20px;
+ }
+ .ant-table-tbody > tr > td {
+ transition: background 0.15s;
+ }
+ .ant-table-row.hidden {
+ .ant-table-cell {
+ color: #444450;
+ }
+
+ }
+ .ant-table-cell {
+ font-size: 12px;
+
+ &.name-col {
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+ &.toggle-col {
+ label {
+ font-size: 11px;
+ }
+ }
+
+ .message-contents {
+ overflow: auto;
+ max-height: 200px;
+ img {
+ position: relative;
+ margin-top: -5px;
+ width: 3rem;
+ padding: 0.25rem;
+ }
+ }
+ }
+
+ .bulk-editor {
+ margin: .5rem 0;
+ padding: .5rem;
+ border: 1px solid #333;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: flex-end;
+ border-radius: 4px;
+
+ &.active {
+ .label {
+ color: #ccc;
+ }
+ }
+
+ .label {
+ font-size: .75rem;
+ color: #666;
+ margin-right: .5rem;
+ }
+
+ button {
+ margin: 0 .2rem;
+ font-size: .75rem;
+ }
+
+ }
+}
+.ant-table-filter-dropdown {
+ max-width: 250px;
+}
+
+.toggle-switch {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ flex-wrap: nowrap;
+ justify-content: flex-end;
+
+ .outcome-icon {
+ margin-right: .5rem;
+ }
+}
diff --git a/web/styles/colors.scss b/web/styles/colors.scss
index 22aa1d407..5c93e506d 100644
--- a/web/styles/colors.scss
+++ b/web/styles/colors.scss
@@ -3,4 +3,10 @@
--owncast-purple-highlight: #ccd;
--online-color: #73dd3f;
+
+
+ --ant-error: #ff4d4f;
+ --ant-success: #52c41a;
+ --ant-warning: #faad14;
+
}
diff --git a/web/styles/globals.scss b/web/styles/globals.scss
index a1278937a..49e7e41d8 100644
--- a/web/styles/globals.scss
+++ b/web/styles/globals.scss
@@ -41,4 +41,4 @@ pre {
background-color: rgb(44, 44, 44);
color:lightgrey;
}
-}
\ No newline at end of file
+}
diff --git a/web/types/chat.ts b/web/types/chat.ts
new file mode 100644
index 000000000..096839b86
--- /dev/null
+++ b/web/types/chat.ts
@@ -0,0 +1,10 @@
+export interface MessageType {
+ author: string;
+ body: string;
+ id: string;
+ key: string;
+ name: string;
+ timestamp: string;
+ type: string;
+ visible: boolean;
+}
diff --git a/web/utils/apis.ts b/web/utils/apis.ts
index a480a5493..0c60790c1 100644
--- a/web/utils/apis.ts
+++ b/web/utils/apis.ts
@@ -34,22 +34,47 @@ export const LOGS_ALL = `${API_LOCATION}logs`;
// Get warnings + errors
export const LOGS_WARN = `${API_LOCATION}logs/warnings`;
+// Get chat history
+export const CHAT_HISTORY = `${API_LOCATION}chat/messages`;
+
+// Get chat history
+export const UPDATE_CHAT_MESSGAE_VIZ = `${NEXT_PUBLIC_API_HOST}api/admin/chat/updatemessagevisibility`;
+
+
const GITHUB_RELEASE_URL = "https://api.github.com/repos/owncast/owncast/releases/latest";
-export async function fetchData(url) {
- let options: RequestInit = {};
+interface FetchOptions {
+ data?: any;
+ method?: string;
+ auth?: boolean;
+};
- if (ADMIN_USERNAME && ADMIN_STREAMKEY) {
+export async function fetchData(url: string, options?: FetchOptions) {
+ const {
+ data,
+ method = 'GET',
+ auth = true,
+ } = options || {};
+
+ const requestOptions: RequestInit = {
+ method,
+ };
+
+ if (data) {
+ requestOptions.body = JSON.stringify(data)
+ }
+
+ if (auth && ADMIN_USERNAME && ADMIN_STREAMKEY) {
const encoded = btoa(`${ADMIN_USERNAME}:${ADMIN_STREAMKEY}`);
- options.headers = {
+ requestOptions.headers = {
'Authorization': `Basic ${encoded}`
}
- options.mode = 'cors';
- options.credentials = 'include'
+ requestOptions.mode = 'cors';
+ requestOptions.credentials = 'include';
}
try {
- const response = await fetch(url, options);
+ const response = await fetch(url, requestOptions);
if (!response.ok) {
const message = `An error has occured: ${response.status}`;
throw new Error(message);
diff --git a/web/utils/format.ts b/web/utils/format.ts
index a34ce1773..c7db93cb0 100644
--- a/web/utils/format.ts
+++ b/web/utils/format.ts
@@ -15,7 +15,11 @@ export function formatIPAddress(ipAddress: string): string {
// check if obj is {}
export function isEmptyObject(obj) {
- return !obj || Object.keys(obj).length === 0;
+ return !obj || (Object.keys(obj).length === 0 && obj.constructor === Object);
+}
+
+export function padLeft(text, pad, size) {
+ return String(pad.repeat(size) + text).slice(-size);
}
export function parseSecondsToDurationString(seconds = 0) {
@@ -35,7 +39,3 @@ export function parseSecondsToDurationString(seconds = 0) {
return daysString + hoursString + minString + secsString;
}
-
-export function padLeft(text, pad, size) {
- return String(pad.repeat(size) + text).slice(-size);
-}
\ No newline at end of file