Skip to content

Commit

Permalink
feat: 完成task拖拽调整顺序
Browse files Browse the repository at this point in the history
  • Loading branch information
myshell-joe committed Sep 19, 2024
1 parent c4d7acd commit 68c909f
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 52 deletions.
6 changes: 4 additions & 2 deletions web/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@
"@heroicons/react": "^2.1.5",
"@microsoft/fetch-event-source": "^2.0.1",
"@monaco-editor/react": "^4.6.0",
"@paralleldrive/cuid2": "^2.2.2",
"@shellagent/chat-engine": "workspace:*",
"@shellagent/flow-engine": "workspace:*",
"@shellagent/form-engine": "workspace:*",
"@shellagent/pro-config": "workspace:*",
"@shellagent/tailwind-config": "workspace:*",
"@shellagent/ui": "workspace:*",
"@shellagent/chat-engine": "workspace:*",
"@paralleldrive/cuid2": "^2.2.2",
"ahooks": "^3.8.1",
"antd": "^5.20.1",
"axios": "^1.7.4",
Expand All @@ -47,6 +47,8 @@
"next-intl": "^2.22.1",
"next-themes": "^0.3.0",
"react": "^18.3.1",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18.3.1",
"react-draggable": "^4.4.6",
"react-dropzone": "^14.2.3",
Expand Down
191 changes: 141 additions & 50 deletions web/apps/web/src/components/app/node-form/widgets/tasks-config.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { PlusIcon } from '@heroicons/react/24/outline';
import { XMarkIcon } from '@heroicons/react/24/solid';
import { WidgetItem, uuid } from '@shellagent/flow-engine';
import { Button, useFormContext } from '@shellagent/ui';
import { Button, useFormContext, Drag } from '@shellagent/ui';
import { useClickAway } from 'ahooks';
import { Dropdown } from 'antd';
import { useState, useRef, useCallback } from 'react';
import { useDrag, useDrop, DndProvider } from 'react-dnd';

import { HTML5Backend } from 'react-dnd-html5-backend';

