This commit is contained in:
ditiqi 2025-03-02 17:49:10 +08:00
parent 9034e61229
commit eba90139e4
4 changed files with 166 additions and 374 deletions

View File

@ -3,10 +3,10 @@ import { Position, getBezierPath } from "reactflow";
import { getBasePath } from "."; import { getBasePath } from ".";
import { import {
kBaseMarkerColor, kBaseMarkerColor,
kBaseMarkerColors, kBaseMarkerColors,
kNoMarkerColor, kNoMarkerColor,
kYesMarkerColor, kYesMarkerColor,
} from "../../components/Edges/Marker"; } from "../../components/Edges/Marker";
import { isEqual } from "../../utils/diff"; import { isEqual } from "../../utils/diff";
import { EdgeLayout, ReactFlowEdgeWithData } from "../../data/types"; import { EdgeLayout, ReactFlowEdgeWithData } from "../../data/types";
@ -14,187 +14,187 @@ import { kReactFlow } from "../../states/reactflow";
import { getPathWithRoundCorners } from "./edge"; import { getPathWithRoundCorners } from "./edge";
interface EdgeStyle { interface EdgeStyle {
color: string; color: string;
edgeType: "solid" | "dashed"; edgeType: "solid" | "dashed";
pathType: "base" | "bezier"; pathType: "base" | "bezier";
} }
/** /**
* Get the style of the connection line * Get the style of the connection line
* *
* 1. When there are more than 3 edges connecting to both ends of the Node, use multiple colors to distinguish the edges. * 1. When there are more than 3 edges connecting to both ends of the Node, use multiple colors to distinguish the edges.
* 2. When the connection line goes backward or connects to a hub Node, use dashed lines to distinguish the edges. * 2. When the connection line goes backward or connects to a hub Node, use dashed lines to distinguish the edges.
* 3. When the connection line goes from a hub to a Node, use bezier path. * 3. When the connection line goes from a hub to a Node, use bezier path.
*/ */
export const getEdgeStyles = (props: { export const getEdgeStyles = (props: {
id: string; id: string;
isBackward: boolean; isBackward: boolean;
}): EdgeStyle => { }): EdgeStyle => {
const { id, isBackward } = props; const { id, isBackward } = props;
const idx = parseInt(lastOf(id.split("#")) ?? "0", 10); const idx = parseInt(lastOf(id.split("#")) ?? "0", 10);
if (isBackward) { if (isBackward) {
// Use dashed lines to distinguish the edges when the connection line goes backward or connects to a hub Node // Use dashed lines to distinguish the edges when the connection line goes backward or connects to a hub Node
return { color: kNoMarkerColor, edgeType: "dashed", pathType: "base" }; return { color: kNoMarkerColor, edgeType: "dashed", pathType: "base" };
} }
const edge: ReactFlowEdgeWithData = kReactFlow.instance!.getEdge(id)!; const edge: ReactFlowEdgeWithData = kReactFlow.instance!.getEdge(id)!;
if (edge.data!.targetPort.edges > 2) { if (edge.data!.targetPort.edges > 2) {
// Use dashed bezier path when the connection line connects to a hub Node // Use dashed bezier path when the connection line connects to a hub Node
return { return {
color: kYesMarkerColor, color: kYesMarkerColor,
edgeType: "dashed", edgeType: "dashed",
pathType: "bezier", pathType: "bezier",
}; };
} }
if (edge.data!.sourcePort.edges > 2) { if (edge.data!.sourcePort.edges > 2) {
// Use multiple colors to distinguish the edges when there are more than 3 edges connecting to both ends of the Node // Use multiple colors to distinguish the edges when there are more than 3 edges connecting to both ends of the Node
return { return {
color: kBaseMarkerColors[idx % kBaseMarkerColors.length], color: kBaseMarkerColors[idx % kBaseMarkerColors.length],
edgeType: "solid", edgeType: "solid",
pathType: "base", pathType: "base",
}; };
} }
return { color: kBaseMarkerColor, edgeType: "solid", pathType: "base" }; return { color: kBaseMarkerColor, edgeType: "solid", pathType: "base" };
}; };
interface ILayoutEdge { interface ILayoutEdge {
id: string; id: string;
layout?: EdgeLayout; layout?: EdgeLayout;
offset: number; offset: number;
borderRadius: number; borderRadius: number;
pathType: EdgeStyle["pathType"]; pathType: EdgeStyle["pathType"];
source: string; source: string;
target: string; target: string;
sourceX: number; sourceX: number;
sourceY: number; sourceY: number;
targetX: number; targetX: number;
targetY: number; targetY: number;
sourcePosition: Position; sourcePosition: Position;
targetPosition: Position; targetPosition: Position;
} }
export function layoutEdge({ export function layoutEdge({
id, id,
layout, layout,
offset, offset,
borderRadius, borderRadius,
pathType, pathType,
source, source,
target, target,
sourceX, sourceX,
sourceY, sourceY,
targetX, targetX,
targetY, targetY,
sourcePosition, sourcePosition,
targetPosition, targetPosition,
}: ILayoutEdge): EdgeLayout { }: ILayoutEdge): EdgeLayout {
const relayoutDeps = [sourceX, sourceY, targetX, targetY]; const relayoutDeps = [sourceX, sourceY, targetX, targetY];
const needRelayout = !isEqual(relayoutDeps, layout?.deps?.relayoutDeps); const needRelayout = !isEqual(relayoutDeps, layout?.deps?.relayoutDeps);
const reBuildPathDeps = layout?.points; const reBuildPathDeps = layout?.points;
const needReBuildPath = !isEqual( const needReBuildPath = !isEqual(
reBuildPathDeps, reBuildPathDeps,
layout?.deps?.reBuildPathDeps layout?.deps?.reBuildPathDeps
); );
let newLayout = layout; let newLayout = layout;
if (needRelayout) { if (needRelayout) {
newLayout = _layoutEdge({ newLayout = _layoutEdge({
id, id,
offset, offset,
borderRadius, borderRadius,
pathType, pathType,
source, source,
target, target,
sourceX, sourceX,
sourceY, sourceY,
targetX, targetX,
targetY, targetY,
sourcePosition, sourcePosition,
targetPosition, targetPosition,
}); });
} else if (needReBuildPath) { } else if (needReBuildPath) {
newLayout = _layoutEdge({ newLayout = _layoutEdge({
layout, layout,
id, id,
offset, offset,
borderRadius, borderRadius,
pathType, pathType,
source, source,
target, target,
sourceX, sourceX,
sourceY, sourceY,
targetX, targetX,
targetY, targetY,
sourcePosition, sourcePosition,
targetPosition, targetPosition,
}); });
} }
newLayout!.deps = deepClone({ relayoutDeps, reBuildPathDeps }); newLayout!.deps = deepClone({ relayoutDeps, reBuildPathDeps });
return newLayout!; return newLayout!;
} }
function _layoutEdge({ function _layoutEdge({
id, id,
layout, layout,
offset, offset,
borderRadius, borderRadius,
pathType, pathType,
source, source,
target, target,
sourceX, sourceX,
sourceY, sourceY,
targetX, targetX,
targetY, targetY,
sourcePosition, sourcePosition,
targetPosition, targetPosition,
}: ILayoutEdge): EdgeLayout { }: ILayoutEdge): EdgeLayout {
const _pathType: EdgeStyle["pathType"] = pathType; const _pathType: EdgeStyle["pathType"] = pathType;
if (_pathType === "bezier") { if (_pathType === "bezier") {
const [path, labelX, labelY] = getBezierPath({ const [path, labelX, labelY] = getBezierPath({
sourceX, sourceX,
sourceY, sourceY,
targetX, targetX,
targetY, targetY,
sourcePosition, sourcePosition,
targetPosition, targetPosition,
}); });
const points = [ const points = [
{ {
id: "source-" + id, id: "source-" + id,
x: sourceX, x: sourceX,
y: sourceY, y: sourceY,
}, },
{ {
id: "target-" + id, id: "target-" + id,
x: targetX, x: targetX,
y: targetY, y: targetY,
}, },
]; ];
return { return {
path, path,
points, points,
inputPoints: points, inputPoints: points,
labelPosition: { labelPosition: {
x: labelX, x: labelX,
y: labelY, y: labelY,
}, },
}; };
} }
if ((layout?.points?.length ?? 0) > 1) { if ((layout?.points?.length ?? 0) > 1) {
layout!.path = getPathWithRoundCorners(layout!.points, borderRadius); layout!.path = getPathWithRoundCorners(layout!.points, borderRadius);
return layout!; return layout!;
} }
return getBasePath({ return getBasePath({
id, id,
offset, offset,
borderRadius, borderRadius,
source, source,
target, target,
sourceX, sourceX,
sourceY, sourceY,
targetX, targetX,
targetY, targetY,
sourcePosition, sourcePosition,
targetPosition, targetPosition,
}); });
} }

