375 lines
11 KiB
TypeScript
375 lines
11 KiB
TypeScript
import { ContainerEdit } from '@kevisual/container/edit';
|
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
import { useParams } from 'react-router';
|
|
import { query, useStore, ws } from '@/modules';
|
|
import { Button, message, Tooltip } from 'antd';
|
|
import { getContainerData } from '@/modules/deck-to-flow/deck';
|
|
import { usePanelStore } from '../store';
|
|
import { useShallow } from 'zustand/react/shallow';
|
|
import { TextArea } from '@/pages/container/components/TextArea';
|
|
import { CloseOutlined, MessageOutlined, SaveOutlined, SelectOutlined } from '@ant-design/icons';
|
|
import { useDeckPageStore } from './deck-store';
|
|
import { FormModal } from './Model.tsx';
|
|
import { useAiStore } from '@/pages/ai-chat/index.tsx';
|
|
export const clearBlank = (newStyle: any) => {
|
|
let change = false;
|
|
for (let key in newStyle) {
|
|
if (newStyle[key] === '' || newStyle[key] === undefined || newStyle[key] === null) {
|
|
delete newStyle[key];
|
|
change = true;
|
|
}
|
|
}
|
|
return change;
|
|
};
|
|
export const useListener = (id?: string, opts?: any) => {
|
|
const { refresh, cids = [] } = opts || {};
|
|
const connected = useStore((state) => state.connected);
|
|
// 监听服务器的消息
|
|
useEffect(() => {
|
|
if (!id) return;
|
|
if (!connected) return;
|
|
if (cids.length === 0) return;
|
|
console.log('cids', cids);
|
|
ws.send(
|
|
JSON.stringify({
|
|
type: 'subscribe',
|
|
data: {
|
|
type: 'pageEdit',
|
|
data: {
|
|
pid: id,
|
|
cids: cids,
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
ws.addEventListener('message', listener);
|
|
return () => {
|
|
if (!id) return;
|
|
if (!connected) return;
|
|
ws.removeEventListener('message', listener);
|
|
};
|
|
}, [id, connected, cids]);
|
|
const listener = (event) => {
|
|
const parseIfJson = (data: string) => {
|
|
try {
|
|
return JSON.parse(data);
|
|
} catch (e) {
|
|
return data;
|
|
}
|
|
};
|
|
const receivedData = parseIfJson(event.data);
|
|
if (typeof receivedData === 'string') return;
|
|
if (receivedData.type === 'pageEdit' && receivedData.source === 'container') {
|
|
const { data: containerData, pid } = receivedData;
|
|
if (pid !== id) return;
|
|
if (refresh) {
|
|
refresh(containerData);
|
|
}
|
|
}
|
|
};
|
|
};
|
|
|
|
export const Deck = () => {
|
|
const params = useParams<{ id: string }>();
|
|
const id = params.id;
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
const containerRef = useRef<ContainerEdit | null>(null);
|
|
const deckPageStore = useDeckPageStore();
|
|
const { code, setCode } = deckPageStore;
|
|
const { selected, setSelected } = deckPageStore;
|
|
const { cids } = deckPageStore;
|
|
const aiStore = useAiStore(
|
|
useShallow((state) => {
|
|
return {
|
|
setOpen: state.setOpen,
|
|
setKey: state.setKey,
|
|
};
|
|
}),
|
|
);
|
|
const panelStore = usePanelStore(
|
|
useShallow((state) => {
|
|
return {
|
|
updateNodeDataStyle: state.updateNodeDataStyle,
|
|
};
|
|
}),
|
|
);
|
|
useEffect(() => {
|
|
if (!id) return;
|
|
deckPageStore.setId(id);
|
|
fetch();
|
|
}, []);
|
|
useEffect(() => {
|
|
ref.current?.addEventListener('onContainer', onContainer);
|
|
return () => {
|
|
ref.current?.removeEventListener('onContainer', onContainer);
|
|
if (ref.current) {
|
|
const children = ref.current;
|
|
children.innerHTML = '';
|
|
}
|
|
};
|
|
}, []);
|
|
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 result = getContainerData({ page, containerList });
|
|
console.log('result', result);
|
|
deckPageStore.setPageData(result);
|
|
deckPageStore.setCids();
|
|
init(result);
|
|
}
|
|
};
|
|
const refresh = async (data: any) => {
|
|
console.log('refresh', data);
|
|
if (!data.id) return;
|
|
const code = {
|
|
codeId: data.id,
|
|
code: data.code,
|
|
hash: '',
|
|
};
|
|
const container = containerRef.current!;
|
|
// @ts-ignore
|
|
await container.updateDataCode([code]);
|
|
const containerList = container.data.filter((item) => item.codeId === data.id);
|
|
await new Promise((resolve) => {
|
|
setTimeout(resolve, 2000);
|
|
});
|
|
// container.reRender();
|
|
containerList.forEach((item) => {
|
|
container.renderId(item.id);
|
|
});
|
|
// @ts-ignore
|
|
window.c = container;
|
|
};
|
|
useListener(id, { refresh, cids });
|
|
const onContainer = (e) => {
|
|
const { data } = e;
|
|
const types = ['position', 'resize'];
|
|
if (types.includes(data.type)) {
|
|
const { type, data: containerData } = data;
|
|
if (type === 'position') {
|
|
const { cid, left, top, rid } = containerData;
|
|
const newData = {
|
|
id: rid,
|
|
nodeData: {
|
|
id: cid,
|
|
data: {
|
|
style: {
|
|
// position: 'absolute',
|
|
left,
|
|
top,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
if (left && top) {
|
|
panelStore.updateNodeDataStyle(newData);
|
|
}
|
|
updateStyle(cid, { left, top });
|
|
setTimeout(() => {
|
|
setCodeStyle(cid);
|
|
}, 1000);
|
|
} else if (type === 'resize') {
|
|
const { cid, rid, width, height } = containerData;
|
|
const newData = {
|
|
id: rid,
|
|
nodeData: {
|
|
id: cid,
|
|
data: {
|
|
style: {
|
|
width,
|
|
height,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
if (width && height) {
|
|
// @ts-ignore
|
|
newData.nodeData.data.style.position = 'absolute';
|
|
panelStore.updateNodeDataStyle(newData);
|
|
}
|
|
updateStyle(cid, { width, height });
|
|
setTimeout(() => {
|
|
setCodeStyle(cid);
|
|
}, 1000);
|
|
}
|
|
} else if (data.type === 'active') {
|
|
if (!data?.data?.cid) {
|
|
setSelected(null);
|
|
return;
|
|
}
|
|
const { cid, rid } = data?.data || {};
|
|
setSelected(data);
|
|
setCodeStyle(cid);
|
|
} else {
|
|
console.log('onContainer', data);
|
|
}
|
|
};
|
|
const onSave = () => {
|
|
const { cid, rid } = selected?.data || {};
|
|
let data: any;
|
|
try {
|
|
data = JSON.parse(code);
|
|
} catch (error) {
|
|
message.error('JSON format error');
|
|
return;
|
|
}
|
|
// clearBlank(data);
|
|
const newData = {
|
|
id: rid,
|
|
nodeData: {
|
|
id: cid,
|
|
data: {
|
|
style: data,
|
|
},
|
|
},
|
|
};
|
|
panelStore.updateNodeDataStyle(newData, true);
|
|
const newDataStyle = updateStyle(cid, data);
|
|
reRender(newDataStyle, cid);
|
|
};
|
|
const setCodeStyle = (cid: string) => {
|
|
const pageData = deckPageStore.getPageData();
|
|
const selected = deckPageStore.getSeleted();
|
|
const _data = pageData.find((item) => item.id === cid);
|
|
const node = _data?.data?.node || {};
|
|
if (selected?.data?.cid === cid) {
|
|
setCode('');
|
|
setCode(JSON.stringify(node?.data?.style || {}, null, 2));
|
|
}
|
|
};
|
|
useEffect(() => {
|
|
if (selected) {
|
|
const { cid } = selected?.data || {};
|
|
cid && setCodeStyle(cid);
|
|
}
|
|
}, [deckPageStore.selected]);
|
|
|
|
const updateStyle = (rid: string, style: any) => {
|
|
const pageData = deckPageStore.getPageData();
|
|
const _pageData = pageData.map((item) => {
|
|
if (item.id === rid) {
|
|
const newStyle = {
|
|
...item.data.node.data.style,
|
|
...style,
|
|
};
|
|
// 过滤掉空的style
|
|
clearBlank(newStyle);
|
|
return {
|
|
...item,
|
|
style: newStyle,
|
|
data: {
|
|
...item.data,
|
|
node: {
|
|
...item.data.node,
|
|
data: {
|
|
...item.data.node.data,
|
|
style: newStyle,
|
|
},
|
|
},
|
|
},
|
|
};
|
|
}
|
|
return item;
|
|
});
|
|
deckPageStore.setPageData([..._pageData]);
|
|
return _pageData;
|
|
};
|
|
const init = async (data: any[]) => {
|
|
// console.log('data', data, ref.current);
|
|
const container = new ContainerEdit({
|
|
root: ref.current!,
|
|
data: data,
|
|
showChild: true,
|
|
// edit: false,
|
|
});
|
|
container.render(id!);
|
|
containerRef.current = container;
|
|
containerRef.current.event.on('save', (data) => {
|
|
console.log('save', data);
|
|
const { id, code } = data;
|
|
});
|
|
};
|
|
const reRender = async (data: any[], cid?: string) => {
|
|
if (containerRef.current) {
|
|
const container = containerRef.current;
|
|
await container.updateData(data);
|
|
await container.renderId(cid!);
|
|
return;
|
|
}
|
|
};
|
|
return (
|
|
<div className='w-full h-full relative'>
|
|
<div className='w-full h-full bg-gray-200 '>
|
|
<div className='text-center mb-10 font-bold text-4xl pt-4 flex items-center justify-center group'>
|
|
Deck
|
|
<Tooltip>
|
|
<Button
|
|
className='ml-4 invisible group-hover:visible'
|
|
icon={<SelectOutlined />}
|
|
onClick={() => {
|
|
deckPageStore.setShowEdit(true);
|
|
}}></Button>
|
|
</Tooltip>
|
|
</div>
|
|
<div
|
|
className='flex '
|
|
style={{
|
|
height: 'calc(100% - 32px)',
|
|
}}>
|
|
<div className='mx-auto border rounded-md bg-white w-[80%] h-[80%] scrollbar overflow-scroll relative' ref={ref}></div>
|
|
</div>
|
|
</div>
|
|
{selected && (
|
|
<div className='absolute bottom-5 z-50 w-full h-[200px]'>
|
|
<div className=' p-2 card w-[80%] mx-auto border bg-slate-200 rounded-md overflow-scroll scrollbar'>
|
|
{/* <pre>{JSON.stringify(selected, null, 2)}</pre> */}
|
|
<div>
|
|
<Button.Group>
|
|
<Tooltip title='Close'>
|
|
<Button
|
|
onClick={() => {
|
|
setSelected(null);
|
|
}}
|
|
icon={<CloseOutlined />}></Button>
|
|
</Tooltip>
|
|
<Tooltip title='Ai Chat'>
|
|
<Button
|
|
onClick={() => {
|
|
aiStore.setOpen(true);
|
|
aiStore.setKey(location.pathname);
|
|
}}
|
|
icon={<MessageOutlined />}></Button>
|
|
</Tooltip>
|
|
<Tooltip title='Save'>
|
|
<Button
|
|
onClick={() => {
|
|
onSave();
|
|
}}
|
|
icon={<SaveOutlined />}></Button>
|
|
</Tooltip>
|
|
</Button.Group>
|
|
</div>
|
|
<div className='h-[200px]'>
|
|
<TextArea
|
|
className='h-[100px] rounded-md'
|
|
value={code}
|
|
onChange={(v) => {
|
|
setCode(v);
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
<FormModal />
|
|
</div>
|
|
);
|
|
};
|