import { materialList } from '@/components/app/constants';
import { TaskList } from '@/components/app/task-list';
Expand Down Expand Up @@ -45,9 +48,11 @@ export interface IWidgetTask {
const TasksConfig = ({
name,
onChange,
draggable,
}: {
name: string;
onChange: (value: (IWorkflowTask | IWidgetTask)[]) => void;
draggable?: boolean;
}) => {
const btnRef = useRef<HTMLButtonElement>(null);
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -102,83 +107,169 @@ const TasksConfig = ({
[values, onChange],
);

const moveTask = useCallback(
(dragIndex: number, hoverIndex: number) => {
const draggedTask = values[dragIndex];
const updatedTasks = [...values];
updatedTasks.splice(dragIndex, 1);
updatedTasks.splice(hoverIndex, 0, draggedTask);

onChange(updatedTasks);
},
[values, onChange],
);

return (
<>
{values?.length > 0 && (
<div className="flex flex-col gap-2 mb-1.5">
{values.map((task, idx) => (
<TaskItem
key={task.name}
name={task.display_name}
onDelete={() => handleItemDelete(idx)}
onClick={() => handleItemClick(idx)}
/>
))}
</div>
)}
<Dropdown
placement="bottomRight"
trigger={['click']}
overlayClassName="shadow-modal-default"
overlayStyle={{ borderRadius: 12, overflow: 'hidden' }}
getPopupContainer={() => btnRef.current || document.body}
open={open}
overlay={
<div
onWheelCapture={e => e.stopPropagation()}
className="w-[200px] max-h-[349px] overflow-y-auto"
onClick={e => e.stopPropagation()}>
<TaskList
className="rounded-xl"
data={materialList.slice(1)}
loading={false}
onChange={handleSelect}
/>
<DndProvider backend={HTML5Backend}>
<div>
{values?.length > 0 && (
<div className="flex flex-col gap-2 mb-1.5">
{values.map((task, idx) => (
<TaskItem
key={task.name}
name={task.display_name}
onDelete={() => handleItemDelete(idx)}
onClick={() => handleItemClick(idx)}
index={idx}
moveTask={moveTask}
draggable={draggable} // 传递draggable参数
/>
))}
</div>
}>
<Button
ref={btnRef}
icon={PlusIcon}
onClick={e => {
e.stopPropagation();
setOpen(true);
}}
variant="outline"
size="sm"
type="button"
className="rounded-lg w-18 border-default">
Add
</Button>
</Dropdown>
</>
)}
<Dropdown
placement="bottomRight"
trigger={['click']}
overlayClassName="shadow-modal-default"
overlayStyle={{ borderRadius: 12, overflow: 'hidden' }}
getPopupContainer={() => btnRef.current || document.body}
open={open}
overlay={
<div
onWheelCapture={e => e.stopPropagation()}
className="w-[200px] max-h-[349px] overflow-y-auto"
onClick={e => e.stopPropagation()}>
<TaskList
className="rounded-xl"
data={materialList.slice(1)}
loading={false}
onChange={handleSelect}
/>
</div>
}>
<Button
ref={btnRef}
icon={PlusIcon}
onClick={e => {
e.stopPropagation();
setOpen(true);
}}
variant="outline"
size="sm"
type="button"
className="rounded-lg w-18 border-default">
Add
</Button>
</Dropdown>
</div>
</DndProvider>
);
};

type DragItem = {
index: number;
};

const TaskItem = ({
name,
onDelete,
onClick,
index,
moveTask,
draggable, // 新增参数
}: {
name: string;
onDelete: () => void;
onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
index: number;
moveTask: (dragIndex: number, hoverIndex: number) => void;
draggable?: boolean; // 新增参数类型
}) => {
const dragRef = useRef<HTMLDivElement>(null);
const previewRef = useRef<HTMLDivElement>(null);

const [, drop] = useDrop<DragItem, void>({
accept: 'TASK',
hover: (item: DragItem, monitor) => {
if (!dragRef.current || !draggable) {
// 添加draggable判断
return;
}
const dragIndex = item.index;
const hoverIndex = index;

const hoverBoundingRect = dragRef.current?.getBoundingClientRect();
const hoverMiddleY =
(hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
const clientOffset = monitor.getClientOffset() || { x: 0, y: 0 };
const hoverClientY = clientOffset?.y - hoverBoundingRect.top;

if (
dragIndex === hoverIndex ||
(hoverClientY < hoverMiddleY && dragIndex < hoverIndex) ||
(hoverClientY > hoverMiddleY && dragIndex > hoverIndex)
) {
return;
}

moveTask(dragIndex, hoverIndex);
item.index = hoverIndex;
},
});

const [{ isDragging }, drag, preview] = useDrag({
type: 'TASK',
item: { index },
collect: monitor => ({
isDragging: monitor.isDragging(),
}),
canDrag: draggable,
});

if (draggable) {
drag(drop(dragRef));
preview(drop(previewRef));
}

return (
<div
ref={previewRef}
onClick={e => {
e.stopPropagation();
onClick(e);
}}
className="group h-8 flex items-center justify-between bg-surface-container-default rounded-lg p-2 text-default font-medium cursor-pointer">
className={`relative group h-8 flex items-center bg-surface-container-default rounded-lg p-2 text-default font-medium cursor-pointer ${isDragging ? 'opacity-50' : ''}`}>
{draggable && (
<div
ref={dragRef}
className="w-6 h-6 flex items-center justify-center cursor-grab"
role="Handle">
<Drag size="md" color="subtle" />
</div>
)}
{name}
<XMarkIcon
className="w-4 h-4 hidden group-hover:block"
className="w-4 h-4 hidden group-hover:block ml-auto"
onClick={e => {
e.preventDefault();
e.stopPropagation();
onDelete();
}}
/>
<div
className="absolute top-0 left-0 w-full h-1 bg-blue-500"
style={{ display: isDragging ? 'block' : 'none' }}
/>
</div>
);
};
Expand Down
3 changes: 3 additions & 0 deletions web/apps/web/src/stores/app/utils/get-state-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export const getStateSchema = (name: string): ISchema => {
'x-title-size': 'h4',
'x-collapsible': true,
'x-component': 'TasksConfig',
'x-component-props': {
draggable: false,
},
},
transition: {
type: 'object',
Expand Down
3 changes: 3 additions & 0 deletions web/apps/web/src/stores/app/utils/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,9 @@ const stateConfigSchema: ISchema = {
'x-title-size': 'h4',
'x-collapsible': true,
'x-component': 'TasksConfig',
'x-component-props': {
draggable: true,
},
},
output: {
type: 'object',
Expand Down

0 comments on commit 68c909f

Please sign in to comment.