Replace arrow buttons with drag-and-drop reordering using drag handles

Co-authored-by: thomasnordquist <7721625+thomasnordquist@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-12-23 07:55:56 +00:00
co-authored by thomasnordquist
parent 1a8a9224b4
commit be83c3f2cc
2 changed files with 77 additions and 23 deletions
@@ -1,7 +1,7 @@
import React, { useCallback } from 'react'
import { connect } from 'react-redux'
import { ListItem, Typography, IconButton, Box } from '@mui/material'
import { ArrowUpward, ArrowDownward } from '@mui/icons-material'
import { ListItem, Typography, Box } from '@mui/material'
import { DragIndicator } from '@mui/icons-material'
import { toMqttConnection, ConnectionOptions } from '../../../model/ConnectionOptions'
import { withStyles } from '@mui/styles'
import { Theme } from '@mui/material/styles'
@@ -16,6 +16,9 @@ export interface Props {
}
selected: boolean
classes: any
onDragStart: (connectionId: string) => void
onDragOver: (e: React.DragEvent) => void
onDrop: (connectionId: string) => void
}
const ConnectionItem = (props: Props) => {
@@ -26,14 +29,21 @@ const ConnectionItem = (props: Props) => {
}
}, [props.connection, props])
const handleMoveUp = (e: React.MouseEvent) => {
const handleDragStart = (e: React.DragEvent) => {
e.stopPropagation()
props.actions.connectionManager.moveConnection(props.connection.id, 'up')
props.onDragStart(props.connection.id)
}
const handleMoveDown = (e: React.MouseEvent) => {
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
props.actions.connectionManager.moveConnection(props.connection.id, 'down')
props.onDragOver(e)
}
const handleDrop = (e: React.DragEvent) => {
e.preventDefault()
e.stopPropagation()
props.onDrop(props.connection.id)
}
const connection = props.connection.host && toMqttConnection(props.connection)
@@ -47,19 +57,20 @@ const ConnectionItem = (props: Props) => {
props.actions.connectionManager.selectConnection(props.connection.id)
connect()
}}
onDragOver={handleDragOver}
onDrop={handleDrop}
>
<Box
className={props.classes.dragHandle}
draggable
onDragStart={handleDragStart}
>
<DragIndicator fontSize="small" />
</Box>
<Box className={props.classes.textContainer}>
<Typography className={props.classes.name}>{props.connection.name || 'mqtt broker'}</Typography>
<Typography className={props.classes.details}>{connection && connection.url}</Typography>
</Box>
<Box className={props.classes.buttonContainer}>
<IconButton size="small" onClick={handleMoveUp} className={props.classes.arrowButton}>
<ArrowUpward fontSize="small" />
</IconButton>
<IconButton size="small" onClick={handleMoveDown} className={props.classes.arrowButton}>
<ArrowDownward fontSize="small" />
</IconButton>
</Box>
</ListItem>
)
}
@@ -90,19 +101,21 @@ export const connectionItemStyle = (theme: Theme) => ({
itemContainer: {
display: 'flex' as 'flex',
alignItems: 'center' as 'center',
padding: '8px 8px 8px 16px',
padding: '8px 8px 8px 8px',
},
textContainer: {
flex: 1,
overflow: 'hidden' as 'hidden',
},
buttonContainer: {
dragHandle: {
display: 'flex' as 'flex',
flexDirection: 'column' as 'column',
marginLeft: '4px',
},
arrowButton: {
padding: '2px',
alignItems: 'center' as 'center',
marginRight: '8px',
cursor: 'grab' as 'grab',
color: theme.palette.text.secondary,
'&:active': {
cursor: 'grabbing' as 'grabbing',
},
},
})
@@ -1,6 +1,6 @@
import ConnectionItem from './ConnectionItem'
const ConnectionItemAny = ConnectionItem as any
import React from 'react'
import React, { useState } from 'react'
import { AddButton } from './AddButton'
import { AppState } from '../../../reducers'
import { bindActionCreators } from 'redux'
@@ -22,6 +22,7 @@ interface Props {
function ProfileList(props: Props) {
const { actions, classes, connections, selected } = props
const [draggedConnectionId, setDraggedConnectionId] = useState<string | null>(null)
const selectConnection = (dir: 'next' | 'previous') => (event: KeyboardEvent) => {
if (!selected) {
@@ -40,6 +41,39 @@ function ProfileList(props: Props) {
useGlobalKeyEventHandler(KeyCodes.arrow_down, selectConnection('next'))
useGlobalKeyEventHandler(KeyCodes.arrow_up, selectConnection('previous'))
const handleDragStart = (connectionId: string) => {
setDraggedConnectionId(connectionId)
}
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault()
}
const handleDrop = (targetConnectionId: string) => {
if (!draggedConnectionId || draggedConnectionId === targetConnectionId) {
setDraggedConnectionId(null)
return
}
const sortedConnections = Object.values(connections).sort((a, b) => (a.order || 0) - (b.order || 0))
const draggedIndex = sortedConnections.findIndex(c => c.id === draggedConnectionId)
const targetIndex = sortedConnections.findIndex(c => c.id === targetConnectionId)
if (draggedIndex === -1 || targetIndex === -1) {
setDraggedConnectionId(null)
return
}
// Swap order values
const draggedConnection = sortedConnections[draggedIndex]
const targetConnection = sortedConnections[targetIndex]
actions.updateConnection(draggedConnection.id, { order: targetConnection.order })
actions.updateConnection(targetConnection.id, { order: draggedConnection.order })
setDraggedConnectionId(null)
}
const createConnectionButton = (
<div style={{ padding: '8px 16px' }}>
<AddButton action={actions.createConnection} />
@@ -53,7 +87,14 @@ function ProfileList(props: Props) {
{Object.values(connections)
.sort((a, b) => (a.order || 0) - (b.order || 0))
.map(connection => (
<ConnectionItemAny connection={connection} key={connection.id} selected={selected === connection.id} />
<ConnectionItemAny
connection={connection}
key={connection.id}
selected={selected === connection.id}
onDragStart={handleDragStart}
onDragOver={handleDragOver}
onDrop={handleDrop}
/>
))}
</div>
</List>