diff --git a/src/web/control.js b/src/web/control.js index 19311f1..60476ed 100644 --- a/src/web/control.js +++ b/src/web/control.js @@ -729,8 +729,8 @@ function App({ wsEndpoint, role }) { Include an empty object at the end to create an extra input for a new custom stream. We need it to be part of the array (rather than JSX below) for DOM diffing to match the key and retain focus. */} - {[...customStreams, { link: '', label: '', kind: 'video' }].map( - ({ link, label, kind }, idx) => ( + {customStreams.map(({ link, label, kind }, idx) => ( +
- ), - )} +
+ ))} + )} @@ -889,6 +890,61 @@ function StreamLine({ ) } +// An input that maintains local edits and fires onChange after blur (like a non-React input does), or optionally on every edit if isEager is set. +function LazyChangeInput({ + value = '', + onChange, + onFocus, + onBlur, + onKeyDown, + isEager = false, + ...props +}) { + const [editingValue, setEditingValue] = useState() + const handleFocus = useCallback( + (ev) => { + setEditingValue(ev.target.value) + onFocus?.(ev) + }, + [onFocus], + ) + const handleBlur = useCallback( + (ev) => { + if (!isEager && editingValue !== undefined) { + onChange(editingValue) + } + setEditingValue() + onBlur?.(ev) + }, + [onBlur, editingValue], + ) + const handleKeyDown = useCallback((ev) => { + if (ev.key === 'Enter') { + handleBlur?.(ev) + } + }) + const handleChange = useCallback( + (ev) => { + const { value } = ev.target + setEditingValue(value) + if (isEager) { + onChange(value) + } + }, + [onChange, isEager], + ) + return ( + + ) +} + function GridInput({ style, idx, @@ -900,25 +956,14 @@ function GridInput({ onFocus, onBlur, }) { - const [editingValue, setEditingValue] = useState() - const handleFocus = useCallback( - (ev) => { - setEditingValue(ev.target.value) - onFocus(idx) - }, - [onFocus, idx], - ) - const handleBlur = useCallback( - (ev) => { - setEditingValue(undefined) - onBlur(idx) - }, - [onBlur, idx], - ) + const handleFocus = useCallback(() => { + onFocus(idx) + }, [onFocus, idx]) + const handleBlur = useCallback(() => { + onBlur(idx) + }, [onBlur, idx]) const handleChange = useCallback( - (ev) => { - const { value } = ev.target - setEditingValue(value) + (value) => { onChangeSpace(idx, value) }, [idx, onChangeSpace], @@ -926,7 +971,7 @@ function GridInput({ return ( ) @@ -1066,31 +1112,31 @@ function GridControls({ function CustomStreamInput({ onChange, ...props }) { const handleChangeLink = useCallback( - (ev) => { - onChange(props.link, { ...props, link: ev.target.value }) + (value) => { + onChange(props.link, { ...props, link: value }) }, - [onChange], + [onChange, props.link], ) const handleChangeLabel = useCallback( - (ev) => { - onChange(props.link, { ...props, label: ev.target.value }) + (value) => { + onChange(props.link, { ...props, label: value }) }, - [onChange], + [onChange, props.link], ) const handleChangeKind = useCallback( (ev) => { onChange(props.link, { ...props, kind: ev.target.value }) }, - [onChange], + [onChange, props.link], ) return ( -
- + - overlay -
+ + ) +} + +function CreateCustomStreamInput({ onCreate }) { + const [stream, setStream] = useState({ link: '' }) + const handleChangeStream = useCallback((oldLink, stream) => { + setStream(stream) + }) + const handleSubmit = useCallback( + (ev) => { + ev.preventDefault() + onCreate(stream.link, stream) + setStream({ link: '' }) + }, + [onCreate, stream], + ) + return ( +
+ + + ) } @@ -1237,7 +1304,7 @@ const StyledGridButtons = styled.div` } ` -const StyledGridInput = styled.input` +const StyledGridInput = styled(LazyChangeInput)` width: 100%; height: 100%; outline: 1px solid black;