- Video Listener Optimizations - Auto Leave Fix - Edge support - Admin Dashboard Update - Vimeo & Dailymotion
807 lines
25 KiB
React
807 lines
25 KiB
React
import React, { Component } from 'react';
|
|
import firebase from 'firebase/app';
|
|
import 'firebase/auth';
|
|
import 'firebase/firestore';
|
|
import {
|
|
Button,
|
|
Card,
|
|
Table,
|
|
Tabs,
|
|
Tag,
|
|
Switch,
|
|
Input,
|
|
notification,
|
|
Divider,
|
|
Row,
|
|
Col,
|
|
PageHeader,
|
|
InputNumber,
|
|
Descriptions,
|
|
Badge,
|
|
Collapse,
|
|
Modal,
|
|
Select,
|
|
} from 'antd';
|
|
import {
|
|
getPartyDashboardData,
|
|
getServerDashboardData,
|
|
postUpdateVersionActive,
|
|
postUpdateVideoSelectors,
|
|
postUpdateVideoNameSelectors,
|
|
postSendMessage,
|
|
} from './Api';
|
|
import {
|
|
CheckCircleTwoTone,
|
|
CloseCircleTwoTone,
|
|
ReloadOutlined,
|
|
LoadingOutlined,
|
|
EnterOutlined,
|
|
DeleteOutlined,
|
|
PlusCircleFilled,
|
|
SaveFilled,
|
|
SaveOutlined,
|
|
LogoutOutlined,
|
|
SendOutlined,
|
|
PlusCircleOutlined,
|
|
} from '@ant-design/icons';
|
|
|
|
const PLAYER_SOURCES = [
|
|
{ text: 'Netflix', value: 'netflix.' },
|
|
{ text: 'Amazon', value: 'amazon.' },
|
|
{ text: 'Hulu', value: 'hulu.' },
|
|
{ text: 'Disney+', value: 'disneyplus.' },
|
|
{ text: 'YouTube', value: 'youtube.' },
|
|
{ text: 'HBO Now', value: 'hbonow.' },
|
|
{ text: 'HBO Max', value: 'hbomax.' },
|
|
{ text: 'Peacock', value: 'peacocktv.' },
|
|
{ text: 'Google Play', value: 'play.google.' },
|
|
{ text: 'Crunchyroll', value: 'crunchyroll.' },
|
|
{ text: 'Apple TV+', value: 'tv.apple.' },
|
|
{ text: 'Viki', value: 'viki.' },
|
|
{ text: 'Funimation', value: 'funimation.' },
|
|
{ text: 'Rooster Teeth', value: 'roosterteeth.' },
|
|
{ text: 'VRV', value: 'vrv.' },
|
|
{ text: 'Stan', value: 'stan.' },
|
|
{ text: 'Hotstar', value: 'hotstar.' },
|
|
{ text: 'Airtel', value: 'airtelxstream.' },
|
|
{ text: 'Vimeo', value: 'vimeo.' },
|
|
{ text: 'Dailymotion', value: 'dailymotion.' },
|
|
];
|
|
|
|
const MESSAGE_TYPES = [
|
|
{
|
|
text: 'REPLY',
|
|
value: 'interactive/text',
|
|
},
|
|
{
|
|
text: 'LINK',
|
|
value: 'interactive/link',
|
|
},
|
|
{
|
|
text: 'MESSAGE',
|
|
value: '',
|
|
},
|
|
];
|
|
|
|
const emptyMessage = {
|
|
type: '',
|
|
text: '',
|
|
uid: '<system>',
|
|
name: '',
|
|
endpoint: '',
|
|
// time set on send,
|
|
};
|
|
|
|
export default class Dashboard extends Component {
|
|
constructor(props) {
|
|
super(props);
|
|
this.state = {
|
|
partiesOverview: null,
|
|
activeTab: '1',
|
|
refreshing: true,
|
|
selectedParties: [],
|
|
nameSelectors: {},
|
|
selectors: {},
|
|
versions: [],
|
|
activeLimit: 5,
|
|
afkLimit: 10,
|
|
messageModel: false,
|
|
newMessage: emptyMessage,
|
|
};
|
|
this.serverStatusListener = null;
|
|
}
|
|
componentDidMount() {
|
|
this.getPartiesOverview();
|
|
this.getVersionsOverview();
|
|
}
|
|
getPartiesOverview = async () => {
|
|
getPartyDashboardData()
|
|
.then((res) => res.json())
|
|
.then((res) => {
|
|
this.setState({ partiesOverview: res.partyData, refreshing: false });
|
|
});
|
|
};
|
|
getVersionsOverview = () => {
|
|
getServerDashboardData()
|
|
.then((res) => res.json())
|
|
.then((res) => this.setState({ ...res.serverData, refreshing: false }));
|
|
};
|
|
refreshData = () => {
|
|
this.setState({ refreshing: true });
|
|
const { activeTab } = this.state;
|
|
switch (activeTab) {
|
|
case '1':
|
|
this.getPartiesOverview();
|
|
break;
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
this.getVersionsOverview();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
tabChange = (activeTab) => {
|
|
this.setState({ activeTab });
|
|
};
|
|
updateVersionMessage = (message, version) => {
|
|
this.setState({ refreshing: true });
|
|
const { versions } = this.state;
|
|
const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0];
|
|
versionData.message = message;
|
|
this.updateVersion(version, versionData);
|
|
};
|
|
updateVersionActive = (active, version) => {
|
|
this.setState({ refreshing: true });
|
|
const { versions } = this.state;
|
|
const versionData = versions.filter((v) => v.version.split('.').join('') === version)[0];
|
|
versionData.active = active;
|
|
this.updateVersion(version, versionData);
|
|
};
|
|
updateVersion = (version, versionData) => {
|
|
postUpdateVersionActive({ version, versionData })
|
|
.then(() => this.refreshData())
|
|
.catch((err) =>
|
|
notification.error({
|
|
description: err.message,
|
|
message: 'Unable To Update',
|
|
})
|
|
);
|
|
};
|
|
getSourceName(source) {
|
|
return PLAYER_SOURCES.filter((p) => p.value === source)[0].text;
|
|
}
|
|
handleVideoSelectorChange = (e, s) => {
|
|
const { selectors } = this.state;
|
|
selectors[s] = e.target.value;
|
|
this.setState({ selectors });
|
|
};
|
|
updateVideoSelector = () => {
|
|
this.setState({ refreshing: true });
|
|
const { selectors } = this.state;
|
|
postUpdateVideoSelectors({ selectors })
|
|
.then(() => this.refreshData())
|
|
.catch((err) => {
|
|
notification.error({
|
|
description: err.message,
|
|
message: 'Unable To Update',
|
|
});
|
|
|
|
this.setState({ refreshing: false });
|
|
});
|
|
};
|
|
handleVersionMessageChange = (e, v) => {
|
|
const { versions } = this.state;
|
|
versions.filter((vs) => vs.version === v)[0].message = e.target.value;
|
|
this.setState({ versions });
|
|
};
|
|
handleAlertTitleChange = (value, version, index) => {
|
|
const { versions } = this.state;
|
|
versions.filter((vs) => vs.version === version)[0].alerts[index].title = value;
|
|
this.setState({ versions });
|
|
};
|
|
handleAlertDescriptionChange = (value, version, index) => {
|
|
const { versions } = this.state;
|
|
versions.filter((vs) => vs.version === version)[0].alerts[index].description = value;
|
|
this.setState({ versions });
|
|
};
|
|
handleAlertLinkChange = (value, version, index) => {
|
|
const { versions } = this.state;
|
|
versions.filter((vs) => vs.version === version)[0].alerts[index].link = value;
|
|
this.setState({ versions });
|
|
};
|
|
handleAlertLinkTextChange = (value, version, index) => {
|
|
const { versions } = this.state;
|
|
versions.filter((vs) => vs.version === version)[0].alerts[index].linkText = value;
|
|
this.setState({ versions });
|
|
};
|
|
deleteAlert = (version, index) => {
|
|
const { versions } = this.state;
|
|
versions.filter((vs) => vs.version === version)[0].alerts.splice(index, 1);
|
|
if (
|
|
versions.filter((vs) => vs.version === version)[0].importantAlert === index ||
|
|
versions.filter((vs) => vs.version === version)[0].alerts.length < index
|
|
) {
|
|
versions.filter((vs) => vs.version === version)[0].importantAlert = undefined;
|
|
}
|
|
this.setState({ versions });
|
|
};
|
|
addEmptyAlert = (version) => {
|
|
const { versions } = this.state;
|
|
versions
|
|
.filter((vs) => vs.version === version)[0]
|
|
.alerts.push({
|
|
title: '',
|
|
description: '',
|
|
link: '',
|
|
linkText: '',
|
|
});
|
|
this.setState({ versions });
|
|
};
|
|
saveAlerts = (version) => {
|
|
this.setState({ refreshing: true });
|
|
const { versions } = this.state;
|
|
const versionData = versions.filter((v) => v.version === version)[0];
|
|
if (versionData.alerts.length > 0) this.updateVersion(version.split('.').join(''), versionData);
|
|
else {
|
|
notification.error({
|
|
message: 'Missing Alerts',
|
|
description: 'Must have atleast one alert',
|
|
});
|
|
|
|
this.setState({ refreshing: false });
|
|
}
|
|
};
|
|
changeImportantAlert = (active, version, index) => {
|
|
const { versions } = this.state;
|
|
if (active === false) {
|
|
versions.filter((vs) => vs.version === version)[0].importantAlert = undefined;
|
|
} else {
|
|
versions.filter((vs) => vs.version === version)[0].importantAlert = index;
|
|
}
|
|
this.setState({ versions });
|
|
};
|
|
getAddMessageText = () => {
|
|
const { activeTab } = this.state;
|
|
switch (activeTab) {
|
|
case '1':
|
|
return 'Send Message';
|
|
case '2':
|
|
return 'Add Version';
|
|
case '3':
|
|
return 'Add Video Selector';
|
|
case '4':
|
|
return 'Add Name Selector';
|
|
default:
|
|
return '';
|
|
}
|
|
};
|
|
getPrettyTime = (t) => {
|
|
return t > 3600
|
|
? new Date(t * 1000).toISOString().substr(11, 8)
|
|
: new Date(t * 1000).toISOString().substr(14, 5);
|
|
};
|
|
handleVideoNameSelectorChange = (event, source, type) => {
|
|
const { nameSelectors } = this.state;
|
|
nameSelectors[source][type] = event.target.value;
|
|
this.setState({ nameSelectors });
|
|
};
|
|
updateVideoNameSelector = () => {
|
|
this.setState({ refreshing: true });
|
|
const { nameSelectors } = this.state;
|
|
postUpdateVideoNameSelectors({ nameSelectors })
|
|
.then(() => this.refreshData())
|
|
.catch((err) => {
|
|
notification.error({
|
|
description: err.message,
|
|
message: 'Unable To Update',
|
|
});
|
|
|
|
this.setState({ refreshing: false });
|
|
});
|
|
};
|
|
sendOrAdd = () => {
|
|
const { activeTab } = this.state;
|
|
switch (activeTab) {
|
|
case '1':
|
|
this.setState({ messageModel: true });
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
editMessage = (value, field) => {
|
|
const { newMessage } = this.state;
|
|
newMessage[field] = value;
|
|
this.setState({ newMessage });
|
|
};
|
|
sendMessage = async () => {
|
|
this.setState({ refreshing: true });
|
|
const { selectedParties, newMessage } = this.state;
|
|
newMessage.time = new Date().toLocaleString();
|
|
await postSendMessage({ selectedParties, newMessage }).catch((err) => {
|
|
notification.error({
|
|
description: err.message,
|
|
message: 'Unable To Send Message',
|
|
});
|
|
});
|
|
this.setState({
|
|
refreshing: false,
|
|
newMessage: {
|
|
type: '',
|
|
text: '',
|
|
uid: '<system>',
|
|
name: '',
|
|
endpoint: '',
|
|
},
|
|
messageModel: false,
|
|
});
|
|
};
|
|
render() {
|
|
const {
|
|
partiesOverview,
|
|
activeTab,
|
|
refreshing,
|
|
selectedParties,
|
|
versions,
|
|
nameSelectors,
|
|
selectors,
|
|
activeLimit,
|
|
afkLimit,
|
|
messageModel,
|
|
newMessage,
|
|
} = this.state;
|
|
|
|
const partiesColumns = [
|
|
{
|
|
title: 'Party Name',
|
|
dataIndex: 'id',
|
|
sorter: (a, b) => (a.id.toLowerCase() < b.id.toLowerCase() ? -1 : 1),
|
|
},
|
|
{
|
|
title: 'Source',
|
|
dataIndex: 'source',
|
|
sorter: (a, b) => (a.source.toLowerCase() < b.source.toLowerCase() ? -1 : 1),
|
|
filters: [
|
|
...PLAYER_SOURCES,
|
|
{
|
|
text: 'Is Playing',
|
|
value: 'PLAYING',
|
|
},
|
|
{
|
|
text: 'Is Paused',
|
|
value: 'PAUSED',
|
|
},
|
|
],
|
|
onFilter: (v, r) => r.source === v || r.status === v,
|
|
render: (a, r) => (
|
|
<Badge
|
|
status={
|
|
r.status === 'PLAYING' ? 'success' : r.status === 'PAUSED' ? 'warning' : 'error'
|
|
}
|
|
text={this.getSourceName(a)}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: 'Created At',
|
|
dataIndex: 'createTime',
|
|
defaultSortOrder: 'descend',
|
|
sorter: (a, b) => Date.parse(a.createTime) - Date.parse(b.createTime),
|
|
render: (a) => new Date(Date.parse(a)).toLocaleString(),
|
|
},
|
|
{
|
|
title: 'Updated At',
|
|
dataIndex: 'updateTime',
|
|
filters: [
|
|
{
|
|
text: 'Active',
|
|
value: 'a',
|
|
},
|
|
{
|
|
text: 'Inactive',
|
|
value: 'i',
|
|
},
|
|
],
|
|
onFilter: (v, r) =>
|
|
v === 'a'
|
|
? (Date.now() - Date.parse(r.updateTime)) / 60000 < afkLimit
|
|
: (Date.now() - Date.parse(r.updateTime)) / 60000 > afkLimit,
|
|
sorter: (a, b) => Date.parse(a.updateTime) - Date.parse(b.updateTime),
|
|
render: (a) => {
|
|
const minutes = (Date.now() - Date.parse(a)) / 60000;
|
|
const dS = new Date(a).toLocaleString();
|
|
return (
|
|
<Tag
|
|
color={
|
|
minutes <= activeLimit ? 'success' : minutes <= afkLimit ? 'processing' : 'default'
|
|
}
|
|
>
|
|
{dS}
|
|
</Tag>
|
|
);
|
|
},
|
|
},
|
|
{
|
|
title: 'On Call',
|
|
dataIndex: 'onCall',
|
|
filters: [
|
|
{
|
|
text: 'On Call',
|
|
value: true,
|
|
},
|
|
{
|
|
text: 'Not On Call',
|
|
value: false,
|
|
},
|
|
],
|
|
render: (a) =>
|
|
a ? (
|
|
<CheckCircleTwoTone twoToneColor="#beff94" id="call-icon" />
|
|
) : (
|
|
<CloseCircleTwoTone twoToneColor="#ff5f5f" id="call-icon" />
|
|
),
|
|
onFilter: (v, r) => r.onCall === v,
|
|
},
|
|
];
|
|
|
|
const versionsColumns = [
|
|
{
|
|
title: 'Version',
|
|
dataIndex: 'version',
|
|
defaultSortOrder: 'descend',
|
|
sorter: (a, b) =>
|
|
parseInt(a.version.split('.').join('')) - parseInt(b.version.split('.').join('')),
|
|
},
|
|
{
|
|
title: 'Active',
|
|
dataIndex: 'active',
|
|
filters: [
|
|
{
|
|
text: 'Active',
|
|
value: true,
|
|
},
|
|
{
|
|
text: 'Inactive',
|
|
value: false,
|
|
},
|
|
],
|
|
onFilter: (v, r) => r.active === v,
|
|
render: (a, r) => (
|
|
<Switch
|
|
disabled={refreshing}
|
|
onChange={(c) => this.updateVersionActive(c, r.version.split('.').join(''))}
|
|
checked={a}
|
|
/>
|
|
),
|
|
},
|
|
{
|
|
title: 'Inactive Message',
|
|
dataIndex: 'message',
|
|
render: (a, r) => (
|
|
<Input.Search
|
|
disabled={refreshing}
|
|
onSearch={(v) => this.updateVersionMessage(v, r.version.split('.').join(''))}
|
|
value={a}
|
|
onChange={(e) => this.handleVersionMessageChange(e, r.version)}
|
|
enterButton={<EnterOutlined />}
|
|
/>
|
|
),
|
|
},
|
|
];
|
|
return (
|
|
<div className="dashboard-wrapper">
|
|
<PageHeader
|
|
className="title-header"
|
|
title="WATCH PARTY"
|
|
subTitle="ADMIN DASHBOARD"
|
|
extra={[
|
|
<Button
|
|
onClick={() => firebase.auth().signOut()}
|
|
key="1"
|
|
type="primary"
|
|
danger
|
|
icon={<LogoutOutlined />}
|
|
>
|
|
Logout
|
|
</Button>,
|
|
]}
|
|
/>
|
|
<Modal
|
|
visible={messageModel}
|
|
closable={false}
|
|
destroyOnClose
|
|
maskClosable={false}
|
|
cancelButtonProps={{
|
|
disabled: refreshing,
|
|
onClick: () => this.setState({ messageModel: false }),
|
|
}}
|
|
okButtonProps={{
|
|
disabled: refreshing,
|
|
icon: <SendOutlined />,
|
|
onClick: this.sendMessage,
|
|
}}
|
|
okText="Send Message"
|
|
>
|
|
<Select
|
|
onChange={(e) => this.editMessage(e, 'type')}
|
|
value={newMessage.type}
|
|
placeholder="Message Type"
|
|
disabled={refreshing}
|
|
>
|
|
{MESSAGE_TYPES.map((v) => (
|
|
<Select.Option key={`msg-type-option-${v.value}`} value={v.value}>
|
|
{v.text}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
<Input.TextArea
|
|
disabled={refreshing}
|
|
value={newMessage.text}
|
|
onChange={(e) => this.editMessage(e.target.value, 'text')}
|
|
placeholder="Message"
|
|
/>
|
|
{(newMessage.type === 'interactive/link' || newMessage.type === 'interactive/text') && (
|
|
<Input
|
|
disabled={refreshing}
|
|
value={newMessage.endpoint}
|
|
onChange={(e) => this.editMessage(e.target.value, 'endpoint')}
|
|
placeholder={newMessage.type === 'interactive/link' ? 'Link' : 'Endpoint'}
|
|
/>
|
|
)}
|
|
{newMessage.type === 'interactive/link' && (
|
|
<Input
|
|
disabled={refreshing}
|
|
value={newMessage.name}
|
|
onChange={(e) => this.editMessage(e.target.value, 'name')}
|
|
placeholder="Link Text"
|
|
/>
|
|
)}
|
|
</Modal>
|
|
<Card>
|
|
<PageHeader
|
|
extra={[
|
|
activeTab === '1' && (
|
|
<Badge key="5" count="ACTIVE" className="active">
|
|
<InputNumber
|
|
prefix="Active"
|
|
value={activeLimit}
|
|
onChange={(activeLimit) => this.setState({ activeLimit })}
|
|
/>
|
|
</Badge>
|
|
),
|
|
activeTab === '1' && (
|
|
<Badge key="4" count="AFK" className="afk">
|
|
<InputNumber
|
|
prefix="AFK"
|
|
key="4"
|
|
value={afkLimit}
|
|
onChange={(afkLimit) => this.setState({ afkLimit })}
|
|
/>
|
|
</Badge>
|
|
),
|
|
<Button
|
|
key="3"
|
|
loading={refreshing}
|
|
disabled={refreshing}
|
|
onClick={this.refreshData}
|
|
shape="circle"
|
|
icon={refreshing ? <LoadingOutlined /> : <ReloadOutlined />}
|
|
/>,
|
|
<Button
|
|
key="2"
|
|
onClick={this.sendOrAdd}
|
|
icon={activeTab === '1' ? <SendOutlined /> : <PlusCircleOutlined />}
|
|
disabled={activeTab === '1' && selectedParties.length === 0}
|
|
>
|
|
{this.getAddMessageText()}
|
|
</Button>,
|
|
activeTab === '3' && (
|
|
<Button
|
|
type="primary"
|
|
onClick={this.updateVideoSelector}
|
|
icon={<SaveOutlined />}
|
|
key="6"
|
|
loading={refreshing}
|
|
>
|
|
Save Video Selectors
|
|
</Button>
|
|
),
|
|
activeTab === '4' && (
|
|
<Button
|
|
type="primary"
|
|
onClick={this.updateVideoNameSelector}
|
|
icon={<SaveOutlined />}
|
|
key="7"
|
|
loading={refreshing}
|
|
>
|
|
Save Video Name Selectors
|
|
</Button>
|
|
),
|
|
]}
|
|
/>
|
|
<Tabs activeKey={activeTab} onChange={this.tabChange}>
|
|
<Tabs.TabPane tab="Parties" key="1">
|
|
<Table
|
|
rowSelection={{
|
|
selectedRowKeys: selectedParties,
|
|
onChange: (selectedParties) => this.setState({ selectedParties }),
|
|
}}
|
|
loading={partiesOverview === null || refreshing}
|
|
pagination={{
|
|
position: ['bottomCenter'],
|
|
total: partiesOverview ? partiesOverview.length : 0,
|
|
showTotal: (t, r) => `${r[0]} to ${r[1]} of ${t}`,
|
|
defaultPageSize: 10,
|
|
showSizeChanger: true,
|
|
pageSizeOptions: [10, 25, 50],
|
|
}}
|
|
rowKey={(p) => p.id}
|
|
dataSource={partiesOverview}
|
|
columns={partiesColumns}
|
|
expandable={{
|
|
rowExpandable: (r) => true,
|
|
expandedRowClassName: () => 'party-details-wrapper',
|
|
expandedRowRender: (r) => (
|
|
<Descriptions bordered column={5}>
|
|
<Descriptions.Item span={2} label="# Of Users">
|
|
{r.userCount}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item span={3} label="# Of Messages">
|
|
{r.messageCount}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item span={5} label="Show Name">
|
|
{r.showName}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item span={2} label="Show Time">
|
|
{this.getPrettyTime(r.time)}
|
|
</Descriptions.Item>
|
|
<Descriptions.Item span={3} label="Show Status">
|
|
<Badge
|
|
status={
|
|
r.status === 'PLAYING'
|
|
? 'success'
|
|
: r.status === 'PAUSED'
|
|
? 'warning'
|
|
: 'error'
|
|
}
|
|
text={r.status}
|
|
/>
|
|
</Descriptions.Item>
|
|
</Descriptions>
|
|
),
|
|
}}
|
|
/>
|
|
</Tabs.TabPane>
|
|
<Tabs.TabPane tab="Versions" key="2">
|
|
<Table
|
|
loading={refreshing}
|
|
pagination={{ position: ['bottomCenter'] }}
|
|
rowKey={(p) => p.version}
|
|
dataSource={versions}
|
|
columns={versionsColumns}
|
|
expandable={{
|
|
rowExpandable: (r) => r.alerts,
|
|
expandedRowClassName: () => 'alerts-wrapper',
|
|
expandedRowRender: (r) => (
|
|
<Row>
|
|
{r.alerts.map((a, i) => (
|
|
<Col key={`${r.version}-alerts-${i}`}>
|
|
<Card
|
|
actions={[
|
|
<Button
|
|
onClick={() => this.deleteAlert(r.version, i)}
|
|
danger
|
|
icon={<DeleteOutlined />}
|
|
shape="circle"
|
|
color="danger"
|
|
/>,
|
|
<Switch
|
|
onClick={(active) =>
|
|
this.changeImportantAlert(active, r.version, i)
|
|
}
|
|
checked={r.importantAlert && r.importantAlert === i}
|
|
/>,
|
|
]}
|
|
>
|
|
<Input
|
|
placeholder="title"
|
|
value={a.title}
|
|
onChange={(e) =>
|
|
this.handleAlertTitleChange(e.target.value, r.version, i)
|
|
}
|
|
/>
|
|
<Input.TextArea
|
|
placeholder="Description"
|
|
autoSize={{ minRows: 2, maxRows: 2 }}
|
|
value={a.description}
|
|
onChange={(e) =>
|
|
this.handleAlertDescriptionChange(e.target.value, r.version, i)
|
|
}
|
|
/>
|
|
<Divider orientation="left">Link</Divider>
|
|
<Input.Group>
|
|
<Input
|
|
value={a.linkText}
|
|
placeholder="Link Text"
|
|
onChange={(e) =>
|
|
this.handleAlertLinkTextChange(e.target.value, r.version, i)
|
|
}
|
|
/>
|
|
<Input
|
|
value={a.link}
|
|
placeholder="Link"
|
|
onChange={(e) =>
|
|
this.handleAlertLinkChange(e.target.value, r.version, i)
|
|
}
|
|
/>
|
|
</Input.Group>
|
|
</Card>
|
|
</Col>
|
|
))}
|
|
<Col>
|
|
<Card
|
|
onClick={() => this.addEmptyAlert(r.version)}
|
|
className="add-alert-card"
|
|
>
|
|
<PlusCircleFilled />
|
|
</Card>
|
|
</Col>
|
|
<Col>
|
|
<Card onClick={() => this.saveAlerts(r.version)} className="add-alert-card">
|
|
<SaveFilled />
|
|
</Card>
|
|
</Col>
|
|
</Row>
|
|
),
|
|
}}
|
|
/>
|
|
</Tabs.TabPane>
|
|
<Tabs.TabPane tab="Video Selectors" key="3">
|
|
{Object.keys(selectors)
|
|
.sort()
|
|
.map((source) => (
|
|
<Input
|
|
key={`video-selector-${source}`}
|
|
className="video-selector-input"
|
|
addonBefore={this.getSourceName(source)}
|
|
value={selectors[source]}
|
|
onChange={(e) => this.handleVideoSelectorChange(e, source)}
|
|
/>
|
|
))}
|
|
</Tabs.TabPane>
|
|
<Tabs.TabPane tab="Name Selectors" key="4">
|
|
<Collapse ghost>
|
|
{Object.keys(nameSelectors)
|
|
.sort()
|
|
.map((source) => (
|
|
<Collapse.Panel
|
|
key={`name-selector-${source}`}
|
|
header={this.getSourceName(source)}
|
|
>
|
|
<Input
|
|
key={`video-selector-${source}-show`}
|
|
className="video-selector-input"
|
|
addonBefore={'Show'}
|
|
value={nameSelectors[source]['show']}
|
|
onChange={(e) => this.handleVideoNameSelectorChange(e, source, 'show')}
|
|
/>
|
|
<Input
|
|
key={`video-selector-${source}-episode`}
|
|
className="video-selector-input"
|
|
addonBefore={'Episode'}
|
|
value={nameSelectors[source]['episode']}
|
|
onChange={(e) => this.handleVideoNameSelectorChange(e, source, 'episode')}
|
|
/>
|
|
</Collapse.Panel>
|
|
))}
|
|
</Collapse>
|
|
</Tabs.TabPane>
|
|
</Tabs>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|
|
}
|