mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-09 17:33:55 +00:00
b7e758bf24
* chore: add typescript and dependencies * refactor: remove react imports * refactor: fix entrypoint * chore: upgrade chakra and deps * chore: configure eslint
309 lines
10 KiB
React
309 lines
10 KiB
React
/* eslint-disable jsx-a11y/anchor-has-content */
|
|
import { useCallback, useContext, useEffect, useState } from 'react';
|
|
import { Button, IconButton } from '@chakra-ui/button';
|
|
import { ModalBody } from '@chakra-ui/modal';
|
|
import { Input } from '@chakra-ui/react';
|
|
import { Tooltip } from '@chakra-ui/tooltip';
|
|
import { IoInformationCircleOutline } from '@react-icons/all-files/io5/IoInformationCircleOutline';
|
|
import { IoRemove } from '@react-icons/all-files/io5/IoRemove';
|
|
import { IoSunny } from '@react-icons/all-files/io5/IoSunny';
|
|
import { ALIASES } from 'common/api/apiConstants';
|
|
import { useFetch } from 'common/hooks/useFetch';
|
|
|
|
import { viewerLocations } from '../../appConstants';
|
|
import { getAliases, postAliases } from '../../common/api/ontimeApi';
|
|
import { LoggingContext } from '../../common/context/LoggingContext';
|
|
import { validateAlias } from '../../common/utils/aliases';
|
|
import { handleLinks, host } from '../../common/utils/linkUtils';
|
|
import { tooltipDelayFast } from '../../ontimeConfig';
|
|
|
|
import SubmitContainer from './SubmitContainer';
|
|
|
|
import style from './Modals.module.scss';
|
|
|
|
export default function AliasesModal() {
|
|
const { data, status, refetch } = useFetch(ALIASES, getAliases);
|
|
const { emitError } = useContext(LoggingContext);
|
|
const [changed, setChanged] = useState(false);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
const [aliases, setAliases] = useState([]);
|
|
|
|
/**
|
|
* Set formdata from server state
|
|
*/
|
|
useEffect(() => {
|
|
if (data == null) return;
|
|
if (changed) return;
|
|
setAliases([...data]);
|
|
}, [changed, data]);
|
|
|
|
/**
|
|
* Validate and submit data
|
|
*/
|
|
const submitHandler = useCallback(
|
|
async (event) => {
|
|
event.preventDefault();
|
|
setSubmitting(true);
|
|
|
|
const validatedAliases = [...aliases];
|
|
let errors = false;
|
|
for (const alias of validatedAliases) {
|
|
// validate url
|
|
const isURLValid = validateAlias(alias.pathAndParams);
|
|
if (!isURLValid.status) {
|
|
alias.urlError = isURLValid.message;
|
|
errors = true;
|
|
} else {
|
|
alias.urlError = undefined;
|
|
}
|
|
// validate alias
|
|
const isAliasValid = validateAlias(alias.alias);
|
|
if (!isAliasValid.status) {
|
|
alias.aliasError = isAliasValid.message;
|
|
errors = true;
|
|
} else {
|
|
alias.aliasError = undefined;
|
|
}
|
|
}
|
|
setAliases(validatedAliases);
|
|
|
|
if (!errors) {
|
|
await postAliases(aliases);
|
|
await refetch();
|
|
setChanged(false);
|
|
}
|
|
|
|
setSubmitting(false);
|
|
},
|
|
[aliases, refetch]
|
|
);
|
|
|
|
/**
|
|
* Creates a new alias in state with a temporary id
|
|
*/
|
|
const addNew = useCallback(() => {
|
|
if (aliases.length > 20) {
|
|
emitError('Maximum amount of aliases reacted (20)');
|
|
return;
|
|
}
|
|
|
|
const emptyAlias = {
|
|
id: Math.floor(Math.random() * 1000),
|
|
enabled: false,
|
|
alias: '',
|
|
pathAndParams: '',
|
|
};
|
|
setAliases((prevState) => [...prevState, emptyAlias]);
|
|
setChanged(true);
|
|
}, [aliases.length, emitError]);
|
|
|
|
/**
|
|
* Deletes an alias by a given id
|
|
* @param {string} id - id of alias to delete
|
|
*/
|
|
const deleteAlias = useCallback((id) => {
|
|
setAliases((prevState) => [...prevState.filter((a) => a.id !== id)]);
|
|
setChanged(true);
|
|
}, []);
|
|
|
|
/**
|
|
* Sets enabled flag to true / false
|
|
* @param {string} id - object id
|
|
* @param {boolean} isEnabled - whether to enable / disable flag
|
|
*/
|
|
const setEnabled = useCallback((id, isEnabled) => {
|
|
const aliasesState = [...aliases];
|
|
for (const a of aliasesState) {
|
|
if (a.id === id) {
|
|
if (isEnabled) {
|
|
if (a.alias === '' || a.pathAndParams === '') {
|
|
emitError('Alias incomplete');
|
|
break;
|
|
}
|
|
|
|
const isRepeated = aliases.some((r) => a.alias === r.alias && r.enabled);
|
|
if (isRepeated) {
|
|
emitError('There is already an alias with this name');
|
|
break;
|
|
}
|
|
}
|
|
a.enabled = isEnabled;
|
|
break;
|
|
}
|
|
}
|
|
setChanged(true);
|
|
setAliases(aliasesState);
|
|
}, [aliases, emitError]);
|
|
|
|
/**
|
|
* Reverts local state equals to server state
|
|
*/
|
|
const revert = async () => {
|
|
setChanged(false);
|
|
await refetch();
|
|
};
|
|
|
|
/**
|
|
* Handles change of input field in local state
|
|
* @param {number} index - index of item in array
|
|
* @param {string} field - object parameter to update
|
|
* @param {string} value - new object parameter value
|
|
*/
|
|
const handleChange = useCallback(
|
|
(index, field, value) => {
|
|
const temp = [...aliases];
|
|
temp[index][field] = value;
|
|
setAliases(temp);
|
|
setChanged(true);
|
|
},
|
|
[aliases]
|
|
);
|
|
|
|
return (
|
|
<ModalBody className={style.modalBody}>
|
|
<p className={style.notes}>
|
|
Configure easy to use URL Aliases
|
|
<br />
|
|
🔥 Changes take effect on save 🔥
|
|
</p>
|
|
<form onSubmit={submitHandler}>
|
|
<div className={style.modalFields}>
|
|
<div className={style.hSeparator}>Default URLs</div>
|
|
<div className={style.blockNotes}>
|
|
{viewerLocations.map((l) => (
|
|
<a
|
|
href={l.link}
|
|
target='_blank'
|
|
rel='noreferrer'
|
|
className={style.flexNote}
|
|
key={l.link}
|
|
onClick={(e) => handleLinks(e, l.link)}
|
|
>
|
|
{`${l.label} - http://${host}/${l.link}`}
|
|
</a>
|
|
))}
|
|
</div>
|
|
<div className={style.hSeparator}>Custom Aliases</div>
|
|
<div className={style.blockNotes}>
|
|
<span className={style.inlineFlex}>
|
|
<IoInformationCircleOutline color='#2b6cb0' fontSize='2em' />
|
|
URL aliases are useful in two main scenarios
|
|
</span>
|
|
<span className={style.labelNote}>Complicated URLs</span>
|
|
<br />
|
|
eg. a lower third url with some custom parameters
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td className={style.labelNote} style={{ width: '30%' }}>
|
|
Alias
|
|
</td>
|
|
<td className={style.labelNote}>Page URL</td>
|
|
</tr>
|
|
<tr>
|
|
<td>mylower</td>
|
|
<td>lower?bg=ff2&text=f00&size=0.6&transition=5</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<br />
|
|
<span className={style.labelNote}>URLs to be changed dynamically</span>
|
|
<br />
|
|
eg. an unattended screen that you would need to change route from the app
|
|
<table>
|
|
<tbody>
|
|
<tr>
|
|
<td className={style.labelNote} style={{ width: '30%' }}>
|
|
Alias
|
|
</td>
|
|
<td className={style.labelNote}>Page URL</td>
|
|
</tr>
|
|
<tr>
|
|
<td>thirdfloor</td>
|
|
<td>public</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div className={style.inlineAliasPlaceholder}>
|
|
<span className={style.labelNote}>Alias</span>
|
|
<span className={style.labelNote}>Page URL</span>
|
|
</div>
|
|
{aliases.map((alias, index) => (
|
|
<div key={alias.id}>
|
|
<div className={style.inlineAlias}>
|
|
<Input
|
|
size='sm'
|
|
variant='flushed'
|
|
name='Alias'
|
|
placeholder='URL Alias'
|
|
autoComplete='off'
|
|
value={alias.alias}
|
|
isInvalid={alias.aliasError}
|
|
onChange={(event) => handleChange(index, 'alias', event.target.value)}
|
|
/>
|
|
<Input
|
|
size='sm'
|
|
fontSize='0.75em'
|
|
variant='flushed'
|
|
name='URL'
|
|
placeholder='URL (portion after ontime Port)'
|
|
autoComplete='off'
|
|
value={alias.pathAndParams}
|
|
isInvalid={alias.urlError}
|
|
onChange={(event) => handleChange(index, 'pathAndParams', event.target.value)}
|
|
/>
|
|
<Tooltip label={`Test /${alias.pathAndParams}`} openDelay={tooltipDelayFast}>
|
|
<a
|
|
href='#!'
|
|
target='_blank'
|
|
rel='noreferrer'
|
|
onClick={(e) => handleLinks(e, alias.pathAndParams)}
|
|
/>
|
|
</Tooltip>
|
|
<Tooltip label='Enable alias' openDelay={tooltipDelayFast}>
|
|
<IconButton
|
|
aria-label='Enable alias'
|
|
size='xs'
|
|
icon={<IoSunny />}
|
|
colorScheme='blue'
|
|
variant={alias.enabled ? null : 'outline'}
|
|
onClick={() => setEnabled(alias.id, !alias.enabled)}
|
|
/>
|
|
</Tooltip>
|
|
<Tooltip label='Delete alias' openDelay={tooltipDelayFast}>
|
|
<IconButton
|
|
aria-label='Delete alias'
|
|
size='xs'
|
|
icon={<IoRemove />}
|
|
colorScheme='red'
|
|
onClick={() => deleteAlias(alias.id)}
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
{alias.aliasError ? (
|
|
<div className={style.error}>{`Alias error: ${alias.aliasError}`}</div>
|
|
) : null}
|
|
{alias.urlError ? (
|
|
<div className={style.error}>{`URL error: ${alias.urlError}`}</div>
|
|
) : null}
|
|
</div>
|
|
))}
|
|
|
|
<div className={style.inlineAliasPlaceholder}>
|
|
<Button size='xs' colorScheme='blue' variant='outline' onClick={() => addNew()}>
|
|
Add new
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<SubmitContainer
|
|
revert={revert}
|
|
submitting={submitting}
|
|
changed={changed}
|
|
status={status}
|
|
/>
|
|
</form>
|
|
</ModalBody>
|
|
);
|
|
}
|