feat: implement copy and link
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { Channel, InputChannel, OutputChannel } from '../../types';
|
||||
import Button from '../../../../components/Button/Button';
|
||||
import Card from '../../../../components/Card/Card';
|
||||
import ChoiceSelect from '../../../../components/ChoiceSelect/ChoiceSelect';
|
||||
|
||||
import './CopyChannelModal.css';
|
||||
|
||||
const INPUT_CHANNELS: readonly string[] = Object.values(InputChannel);
|
||||
const OUTPUT_CHANNELS: readonly string[] = Object.values(OutputChannel);
|
||||
const ALL_CHANNELS: readonly string[] = [...INPUT_CHANNELS, ...OUTPUT_CHANNELS];
|
||||
|
||||
const isInputChannel = (raw: string) => INPUT_CHANNELS.includes(raw);
|
||||
|
||||
/** Wraps a raw enum string back into the { Input } / { Output } shape. */
|
||||
const wrapChannel = (raw: string): Channel =>
|
||||
isInputChannel(raw) ? { Input: raw as InputChannel } : { Output: raw as OutputChannel };
|
||||
|
||||
/** The channels a given source can be copied onto: same type, minus itself. */
|
||||
const destinationsFor = (source: string): string[] =>
|
||||
(isInputChannel(source) ? INPUT_CHANNELS : OUTPUT_CHANNELS).filter(
|
||||
(channel) => channel !== source
|
||||
);
|
||||
|
||||
function CopyChannelModal({
|
||||
onClose,
|
||||
copyChannel,
|
||||
}: {
|
||||
onClose: () => void;
|
||||
copyChannel: (channel1: Channel, channel2: Channel) => Promise<boolean>;
|
||||
}) {
|
||||
const [source, setSource] = useState<string>(ALL_CHANNELS[0]);
|
||||
const [destination, setDestination] = useState<string>(() => destinationsFor(ALL_CHANNELS[0])[0]);
|
||||
const [isCopying, setIsCopying] = useState(false);
|
||||
|
||||
const destinationOptions = useMemo(() => destinationsFor(source), [source]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [onClose]);
|
||||
|
||||
const handleSourceChange = (nextSource: string) => {
|
||||
setSource(nextSource);
|
||||
|
||||
// Picking a source of the other type (or picking the current
|
||||
// destination as source) invalidates the destination.
|
||||
const nextDestinations = destinationsFor(nextSource);
|
||||
|
||||
if (!nextDestinations.includes(destination)) {
|
||||
setDestination(nextDestinations[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopy = async () => {
|
||||
setIsCopying(true);
|
||||
|
||||
try {
|
||||
await copyChannel(wrapChannel(source), wrapChannel(destination));
|
||||
onClose();
|
||||
} finally {
|
||||
setIsCopying(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="copy-channel-overlay"
|
||||
onMouseDown={(e) => {
|
||||
if (e.target === e.currentTarget) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Card className="copy-channel-modal">
|
||||
<div className="copy-channel-modal__header">
|
||||
<h2>Copy Channel</h2>
|
||||
<p>
|
||||
Copy every setting of one channel onto another. Channels can only be copied onto
|
||||
channels of the same type.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="copy-channel-modal__transfer">
|
||||
<div className="copy-channel-modal__field">
|
||||
<label htmlFor="copy-channel-source">From</label>
|
||||
|
||||
<ChoiceSelect<string>
|
||||
id="copy-channel-source"
|
||||
value={source}
|
||||
options={ALL_CHANNELS}
|
||||
onChange={handleSourceChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="copy-channel-modal__arrow" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5">
|
||||
<path d="M4 12h15M13 6l6 6-6 6" strokeLinecap="round" strokeLinejoin="round" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div className="copy-channel-modal__field">
|
||||
<label htmlFor="copy-channel-destination">To</label>
|
||||
|
||||
<ChoiceSelect<string>
|
||||
id="copy-channel-destination"
|
||||
value={destination}
|
||||
options={destinationOptions}
|
||||
onChange={setDestination}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="copy-channel-modal__summary">
|
||||
{destination} will take the settings of {source}.
|
||||
<br />
|
||||
This overwrites {destination}.
|
||||
</p>
|
||||
|
||||
<div className="copy-channel-modal__actions">
|
||||
<Button variant="outline" onClick={onClose} disabled={isCopying}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button variant="outline" onClick={handleCopy} disabled={isCopying}>
|
||||
{isCopying ? 'Copying...' : 'Copy'}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CopyChannelModal;
|
||||
Reference in New Issue
Block a user