diff --git a/.gitignore b/.gitignore index 30d76c9d3..67511c0a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .idea .vscode .zed +.history upload *.exe *.db diff --git a/web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx b/web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx index da0c2a8e5..67cbe51c7 100644 --- a/web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx +++ b/web/src/components/table/channels/modals/ParamOverrideEditorModal.jsx @@ -34,7 +34,7 @@ import { TextArea, Typography, } from '@douyinfe/semi-ui'; -import { IconDelete, IconPlus } from '@douyinfe/semi-icons'; +import { IconDelete, IconMenu, IconPlus } from '@douyinfe/semi-icons'; import { copy, showError, showSuccess, verifyJSON } from '../../../../helpers'; import { CLAUDE_CLI_HEADER_PASSTHROUGH_TEMPLATE, @@ -800,6 +800,38 @@ const normalizeOperation = (operation = {}) => ({ const createDefaultOperation = () => normalizeOperation({ mode: 'set' }); +const reorderOperations = ( + sourceOperations = [], + sourceId, + targetId, + position = 'before', +) => { + if (!sourceId || !targetId || sourceId === targetId) { + return sourceOperations; + } + + const sourceIndex = sourceOperations.findIndex((item) => item.id === sourceId); + + if (sourceIndex < 0) { + return sourceOperations; + } + + const nextOperations = [...sourceOperations]; + const [moved] = nextOperations.splice(sourceIndex, 1); + let insertIndex = nextOperations.findIndex((item) => item.id === targetId); + + if (insertIndex < 0) { + return sourceOperations; + } + + if (position === 'after') { + insertIndex += 1; + } + + nextOperations.splice(insertIndex, 0, moved); + return nextOperations; +}; + const getOperationSummary = (operation = {}, index = 0) => { const mode = operation.mode || 'set'; const modeLabel = OPERATION_MODE_LABEL_MAP[mode] || mode; @@ -1037,6 +1069,9 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { const [operationSearch, setOperationSearch] = useState(''); const [selectedOperationId, setSelectedOperationId] = useState(''); const [expandedConditionMap, setExpandedConditionMap] = useState({}); + const [draggedOperationId, setDraggedOperationId] = useState(''); + const [dragOverOperationId, setDragOverOperationId] = useState(''); + const [dragOverPosition, setDragOverPosition] = useState('before'); const [templateGroupKey, setTemplateGroupKey] = useState('basic'); const [templatePresetKey, setTemplatePresetKey] = useState('operations_default'); const [fieldGuideVisible, setFieldGuideVisible] = useState(false); @@ -1055,6 +1090,9 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { setOperationSearch(''); setSelectedOperationId(nextState.operations[0]?.id || ''); setExpandedConditionMap({}); + setDraggedOperationId(''); + setDragOverOperationId(''); + setDragOverPosition('before'); if (nextState.visualMode === 'legacy') { setTemplateGroupKey('basic'); setTemplatePresetKey('legacy_default'); @@ -1583,6 +1621,67 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { setSelectedOperationId(created.id); }; + const resetOperationDragState = useCallback(() => { + setDraggedOperationId(''); + setDragOverOperationId(''); + setDragOverPosition('before'); + }, []); + + const moveOperation = useCallback( + (sourceId, targetId, position = 'before') => { + if (!sourceId || !targetId || sourceId === targetId) { + return; + } + setOperations((prev) => + reorderOperations(prev, sourceId, targetId, position), + ); + setSelectedOperationId(sourceId); + }, + [], + ); + + const handleOperationDragStart = useCallback((event, operationId) => { + setDraggedOperationId(operationId); + setSelectedOperationId(operationId); + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', operationId); + }, []); + + const handleOperationDragOver = useCallback( + (event, operationId) => { + event.preventDefault(); + if (!draggedOperationId || draggedOperationId === operationId) { + return; + } + const rect = event.currentTarget.getBoundingClientRect(); + const position = + event.clientY - rect.top > rect.height / 2 ? 'after' : 'before'; + setDragOverOperationId(operationId); + setDragOverPosition(position); + event.dataTransfer.dropEffect = 'move'; + }, + [draggedOperationId], + ); + + const handleOperationDrop = useCallback( + (event, operationId) => { + event.preventDefault(); + const sourceId = + draggedOperationId || event.dataTransfer.getData('text/plain'); + const position = + dragOverOperationId === operationId ? dragOverPosition : 'before'; + moveOperation(sourceId, operationId, position); + resetOperationDragState(); + }, + [ + dragOverOperationId, + dragOverPosition, + draggedOperationId, + moveOperation, + resetOperationDragState, + ], + ); + const duplicateOperation = (operationId) => { let insertedId = ''; setOperations((prev) => { @@ -1941,14 +2040,31 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { ); const isActive = operation.id === selectedOperationId; + const isDragging = + operation.id === draggedOperationId; + const isDropTarget = + operation.id === dragOverOperationId && + draggedOperationId && + draggedOperationId !== operation.id; return (
1} onClick={() => setSelectedOperationId(operation.id) } + onDragStart={(event) => + handleOperationDragStart(event, operation.id) + } + onDragOver={(event) => + handleOperationDragOver(event, operation.id) + } + onDrop={(event) => + handleOperationDrop(event, operation.id) + } + onDragEnd={resetOperationDragState} onKeyDown={(event) => { if ( event.key === 'Enter' || @@ -1966,35 +2082,53 @@ const ParamOverrideEditorModal = ({ visible, value, onSave, onCancel }) => { border: isActive ? '1px solid var(--semi-color-primary)' : '1px solid var(--semi-color-border)', + opacity: isDragging ? 0.6 : 1, + boxShadow: isDropTarget + ? dragOverPosition === 'after' + ? 'inset 0 -3px 0 var(--semi-color-primary)' + : 'inset 0 3px 0 var(--semi-color-primary)' + : 'none', }} >
-
- {`#${index + 1}`} - +
1 ? 'grab' : 'default', + marginTop: 1, + }} > - {getOperationSummary(operation, index)} - - {String(operation.description || '').trim() ? ( + +
+
+ {`#${index + 1}`} - {operation.description} + {getOperationSummary(operation, index)} - ) : null} + {String(operation.description || '').trim() ? ( + + {operation.description} + + ) : null} +
{(operation.conditions || []).length}