mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 09:55:01 +00:00
feat: add drag-and-drop functionality for operation reordering in ParamOverrideEditorModal
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
.idea
|
||||
.vscode
|
||||
.zed
|
||||
.history
|
||||
upload
|
||||
*.exe
|
||||
*.db
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
key={operation.id}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
draggable={operations.length > 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',
|
||||
}}
|
||||
>
|
||||
<div className='flex items-start justify-between gap-2'>
|
||||
<div>
|
||||
<Text strong>{`#${index + 1}`}</Text>
|
||||
<Text
|
||||
type='tertiary'
|
||||
size='small'
|
||||
className='block mt-1'
|
||||
<div className='flex items-start gap-2 min-w-0'>
|
||||
<div
|
||||
className='flex-shrink-0'
|
||||
style={{
|
||||
color: 'var(--semi-color-text-2)',
|
||||
cursor: operations.length > 1 ? 'grab' : 'default',
|
||||
marginTop: 1,
|
||||
}}
|
||||
>
|
||||
{getOperationSummary(operation, index)}
|
||||
</Text>
|
||||
{String(operation.description || '').trim() ? (
|
||||
<IconMenu />
|
||||
</div>
|
||||
<div className='min-w-0'>
|
||||
<Text strong>{`#${index + 1}`}</Text>
|
||||
<Text
|
||||
type='tertiary'
|
||||
size='small'
|
||||
className='block mt-1'
|
||||
style={{
|
||||
lineHeight: 1.5,
|
||||
wordBreak: 'break-word',
|
||||
overflow: 'hidden',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
}}
|
||||
>
|
||||
{operation.description}
|
||||
{getOperationSummary(operation, index)}
|
||||
</Text>
|
||||
) : null}
|
||||
{String(operation.description || '').trim() ? (
|
||||
<Text
|
||||
type='tertiary'
|
||||
size='small'
|
||||
className='block mt-1'
|
||||
style={{
|
||||
lineHeight: 1.5,
|
||||
wordBreak: 'break-word',
|
||||
overflow: 'hidden',
|
||||
display: '-webkit-box',
|
||||
WebkitLineClamp: 2,
|
||||
WebkitBoxOrient: 'vertical',
|
||||
}}
|
||||
>
|
||||
{operation.description}
|
||||
</Text>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<Tag size='small' color='grey'>
|
||||
{(operation.conditions || []).length}
|
||||
|
||||
Reference in New Issue
Block a user