package/src/containers/ObjectCoordinates/ObjectCoordinates.jsx (140 lines of code) (raw):
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { updateLabels, updateTables } from '@/actions/markup'
import { Input } from '@/components/Input'
import { useComponentWillUnmount } from '@/hooks/useComponentWillUnmount'
import { usePrevious } from '@/hooks/usePrevious'
import { Label, labelShape } from '@/models/Label'
import * as Table from '@/models/Table'
import { tableShape } from '@/models/Table'
import { getPosition } from '@/models/Table/getPosition'
import { getSize } from '@/models/Table/getSize'
import { pageSelectedLabelsSelector, pageSelectedTablesSelector } from '@/selectors/markup'
import { currentPageSelector } from '@/selectors/pagination'
import { InputPrefix, Param, InputsWrapper } from './ObjectCoordinates.styles'
const INPUT_LENGTH = 6
const CoordSymbol = {
X: 'x',
Y: 'y',
W: 'w',
H: 'h'
}
const TABLE_TYPE_NAME = 'table'
const coordsToSlicedString = (coords) => Object.fromEntries(
Object.entries(coords).map(([param, value]) => (
[param, String(value).slice(0, 6)]
))
)
const getInitialCoords = (selected) => {
if (selected.typeName === TABLE_TYPE_NAME) {
return coordsToSlicedString({
...getPosition(selected),
...getSize(selected)
})
}
return coordsToSlicedString(Label.toRectangle(selected))
}
const ObjectCoordinates = ({
currentPage,
selectedLabels: [selectedLabel],
selectedTables: [selectedTable],
updateLabels,
updateTables
}) => {
const selectedObject = selectedLabel ?? selectedTable
const prevSelectedObject = usePrevious(selectedObject)
const [coords, setCoords] = useState(getInitialCoords(selectedObject))
const lastParam = useRef()
const updateObject = useCallback((param) => {
if (!param) {
return
}
const numericValue = Number(coords[param])
if (selectedTable) {
const tableWithNewPos = param === CoordSymbol.X
? Table.setPosition(selectedTable, { x: numericValue })
: Table.setPosition(selectedTable, { y: numericValue })
return updateTables(currentPage, [tableWithNewPos])
}
return updateLabels(currentPage, [{
...selectedLabel,
[param]: numericValue
}])
}, [
coords,
currentPage,
selectedLabel,
selectedTable,
updateLabels,
updateTables
])
useComponentWillUnmount(() => updateObject(lastParam.current))
useEffect(() => {
if (prevSelectedObject !== selectedObject) {
setCoords(getInitialCoords(selectedObject))
}
}, [
coords,
prevSelectedObject,
selectedObject,
selectedLabel,
selectedTable,
updateObject
])
const onChangeCoord = (value, param) => {
lastParam.current = param
setCoords((prevCoords) => ({
...prevCoords,
[param]: `${value}`
}))
}
const isInputDisabled = useCallback(
(param) => !!selectedTable && (param === CoordSymbol.W || param === CoordSymbol.H),
[selectedTable]
)
const getCoordPrefix = (prefix) => (
<InputPrefix>
<Param>{prefix}</Param>
</InputPrefix>
)
const renderInputs = useMemo(() => (
Object.keys(coords).map((param) => (
<Input
key={param}
disabled={isInputDisabled(param)}
maxLength={INPUT_LENGTH}
onBlur={() => updateObject(param)}
onChange={(e) => onChangeCoord(e.target.value, param)}
onPressEnter={() => updateObject(param)}
prefix={getCoordPrefix(param)}
value={coords[param]}
/>
))
), [coords, isInputDisabled, updateObject])
return (
<InputsWrapper>
{renderInputs}
</InputsWrapper>
)
}
ObjectCoordinates.propTypes = {
currentPage: PropTypes.number.isRequired,
updateLabels: PropTypes.func.isRequired,
updateTables: PropTypes.func.isRequired,
selectedLabels: PropTypes.arrayOf(labelShape).isRequired,
selectedTables: PropTypes.arrayOf(tableShape).isRequired
}
const mapStateToProps = (state) => ({
currentPage: currentPageSelector(state),
selectedLabels: pageSelectedLabelsSelector(state),
selectedTables: pageSelectedTablesSelector(state)
})
const mapDispatchToProps = {
updateLabels,
updateTables
}
const ConnectedComponent = connect(mapStateToProps, mapDispatchToProps)(ObjectCoordinates)
export {
ConnectedComponent as ObjectCoordinates
}