View File

@ -1,109 +1,6 @@
// import React, { useState } from 'react';
// import { Form, Select, Input } from 'antd';
// import axios from 'axios';
// const { Option } = Select;
// // 假数据
// const fakePostsData = Array.from({ length: 15 }, (_, index) => ({
// id: index + 1,
// type: 'Lecture',
// name: `Lecture ${index + 1}`,
// description: `This is lecture number ${index + 1}`,
// }));
// // 模拟获取数据的函数
// async function fetchPosts(query = '') {
// // 在实际应用中这里应该是一个真实的API调用
// return fakePostsData.filter(post =>
// post.name.toLowerCase().includes(query.toLowerCase()) ||
// post.description.toLowerCase().includes(query.toLowerCase())
// );
// }
// const PostSelector = ({ value, onChange }) => {
// const [posts, setPosts] = useState([]);
// const [searchValue, setSearchValue] = useState('');
// const handleSearch = async (query) => {
// setSearchValue(query);
// const result = await fetchPosts(query);
// setPosts(result);
// };
// const handlePostChange = (selectedIds) => {
// onChange(selectedIds); // 更新父组件的状态
// };
// const renderOption = (post) => (
// <Option key={post.id} value={post.id.toString()}>
// {post.name} - {post.description}
// </Option>
// );
// return (
// <Select
// mode="multiple" // 支持多选
// showSearch
// placeholder="请选择或搜索讲座"
// optionFilterProp="children"
// onSearch={handleSearch}
// filterOption={false} // 禁用默认过滤,因为我们已经在 onSearch 中实现了自定义过滤
// notFoundContent={''}
// style={{ width: '100%' }}
// value={value || []}
// onChange={handlePostChange}
// >
// {posts.map(renderOption)}
// </Select>
// );
// };
// const PostForm = () => {
// const [form] = Form.useForm();
// const onFinish = (values) => {
// console.log('Received values of form: ', values);
// };
// return (
// <Form
// form={form}
// name="postForm"
// onFinish={onFinish}
// initialValues={{
// postIds: [], // 初始值为空数组
// }}
// >
// <Form.Item
// name="postIds"
// label="选择讲座"
// rules={[{ required: true, message: '请选择至少一个讲座!' }]}
// >
// <PostSelector
// value={form.getFieldValue('postIds')}
// onChange={(selectedIds) => form.setFieldsValue({ postIds: selectedIds })}
// />
// </Form.Item>
// <Form.Item>
// <button type="submit">提交</button>
// </Form.Item>
// </Form>
// );
// };
// export default PostForm;
import { api } from "@nice/client"; import { api } from "@nice/client";
import { Button, Select } from "antd"; import { Select } from "antd";
import { import { Lecture, postDetailSelect, Prisma } from "@nice/common";
Lecture,
lectureDetailSelect,
postDetailSelect,
postUnDetailSelect,
Prisma,
} from "@nice/common";
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import PostSelectOption from "./PostSelectOption"; import PostSelectOption from "./PostSelectOption";
import { DefaultArgs } from "@prisma/client/runtime/library"; import { DefaultArgs } from "@prisma/client/runtime/library";
@ -205,4 +102,3 @@ export default function PostSelect({
</div> </div>
); );
} }

