Skip to content

Commit

Permalink
Merge pull request #6442 from TheThingsNetwork/fix/refactor-table-com…
Browse files Browse the repository at this point in the history
…ponent

Refactor table component
  • Loading branch information
ryaplots authored Aug 7, 2023
2 parents 7aed8e5 + b422d30 commit f613532
Show file tree
Hide file tree
Showing 8 changed files with 514 additions and 525 deletions.
88 changes: 36 additions & 52 deletions pkg/webui/components/button/modal-button/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright © 2019 The Things Network Foundation, The Things Industries B.V.
// Copyright © 2023 The Things Network Foundation, The Things Industries B.V.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -12,73 +12,57 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import React from 'react'
import bind from 'autobind-decorator'
import React, { useCallback } from 'react'

import PortalledModal from '@ttn-lw/components/modal/portalled'

import PropTypes from '@ttn-lw/lib/prop-types'

import Button from '..'

/**
* ModalButton is a button which needs a modal confirmation to complete the
* action. It can be used as an easy way to get the users explicit confirmation
* before doing an action, e.g. Deleting a resource.
*/
class ModalButton extends React.Component {
constructor(props) {
super(props)

this.state = {
modalVisible: false,
}
}

@bind
handleClick() {
const { modalData } = this.props
// ModalButton is a button which needs a modal confirmation to complete the
// action. It can be used as an easy way to get the users explicit confirmation
// before doing an action, e.g. Deleting a resource.
const ModalButton = ({ modalData, message, onApprove, onCancel, ...rest }) => {
const [modalVisible, setModalVisible] = React.useState(false)

const handleClick = useCallback(() => {
if (!modalData) {
// No modal data likely means a faulty implementation, so since it's
// likely best to not do anything in this case
return
}

this.setState({ modalVisible: true })
}

@bind
handleComplete(confirmed) {
const { onApprove, onCancel } = this.props

if (confirmed) {
onApprove()
} else {
onCancel()
}
this.setState({ modalVisible: false })
setModalVisible(true)
}, [modalData])

const handleComplete = useCallback(
confirmed => {
if (confirmed) {
onApprove()
} else {
onCancel()
}
setModalVisible(false)
},
[onCancel, onApprove],
)

const modalComposedData = {
approval: true,
danger: true,
buttonMessage: message,
title: message,
onComplete: handleComplete,
...modalData,
}

render() {
const { modalData, message, onApprove, onCancel, ...rest } = this.props

const modalComposedData = {
approval: true,
danger: true,
buttonMessage: message,
title: message,
onComplete: this.handleComplete,
...modalData,
}

return (
<React.Fragment>
<PortalledModal visible={this.state.modalVisible} {...modalComposedData} />
<Button onClick={this.handleClick} message={message} {...rest} />
</React.Fragment>
)
}
return (
<React.Fragment>
<PortalledModal visible={modalVisible} {...modalComposedData} />
<Button onClick={handleClick} message={message} {...rest} />
</React.Fragment>
)
}

ModalButton.defaultProps = {
Expand Down
200 changes: 93 additions & 107 deletions pkg/webui/components/key-value-map/entry.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import React from 'react'
import bind from 'autobind-decorator'
import React, { useCallback, useMemo } from 'react'
import { defineMessages } from 'react-intl'
import classnames from 'classnames'

Expand All @@ -27,117 +26,102 @@ const m = defineMessages({
deleteEntry: 'Delete entry',
})

class Entry extends React.Component {
static propTypes = {
readOnly: PropTypes.bool,
}
static defaultProps = { readOnly: false }

_getKeyInputName() {
const { name, index } = this.props

return `${name}[${index}].key`
}

_getValueInputName() {
const { name, index } = this.props

return `${name}[${index}].value`
}

@bind
handleRemoveButtonClicked(event) {
const { onRemoveButtonClick, index } = this.props

onRemoveButtonClick(index, event)
}

@bind
handleKeyChanged(newKey) {
const { onChange, index } = this.props

onChange(index, { key: newKey })
}

@bind
handleValueChanged(newValue) {
const { onChange, index } = this.props

onChange(index, { value: newValue })
}

@bind
handleBlur(event) {
const { name, onBlur, value } = this.props

const { relatedTarget } = event
const nextTarget = relatedTarget || {}

if (
nextTarget.name !== this._getKeyInputName() &&
nextTarget.name !== this._getValueInputName()
) {
onBlur({
target: {
name,
value,
},
})
}
}

render() {
const {
keyPlaceholder,
valuePlaceholder,
value,
indexAsKey,
readOnly,
inputElement: InputElement,
additionalInputProps,
} = this.props

return (
<div className={style.entriesRow}>
{!indexAsKey && (
<InputElement
data-test-id={this._getKeyInputName()}
className={style.input}
name={this._getKeyInputName()}
placeholder={keyPlaceholder}
type="text"
onChange={this.handleKeyChanged}
onBlur={this.handleBlur}
value={value.key}
readOnly={readOnly}
code
{...additionalInputProps}
/>
)}
const Entry = ({
readOnly,
name,
value,
index,
onRemoveButtonClick,
onChange,
onBlur,
inputElement: InputElement,
indexAsKey,
valuePlaceholder,
keyPlaceholder,
additionalInputProps,
}) => {
const _getKeyInputName = useMemo(() => `${name}[${index}].key`, [index, name])

const _getValueInputName = useMemo(() => `${name}[${index}].value`, [index, name])

const handleRemoveButtonClicked = useCallback(
event => {
onRemoveButtonClick(index, event)
},
[index, onRemoveButtonClick],
)

const handleKeyChanged = useCallback(
newKey => {
onChange(index, { key: newKey })
},
[index, onChange],
)

const handleValueChanged = useCallback(
newValue => {
onChange(index, { value: newValue })
},
[index, onChange],
)

const handleBlur = useCallback(
event => {
const { relatedTarget } = event
const nextTarget = relatedTarget || {}

if (nextTarget.name !== _getKeyInputName && nextTarget.name !== _getValueInputName) {
onBlur({
target: {
name,
value,
},
})
}
},
[onBlur, name, value, _getKeyInputName, _getValueInputName],
)

return (
<div className={style.entriesRow}>
{!indexAsKey && (
<InputElement
data-test-id={this._getValueInputName()}
className={classnames(style.input, { [style.inputIndexAsKey]: indexAsKey })}
name={this._getValueInputName()}
placeholder={valuePlaceholder}
data-test-id={_getKeyInputName}
className={style.input}
name={_getKeyInputName}
placeholder={keyPlaceholder}
type="text"
onChange={this.handleValueChanged}
onBlur={this.handleBlur}
value={indexAsKey ? value : value.value}
onChange={handleKeyChanged}
onBlur={handleBlur}
value={value.key}
readOnly={readOnly}
code
{...additionalInputProps}
/>
<Button
type="button"
onClick={this.handleRemoveButtonClicked}
icon="delete"
title={m.deleteEntry}
disabled={readOnly}
danger
/>
</div>
)
}
)}
<InputElement
data-test-id={_getValueInputName}
className={classnames(style.input, { [style.inputIndexAsKey]: indexAsKey })}
name={_getValueInputName}
placeholder={valuePlaceholder}
type="text"
onChange={handleValueChanged}
onBlur={handleBlur}
value={indexAsKey ? value : value.value}
readOnly={readOnly}
code
{...additionalInputProps}
/>
<Button
type="button"
onClick={handleRemoveButtonClicked}
icon="delete"
title={m.deleteEntry}
disabled={readOnly}
danger
/>
</div>
)
}

Entry.propTypes = {
Expand All @@ -150,6 +134,7 @@ Entry.propTypes = {
onBlur: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onRemoveButtonClick: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
value: PropTypes.oneOfType([
PropTypes.shape({
key: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
Expand All @@ -162,6 +147,7 @@ Entry.propTypes = {

Entry.defaultProps = {
value: undefined,
readOnly: false,
}

export default Entry
Loading

0 comments on commit f613532

Please sign in to comment.