Browse Source
* WIP external actions * Add comment * Add support for external actions icons and colorspull/1886/head
6 changed files with 294 additions and 1 deletions
@ -0,0 +1,276 @@
@@ -0,0 +1,276 @@
|
||||
// comment
|
||||
|
||||
import React, { useState, useEffect, useContext } from 'react'; |
||||
import { Table, Space, Button, Modal, Checkbox, Input, Typography } from 'antd'; |
||||
import { ServerStatusContext } from '../utils/server-status-context'; |
||||
import { DeleteOutlined } from '@ant-design/icons'; |
||||
import isValidUrl from '../utils/urls'; |
||||
import FormStatusIndicator from '../components/config/form-status-indicator'; |
||||
import { |
||||
createInputStatus, |
||||
StatusState, |
||||
STATUS_ERROR, |
||||
STATUS_PROCESSING, |
||||
STATUS_SUCCESS, |
||||
} from '../utils/input-statuses'; |
||||
|
||||
import { |
||||
postConfigUpdateToAPI, |
||||
API_EXTERNAL_ACTIONS, |
||||
RESET_TIMEOUT, |
||||
} from '../utils/config-constants'; |
||||
|
||||
const { Title, Paragraph } = Typography; |
||||
let resetTimer = null; |
||||
|
||||
interface Props { |
||||
onCancel: () => void; |
||||
onOk: any; // todo: make better type
|
||||
visible: boolean; |
||||
} |
||||
|
||||
function NewActionModal(props: Props) { |
||||
const { onOk, onCancel, visible } = props; |
||||
|
||||
const [actionUrl, setActionUrl] = useState(''); |
||||
const [actionTitle, setActionTitle] = useState(''); |
||||
const [actionDescription, setActionDescription] = useState(''); |
||||
const [actionIcon, setActionIcon] = useState(''); |
||||
const [actionColor, setActionColor] = useState(''); |
||||
const [openExternally, setOpenExternally] = useState(false); |
||||
|
||||
function save() { |
||||
onOk(actionUrl, actionTitle, actionDescription, actionIcon, actionColor, openExternally); |
||||
} |
||||
|
||||
const okButtonProps = { |
||||
disabled: !isValidUrl(actionUrl) || actionTitle === '', |
||||
}; |
||||
|
||||
const onOpenExternallyChanged = checkbox => { |
||||
setOpenExternally(checkbox.target.checked); |
||||
}; |
||||
|
||||
return ( |
||||
<Modal |
||||
title="Create New Action" |
||||
visible={visible} |
||||
onOk={save} |
||||
onCancel={onCancel} |
||||
okButtonProps={okButtonProps} |
||||
> |
||||
<div> |
||||
<p> |
||||
<Input |
||||
value={actionUrl} |
||||
required |
||||
placeholder="https://myserver.com/action (required)" |
||||
onChange={input => setActionUrl(input.currentTarget.value)} |
||||
/> |
||||
</p> |
||||
<p> |
||||
<Input |
||||
value={actionTitle} |
||||
required |
||||
placeholder="Your action title (required)" |
||||
onChange={input => setActionTitle(input.currentTarget.value)} |
||||
/> |
||||
</p> |
||||
|
||||
<p> |
||||
<Input |
||||
value={actionDescription} |
||||
placeholder="Optional description" |
||||
onChange={input => setActionDescription(input.currentTarget.value)} |
||||
/> |
||||
</p> |
||||
|
||||
<p> |
||||
<Input |
||||
value={actionIcon} |
||||
placeholder="https://myserver.com/action/icon.png (optional)" |
||||
onChange={input => setActionIcon(input.currentTarget.value)} |
||||
/> |
||||
</p> |
||||
|
||||
<p> |
||||
<Input |
||||
type="color" |
||||
value={actionColor} |
||||
onChange={input => setActionColor(input.currentTarget.value)} |
||||
/> |
||||
</p> |
||||
|
||||
<Checkbox |
||||
checked={openExternally} |
||||
defaultChecked={openExternally} |
||||
onChange={onOpenExternallyChanged} |
||||
> |
||||
Open in a new tab instead of within your page. |
||||
</Checkbox> |
||||
</div> |
||||
</Modal> |
||||
); |
||||
} |
||||
|
||||
export default function Actions() { |
||||
const serverStatusData = useContext(ServerStatusContext); |
||||
const { serverConfig, setFieldInConfigState } = serverStatusData || {}; |
||||
const { externalActions } = serverConfig; |
||||
const [actions, setActions] = useState([]); |
||||
const [isModalVisible, setIsModalVisible] = useState(false); |
||||
const [submitStatus, setSubmitStatus] = useState(null); |
||||
|
||||
const resetStates = () => { |
||||
setSubmitStatus(null); |
||||
resetTimer = null; |
||||
clearTimeout(resetTimer); |
||||
}; |
||||
|
||||
useEffect(() => { |
||||
setActions(externalActions || []); |
||||
}, [externalActions]); |
||||
|
||||
const columns = [ |
||||
{ |
||||
title: '', |
||||
key: 'delete', |
||||
render: (text, record) => ( |
||||
<Space size="middle"> |
||||
<Button onClick={() => handleDelete(record)} icon={<DeleteOutlined />} /> |
||||
</Space> |
||||
), |
||||
}, |
||||
{ |
||||
title: 'Name', |
||||
dataIndex: 'title', |
||||
key: 'title', |
||||
}, |
||||
{ |
||||
title: 'Description', |
||||
dataIndex: 'description', |
||||
key: 'description', |
||||
}, |
||||
{ |
||||
title: 'URL', |
||||
dataIndex: 'url', |
||||
key: 'url', |
||||
}, |
||||
{ |
||||
title: 'Icon', |
||||
dataIndex: 'icon', |
||||
key: 'icon', |
||||
render: (url: string) => { |
||||
return url ? <img src={url} /> : null; |
||||
}, |
||||
}, |
||||
{ |
||||
title: 'Color', |
||||
dataIndex: 'color', |
||||
key: 'color', |
||||
render: (color: string) => { |
||||
return color ? (<div style={{backgroundColor: color, height: '30px'}}>{color}</div>) : null; |
||||
}, |
||||
}, |
||||
{ |
||||
title: 'Opens', |
||||
dataIndex: 'openExternally', |
||||
key: 'openExternally', |
||||
render: (openExternally: boolean) => { |
||||
return openExternally ? 'In a new tab' : 'In a modal'; |
||||
}, |
||||
}, |
||||
]; |
||||
|
||||
async function handleDelete(action) { |
||||
let actionsData = [...actions]; |
||||
const index = actions.findIndex(item => item.url === action.url); |
||||
actionsData.splice(index, 1); |
||||
|
||||
setActions(actionsData); |
||||
save(actionsData); |
||||
try { |
||||
} catch (error) { |
||||
console.error(error); |
||||
} |
||||
} |
||||
|
||||
async function handleSave( |
||||
url: string, |
||||
title: string, |
||||
description: string, |
||||
icon: string, |
||||
color: string, |
||||
openExternally: boolean, |
||||
) { |
||||
try { |
||||
let actionsData = [...actions]; |
||||
const updatedActions = actionsData.concat({ url, title, description, icon, color, openExternally }); |
||||
setActions(updatedActions); |
||||
await save(updatedActions); |
||||
} catch (error) { |
||||
console.error(error); |
||||
} |
||||
} |
||||
|
||||
async function save(actionsData) { |
||||
await postConfigUpdateToAPI({ |
||||
apiPath: API_EXTERNAL_ACTIONS, |
||||
data: { value: actionsData }, |
||||
onSuccess: () => { |
||||
setFieldInConfigState({ fieldName: 'externalActions', value: actionsData, path: '' }); |
||||
setSubmitStatus(createInputStatus(STATUS_SUCCESS, 'Updated.')); |
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); |
||||
}, |
||||
onError: (message: string) => { |
||||
console.log(message); |
||||
setSubmitStatus(createInputStatus(STATUS_ERROR, message)); |
||||
resetTimer = setTimeout(resetStates, RESET_TIMEOUT); |
||||
}, |
||||
}); |
||||
} |
||||
|
||||
const showCreateModal = () => { |
||||
setIsModalVisible(true); |
||||
}; |
||||
|
||||
const handleModalSaveButton = ( |
||||
actionUrl: string, |
||||
actionTitle: string, |
||||
actionDescription: string, |
||||
actionIcon: string, |
||||
actionColor: string, |
||||
openExternally: boolean, |
||||
) => { |
||||
setIsModalVisible(false); |
||||
handleSave(actionUrl, actionTitle, actionDescription, actionIcon, actionColor, openExternally); |
||||
}; |
||||
|
||||
const handleModalCancelButton = () => { |
||||
setIsModalVisible(false); |
||||
}; |
||||
|
||||
return ( |
||||
<div> |
||||
<Title>External Actions</Title> |
||||
<Paragraph>Description goes here.</Paragraph> |
||||
<Paragraph> |
||||
Read more about how to use actions, with examples, at{' '} |
||||
<a href="https://owncast.online/thirdparty/?source=admin">our documentation</a>. |
||||
</Paragraph> |
||||
|
||||
<Table rowKey="id" columns={columns} dataSource={actions} pagination={false} /> |
||||
<br /> |
||||
<Button type="primary" onClick={showCreateModal}> |
||||
Create New Action |
||||
</Button> |
||||
<FormStatusIndicator status={submitStatus} /> |
||||
|
||||
<NewActionModal |
||||
visible={isModalVisible} |
||||
onOk={handleModalSaveButton} |
||||
onCancel={handleModalCancelButton} |
||||
/> |
||||
</div> |
||||
); |
||||
} |
||||
Loading…
Reference in new issue