View File

@ -1,95 +0,0 @@
import {
Post,
Department,
Staff,
Enrollment,
Taxonomy,
Term,
} from "@prisma/client";
import { StaffDto } from "./staff";
import { TermDto } from "./term";
import { ResourceDto } from "./resource";
import { DepartmentDto } from "./department";
export type PostComment = {
id: string;
type: string;
title: string;
content: string;
authorId: string;
domainId: string;
referenceId: string;
resources: string[];
createdAt: Date;
updatedAt: Date;
parentId: string;
author: {
id: string;
showname: string;
username: string;
avatar: string;
};
};
export type PostDto = Post & {
readed: boolean;
readedCount: number;
author: StaffDto;
limitedComments: PostComment[];
commentsCount: number;
perms?: {
delete: boolean;
// edit: boolean;
};
watchableDepts: Department[];
watchableStaffs: Staff[];
terms: TermDto[];
depts: DepartmentDto[];
meta?: {
thumbnail?: string;
views?: number;
};
studentIds?: string[];
};
export type LectureMeta = {
type?: string;
views?: number;
videoUrl?: string;
videoThumbnail?: string;
videoIds?: string[];
videoThumbnailIds?: string[];
};
export type Lecture = Post & {
resources?: ResourceDto[];
meta?: LectureMeta;
};
export type SectionMeta = {
objectives?: string[];
};
export type Section = Post & {
meta?: SectionMeta;
};
export type SectionDto = Section & {
lectures: Lecture[];
};
export type CourseMeta = {
thumbnail?: string;
objectives?: string[];
views?: number;
likes?: number;
hates?: number;
};
export type Course = PostDto & {
meta?: CourseMeta;
};
export type CourseDto = Course & {
enrollments?: Enrollment[];
sections?: SectionDto[];
terms: TermDto[];
lectureCount?: number;
depts: Department[];
studentIds: string[];
};

View File

@ -1,9 +0,0 @@
import { useEffect, useRef } from 'react';
export default function MindMapEditor(): JSX.Element {
const containerRef = useRef<HTMLDivElement>(null);
}