collect-system/apps/web/src/components/common/editor/MindEditor.tsx

198 lines
5.5 KiB
TypeScript
Raw Normal View History

2025-02-26 23:18:14 +08:00
import { Button, Card, Empty, Form, Space, Spin, message, theme } from 'antd';
import NodeMenu from './NodeMenu';
import { useEntity, api, usePost } from '@nice/client';
import { ObjectType, postDetailSelect, PostDto, PostType, Prisma, Taxonomy } from '@nice/common';
import TermSelect from '../../models/term/term-select';
import DepartmentSelect from '../../models/department/department-select';
import { useEffect, useRef, useState } from 'react';
import toast from 'react-hot-toast';
import { MindElixirInstance } from 'mind-elixir';
import MindElixir from 'mind-elixir';
import { useTusUpload } from '@web/src/hooks/useTusUpload';
import { useNavigate } from 'react-router-dom';
const MIND_OPTIONS = {
direction: MindElixir.SIDE,
draggable: true,
contextMenu: true,
toolBar: true,
nodeMenu: true,
keypress: true,
locale: 'zh_CN' as const,
theme: {
name: 'Latte',
palette: [
'#dd7878',
'#ea76cb',
'#8839ef',
'#e64553',
'#fe640b',
'#df8e1d',
'#40a02b',
'#209fb5',
'#1e66f5',
'#7287fd',
],
cssVar: {
'--main-color': '#444446',
'--main-bgcolor': '#ffffff',
'--color': '#777777',
'--bgcolor': '#f6f6f6',
'--panel-color': '#444446',
'--panel-bgcolor': '#ffffff',
'--panel-border-color': '#eaeaea',
},
}
};
export default function MindEditor({ id }: { id?: string }) {
const containerRef = useRef<HTMLDivElement>(null);
const [instance, setInstance] = useState<MindElixirInstance | null>(null);
2025-02-19 16:06:03 +08:00
2025-02-26 23:18:14 +08:00
const { data: post, isLoading }: { data: PostDto, isLoading: boolean } = api.post.findFirst.useQuery({
where: {
id
},
select: postDetailSelect
})
const navigate = useNavigate()
const { create, update } = usePost();
const { data: taxonomies } = api.taxonomy.getAll.useQuery({
type: ObjectType.COURSE,
});
const { handleFileUpload } = useTusUpload()
const [form] = Form.useForm()
2025-02-21 16:57:22 +08:00
useEffect(() => {
2025-02-26 23:18:14 +08:00
if (post && form && instance && id) {
console.log(post)
instance.refresh((post as any).meta)
const deptIds = (post?.depts || [])?.map((dept) => dept.id);
const formData = {
title: post.title,
deptIds: deptIds,
};
post.terms?.forEach((term) => {
formData[term.taxonomyId] = term.id; // 假设 taxonomyName 是您在 Form.Item 中使用的 name
});
form.setFieldsValue(formData);
}
}, [post, form, instance, id]);
useEffect(() => {
if (!containerRef.current) return;
const mind = new MindElixir({
...MIND_OPTIONS,
el: containerRef.current,
2025-02-21 16:57:22 +08:00
});
2025-02-26 23:18:14 +08:00
mind.init(MindElixir.new('新学习路径'));
containerRef.current.hidden = true;
setInstance(mind);
2025-02-21 16:57:22 +08:00
}, []);
2025-02-26 23:18:14 +08:00
useEffect(() => {
if ((!id || post) && instance) {
containerRef.current.hidden = false
instance.toCenter()
instance.refresh((post as any)?.meta)
}
}, [id, post, instance])
const handleSave = async () => {
if (!instance) return;
const values = form.getFieldsValue()
const imgBlob = await instance?.exportPng()
handleFileUpload(imgBlob, async (result) => {
const termIds = taxonomies.map((tax) => values[tax.id]).filter((id) => id);
const deptIds = (values?.deptIds || []) as string[];
const { theme, ...data } = instance.getData();
try {
if (post && id) {
const params: Prisma.PostUpdateArgs = {
where: {
id
},
data: {
title: data.nodeData.topic,
meta: { ...data, thumbnail: result.compressedUrl },
terms: {
set: termIds.map((id) => ({ id }))
},
depts: {
set: deptIds.map((id) => ({ id })),
},
updatedAt: new Date()
}
}
await update.mutateAsync(params);
toast.success('更新成功');
} else {
const params: Prisma.PostCreateInput = {
type: PostType.PATH,
title: data.nodeData.topic,
meta: { ...data, thumbnail: result.compressedUrl },
terms: {
connect: termIds.map((id) => ({ id }))
},
depts: {
connect: deptIds.map((id) => ({ id })),
},
updatedAt: new Date()
};
const res = await create.mutateAsync({ data: params });
navigate(`/path/editor/${res.id}`, { replace: true })
toast.success('创建成功');
}
} catch (error) {
toast.error('保存失败');
throw error;
}
console.log(result)
}, (error) => { }, `mind-thumb-${new Date().toString()}`)
};
2025-02-21 16:57:22 +08:00
return (
2025-02-26 23:18:14 +08:00
<div className=' flex flex-col border rounded-lg overflow-hidden'>
{taxonomies && (
<Form onFinish={(values) => {
console.log(values)
}} form={form} className=' bg-white p-2 '>
<div className='flex items-center justify-between gap-4'>
<div className='flex items-center gap-4'>
{taxonomies.map((tax, index) => (
<Form.Item
key={tax.id}
name={tax.id}
rules={[{ required: false }]}
noStyle
>
<TermSelect
className=' w-48'
placeholder={`请选择${tax.name}`}
taxonomyId={tax.id}
/>
</Form.Item>
))}
<Form.Item name="deptIds" noStyle>
<DepartmentSelect className='w-96' placeholder='请选择制作单位' multiple />
</Form.Item>
</div>
<Button ghost type='primary' onClick={handleSave} >{id ? '更新' : '保存'}</Button>
</div>
</Form>
)
}
<div
ref={containerRef}
className='mind-editor'
/>
{instance && (<NodeMenu mind={instance} />
)}
{isLoading && <div className='py-64 justify-center flex' style={{ height: "calc(100vh - 287px)" }}>
<Spin size='large'></Spin>
</div>}
{!post && id && !isLoading && <div className='py-64' style={{ height: "calc(100vh - 287px)" }}>
<Empty></Empty>
</div>}
2025-02-21 16:57:22 +08:00
</div>
);
2025-02-21 08:18:48 +08:00
}