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
|
|
|
}
|