feat: add preview and change edit and flow

This commit is contained in:
2024-09-18 23:11:13 +08:00
parent b838488776
commit 052dd919cd
13 changed files with 755 additions and 58 deletions

View File

@@ -4,7 +4,8 @@ import { TextArea } from '../components/TextArea';
import { useContainerStore } from '../store';
import { useShallow } from 'zustand/react/shallow';
import { Form } from 'antd';
import copy from 'copy-to-clipboard';
import { useNavigate } from 'react-router';
const FormModal = () => {
const [form] = Form.useForm();
const containerStore = useContainerStore(
@@ -73,6 +74,7 @@ const FormModal = () => {
);
};
export const ContainerList = () => {
const navicate = useNavigate();
const containerStore = useContainerStore(
useShallow((state) => {
return {
@@ -90,10 +92,23 @@ export const ContainerList = () => {
}, []);
const columns = [
// {
// title: 'ID',
// dataIndex: 'id',
// },
{
title: 'ID',
dataIndex: 'id',
render: (text: string) => {
return (
<div
className='w-40 truncate cursor-pointer'
title={text}
onClick={() => {
copy(text);
message.success('copy success');
}}>
{text}
</div>
);
},
},
{
title: 'Title',
dataIndex: 'title',
@@ -124,6 +139,12 @@ export const ContainerList = () => {
}}>
Edit
</Button>
<Button
onClick={() => {
navicate('/container/preview/' + record.id);
}}>
Preview
</Button>
<Button
danger
onClick={() => {

View File

@@ -1,13 +1,15 @@
import { Navigate, Route, Routes } from 'react-router-dom';
import { ContainerList } from './edit/List';
import { Main } from './layouts';
import { Preview } from './preview';
export const App = () => {
return (
<Routes>
<Route element={<Main />}>
<Route path='/' element={<Navigate to='/container/edit/list' />}></Route>
<Route path='edit/list' element={<ContainerList />} />
<Route path='preview/:id' element={<Preview />} />
<Route path='/' element={<div>Home</div>} />
</Route>
</Routes>

View File

@@ -0,0 +1,61 @@
import { Container } from '@abearxiong/container';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router';
import { query } from '@/modules';
import { message } from 'antd';
export const Preview = () => {
const params = useParams<{ id: string }>();
const id = params.id;
const ref = useRef<HTMLDivElement>(null);
const containerRef = useRef<any>(null);
const [data, setData] = useState<any>({});
useEffect(() => {
if (!id) return;
fetch();
}, []);
const fetch = async () => {
const res = await query.post({
path: 'container',
key: 'get',
id,
});
if (res.code === 200) {
const data = res.data;
// setData([data]);
console.log('data', data);
const code = {
id: data.id,
title: data.title,
code: data.code,
data: data.data,
};
init([code]);
} else {
message.error(res.msg || 'Failed to fetch data');
}
};
const init = async (data: any[]) => {
// console.log('data', data, ref.current);
const container = new Container({
root: ref.current!,
data: data as any,
showChild: false,
});
container.render(id!);
containerRef.current = container;
};
return (
<div className='w-full h-full bg-gray-200'>
<div className='text-center mb-10 font-bold text-4xl mt-4 '>Preview</div>
<div
className='flex '
style={{
height: 'calc(100% - 32px)',
}}>
<div className='mx-auto border bg-white h-h-full w-[80%] h-[80%]' ref={ref}></div>
</div>
</div>
);
};

View File

@@ -0,0 +1,101 @@
import { Container } from '@abearxiong/container';
import { useEffect, useRef, useState } from 'react';
import { useParams } from 'react-router';
import { query } from '@/modules';
import { message } from 'antd';
export const Deck = () => {
const params = useParams<{ id: string }>();
const id = params.id;
const ref = useRef<HTMLDivElement>(null);
const containerRef = useRef<any>(null);
const [data, setData] = useState<any>({});
useEffect(() => {
if (!id) return;
fetch();
}, []);
const fetch = async () => {
const res = await query.post({
path: 'page',
key: 'getDeck',
id,
});
if (res.code === 200) {
const data = res.data;
console.log('data', data);
const { page, containerList } = data;
const { edges, nodes } = page.data;
for (let edge of edges) {
const { source, target } = edge;
const node = nodes.find((node: any) => node.id === source);
if (!node) continue;
node.children = node.children || [];
node.children.push(target);
}
for (let node of nodes) {
const container = containerList.find((container: any) => container.id === node.data?.cid);
if (container) {
node.container = container;
}
}
const codes = nodes.map((node: any) => {
const container = node.container;
const data = container?.data || {};
return {
id: node.id,
title: node.title,
code: container?.code || '',
data: data,
children: node.children,
...data, // style className shadowRoot showChild
};
});
// const code = {
// id: data.id,
// title: data.title,
// code: data.code,
// data: data.data,
// };
// init([code]);
init(codes);
console.log('codes', codes);
}
// if (res.code === 200) {
// const data = res.data;
// // setData([data]);
// console.log('data', data);
// const code = {
// id: data.id,
// title: data.title,
// code: data.code,
// data: data.data,
// };
// init([code]);
// } else {
// message.error(res.msg || 'Failed to fetch data');
// }
};
const init = async (data: any[]) => {
// console.log('data', data, ref.current);
const container = new Container({
root: ref.current!,
data: data as any,
showChild: true,
});
container.render(id!);
containerRef.current = container;
};
return (
<div className='w-full h-full bg-gray-200'>
<div className='text-center mb-10 font-bold text-4xl mt-4 '>Preview</div>
<div
className='flex '
style={{
height: 'calc(100% - 32px)',
}}>
<div className='mx-auto border bg-white h-h-full w-[80%] h-[80%]' ref={ref}></div>
</div>
</div>
);
};

View File

@@ -1,3 +1,172 @@
export const List = () => {
return <div>List</div>;
import { useEditStore } from '../store';
import { Button, Input, message, Modal, Table } from 'antd';
import { useEffect, useState } from 'react';
import { useShallow } from 'zustand/react/shallow';
import { Form } from 'antd';
import copy from 'copy-to-clipboard';
import { useNavigate } from 'react-router';
const FormModal = () => {
const [form] = Form.useForm();
const editStore = useEditStore(
useShallow((state) => {
return {
showEdit: state.showEditModal,
setShowEdit: state.setShowEditModal,
formData: state.formData,
updateData: state.updateData,
};
}),
);
useEffect(() => {
const open = editStore.showEdit;
if (open) {
form.setFieldsValue(editStore.formData || {});
} else {
form.resetFields();
}
}, [editStore.showEdit]);
const onFinish = async (values: any) => {
editStore.updateData(values);
};
const onClose = () => {
editStore.setShowEdit(false);
form.resetFields();
};
const isEdit = editStore.formData.id;
return (
<Modal title={isEdit ? 'Edit' : 'Add'} open={editStore.showEdit} onClose={onClose} destroyOnClose footer={false} width={800} onCancel={onClose}>
<Form
form={form}
onFinish={onFinish}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 20,
}}>
<Form.Item name='id' hidden>
<Input />
</Form.Item>
<Form.Item name='title' label='title'>
<Input />
</Form.Item>
{/* <Form.Item name='code' label='code'>
<TextArea value={containerStore.formData.code} />
</Form.Item> */}
<Form.Item label=' ' colon={false}>
<Button type='primary' htmlType='submit'>
Submit
</Button>
<Button className='ml-2' htmlType='reset' onClick={onClose}>
Cancel
</Button>
</Form.Item>
</Form>
</Modal>
);
};
export const List = () => {
const navicate = useNavigate();
const editStore = useEditStore(
useShallow((state) => {
return {
setFormData: state.setFormData,
setShowEdit: state.setShowEditModal,
list: state.list,
deleteData: state.deleteData,
getList: state.getList,
loading: state.loading,
};
}),
);
useEffect(() => {
editStore.getList();
}, []);
const columns = [
{
title: 'ID',
dataIndex: 'id',
render: (text: string) => {
return (
<div
className='w-40 truncate cursor-pointer'
title={text}
onClick={() => {
copy(text);
message.success('copy success');
}}>
{text}
</div>
);
},
},
{
title: 'Title',
dataIndex: 'title',
},
{
title: 'Operation',
dataIndex: 'operation',
render: (text: string, record: any) => {
return (
<div className='flex gap-2'>
<Button
type='primary'
onClick={() => {
editStore.setFormData(record);
editStore.setShowEdit(true);
}}>
Edit
</Button>
<Button
onClick={() => {
navicate('/container/preview/' + record.id);
}}>
Preview
</Button>
<Button
danger
onClick={() => {
editStore.deleteData(record.id);
}}>
Delete
</Button>
</div>
);
},
},
];
return (
<div className='w-full h-full flex flex-col'>
<div className='mb-2 w-full p-2 bg-white rounded-lg'>
<Button
className='w-20 '
type='primary'
onClick={() => {
editStore.setFormData({});
editStore.setShowEdit(true);
}}>
Add
</Button>
</div>
<div className='flex-grow overflow-scroll'>
<Table
pagination={false}
scroll={{
y: 600,
}}
loading={editStore.loading}
dataSource={editStore.list}
rowKey='id'
columns={columns}
/>
</div>
<div className='h-2'></div>
<FormModal />
</div>
);
};

View File

@@ -14,33 +14,17 @@ import {
useStoreApi,
Panel,
} from '@xyflow/react';
import type { Node } from '@xyflow/react';
import '@xyflow/react/dist/style.css';
import { useShallow } from 'zustand/react/shallow';
import { useAddNode } from '@abearxiong/flows';
import { Router, Container } from '@abearxiong/flows';
console.log("R", Router);
import { useContainerMenu, useRouterMenu } from '@abearxiong/flows';
import { Container, useAddNode, ContainerMenusList, useMenuFlow } from '@abearxiong/flows';
import { useMenuEmitter, ContainerMenusKeys } from '@abearxiong/flows';
import { nanoid } from 'nanoid';
import { Button } from 'antd';
import { usePanelStore } from '../store';
// router: Router
const nodeTypes = {
container: Container,
};
export const initialNodes: Node[] = [
{ id: '1', position: { x: 100, y: 100 }, data: { label: '1' } },
{ id: '2', position: { x: 100, y: 400 }, data: { label: '2' } },
{ id: '3', position: { x: 100, y: 700 }, data: { label: '3' }, drag: true },
].map((node) => ({
...node,
// type: 'router',
type: 'container',
position: {
x: node.position.y,
y: node.position.x,
},
}));
export const initialEdges = [{ id: 'e1-2', source: '1', target: '2' }];
export const Flow = () => {
return (
@@ -50,17 +34,58 @@ export const Flow = () => {
);
};
const ReactFlowApp = () => {
const [nodes, setNodes, onNodesChange] = useNodesState([...initialNodes]);
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([...initialEdges]);
const reactFlow = useReactFlow();
const [nodes, setNodes, onNodesChange] = useNodesState([]);
const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]);
const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]);
const panelStore = usePanelStore(
useShallow((state) => {
return {
data: state.data,
saveNodesEdges: state.saveNodesEdges,
};
}),
);
useEffect(() => {
if (panelStore.data?.id) {
const { data } = panelStore.data || {};
const nodes = data.nodes || [];
const edges = data.edges || [];
setNodes(nodes);
setEdges(edges);
} else {
setNodes([]);
setEdges([]);
}
}, [panelStore.data]);
const { menuCom, onContextMenu, onClose } = useMenuFlow(
(item, cacheData) => {
emit({
menu: item,
data: cacheData,
});
},
{
menusList: ContainerMenusList,
},
);
const { menuCom, onContextMenu, onClose } = useContainerMenu((item, cacheData) => {
console.log(item, cacheData);
const { emit } = useMenuEmitter<ContainerMenusKeys>({
preview: ({ menu, data }) => {
console.log('preview', data);
if (data?.data?.cid) {
window.open(`/container/preview/${data.data.cid}`, '_blank');
}
},
});
const { onNeedAdd, onAdd, onMouseMove, adding } = useAddNode();
const onSave = useCallback(() => {
panelStore.saveNodesEdges({
nodes,
edges,
});
}, [nodes, edges]);
return (
<ReactFlow
nodes={nodes}
@@ -99,16 +124,25 @@ const ReactFlowApp = () => {
}}>
<MiniMap />
<Controls />
<Background gap={[14, 14]} size={2} color='#E4E5E7' />
{/* <Background gap={[14, 14]} size={2} color='#E4E5E7' /> */}
<Background color='#000' />
<Panel>{menuCom}</Panel>
<Panel>
<Button
type='primary'
onClick={(e) => {
onNeedAdd({ id: '5', data: { label: '测试添加按钮' }, type: 'router' }, e);
}}>
</Button>
<div className='flex gap-2'>
<Button
type='primary'
onClick={(e) => {
onNeedAdd({ id: nanoid(6), data: { label: '容器' }, type: 'container' }, e);
}}>
</Button>
<Button
onClick={() => {
onSave();
}}>
</Button>
</div>
</Panel>
</ReactFlow>
);

View File

@@ -1,10 +1,31 @@
import { useParams } from 'react-router';
import Flow from './Flow';
import { usePanelStore } from '../store';
import { useShallow } from 'zustand/react/shallow';
import { useEffect } from 'react';
export const App = () => {
const param = useParams();
const id = param.id;
const panel = usePanelStore(
useShallow((state) => {
return {
getPanel: state.getPanel,
};
}),
);
useEffect(() => {
id && panel.getPanel(id);
}, []);
if (!id) {
return <>No ID</>;
}
return (
<div className='w-full h-full'>
sdf
<Flow />
<div className='w-full h-full p-4'>
<div className='w-full h-full'>
<Flow />
</div>
</div>
);
};

View File

@@ -2,14 +2,15 @@ import { Navigate, Route, Routes } from 'react-router-dom';
import { List } from './edit/List';
import { Main } from './layouts';
import { App as FlowApp } from './flow';
import { Deck } from './deck';
export const App = () => {
return (
<Routes>
<Route element={<Main />}>
<Route path='/' element={<Navigate to='/panel/edit/list' />}></Route>
<Route path='edit/list' element={<List />} />
<Route path='flow' element={<FlowApp />} />
<Route path='flow/:id' element={<FlowApp />} />
<Route path='deck/:id' element={<Deck />} />
<Route path='*' element={'Not Found'}></Route>
</Route>
</Routes>

View File

@@ -26,7 +26,7 @@ export const useEditStore = create<EditStore>((set, get) => {
getList: async () => {
set({ loading: true });
const res = await query.post({ path: 'panel', key: 'list' });
const res = await query.post({ path: 'page', key: 'list' });
set({ loading: false });
if (res.code === 200) {
set({ list: res.data });
@@ -37,7 +37,7 @@ export const useEditStore = create<EditStore>((set, get) => {
updateData: async (data) => {
const { getList } = get();
const res = await query.post({
path: 'panel',
path: 'page',
key: 'update',
data,
});
@@ -52,7 +52,7 @@ export const useEditStore = create<EditStore>((set, get) => {
deleteData: async (id) => {
const { getList } = get();
const res = await query.post({
path: 'panel',
path: 'page',
key: 'delete',
id,
});

View File

@@ -1,14 +1,82 @@
import { create } from 'zustand';
import { query } from '@/modules';
import { message } from 'antd';
import { produce } from 'immer';
type PanelStore = {
id: string;
setId: (id: string) => void;
loading: boolean;
setLoading: (loading: boolean) => void;
data: any;
setData: (data: any) => void;
getPanel: (id?: string) => Promise<void>;
saveNodesEdges: (data: { nodes?: any[]; edges?: any[]; viewport?: any }) => Promise<void>;
};
export const usePanelStore = create<PanelStore>((set, get) => {
return {
id: '',
setId: (id) => set({ id }),
loading: false,
setLoading: (loading) => set({ loading }),
data: {},
setData: (data) => set({ data }),
getPanel: async (pid) => {
const id = pid || get().id;
if (!id) {
message.error('ID is required');
return;
}
set(
produce((state) => {
state.data = {};
state.loading = true;
}),
);
const res = await query.post({
path: 'page',
key: 'get',
id,
});
set({ loading: false });
console.log('res', res);
if (res.code === 200) {
set(
produce((state) => {
state.data = res.data;
state.id = res.data.id;
}),
);
} else {
message.error(res.msg || 'Request failed');
}
},
saveNodesEdges: async ({ edges, nodes, viewport }) => {
const { id, data: panelData } = get();
const { data } = panelData || {};
const res = await query.post({
path: 'page',
key: 'update',
data: {
id,
data: {
...data.data,
edges,
nodes,
viewport,
},
},
});
if (res.code === 200) {
message.success('Success');
set(
produce((state) => {
state.data = res.data;
}),
);
} else {
message.error(res.msg || 'Request failed');
}
},
};
});