Skip to content

Commit 7f48fa4

Browse files
committed
feat: 增加文件级别的引用块节点
1 parent 30984a2 commit 7f48fa4

File tree

5 files changed

+162
-84
lines changed

5 files changed

+162
-84
lines changed

app/src/core/service/controlService/controller/concrete/utilsControl.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -573,11 +573,13 @@ export class ControllerUtils {
573573
toast.error(`文件【${parserResult.fileName}】不在“最近打开的文件”中,不能创建引用`);
574574
return;
575575
}
576-
// 获取该文件中的所有section
577-
const sections = await CrossFileContentQuery.getSectionsByFileName(parserResult.fileName);
578-
if (!sections.includes(parserResult.sectionName)) {
579-
toast.error(`文件【${parserResult.fileName}】中没有section【${parserResult.sectionName}】,不能创建引用`);
580-
return;
576+
if (parserResult.sectionName) {
577+
// 用户输入了#,需要检查section是否存在
578+
const sections = await CrossFileContentQuery.getSectionsByFileName(parserResult.fileName);
579+
if (!sections.includes(parserResult.sectionName)) {
580+
toast.error(`文件【${parserResult.fileName}】中没有section【${parserResult.sectionName}】,不能创建引用`);
581+
return;
582+
}
581583
}
582584
await TextNodeSmartTools.changeTextNodeToReferenceBlock(project);
583585
}

app/src/core/service/dataGenerateService/generateScreenshot.tsx

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,69 +4,48 @@ import { PathString } from "@/utils/pathString";
44
import { RecentFileManager } from "../dataFileService/RecentFileManager";
55
import { Section } from "@/core/stage/stageObject/entity/Section";
66
import { sleep } from "@/utils/sleep";
7+
import { Rectangle } from "@graphif/shapes";
78

89
/**
910
* 从一个文件中生成截图
1011
*/
1112
export namespace GenerateScreenshot {
1213
/**
13-
* 根据文件名和Section框名生成截图
14-
* @param fileName 文件名
15-
* @param sectionName Section框名
14+
* 创建临时Canvas并渲染Project
15+
* @param project 项目实例
16+
* @param targetRect 目标矩形区域
1617
* @returns 截图的Blob对象
1718
*/
18-
export async function generateSection(fileName: string, sectionName: string): Promise<Blob | undefined> {
19-
try {
20-
// 1. 根据文件名查找并加载prg文件
21-
const recentFiles = await RecentFileManager.getRecentFiles();
22-
const file = recentFiles.find((file) => PathString.getFileNameFromPath(file.uri.fsPath) === fileName);
23-
if (!file) {
24-
return undefined;
25-
}
26-
const fileUri = file.uri;
27-
const project = new Project(fileUri);
28-
loadAllServicesBeforeInit(project);
29-
await project.init();
30-
// 5. 查找指定名称的Section
31-
const targetSection = project.stage.find((obj) => obj instanceof Section && obj.text === sectionName);
19+
async function renderProjectToBlob(project: Project, targetRect: Rectangle): Promise<Blob> {
20+
// 计算缩放比例,确保最终截图宽高不超过1920
21+
const maxDimension = 1920;
22+
let scaleFactor = 1;
23+
if (targetRect.width > maxDimension || targetRect.height > maxDimension) {
24+
const widthRatio = maxDimension / targetRect.width;
25+
const heightRatio = maxDimension / targetRect.height;
26+
scaleFactor = Math.min(widthRatio, heightRatio);
27+
}
28+
project.camera.currentScale = scaleFactor;
29+
project.camera.targetScale = scaleFactor;
3230

33-
if (!targetSection) {
34-
console.error(`Section框 【${sectionName}】 没有发现 in file ${fileName}`);
35-
return undefined;
36-
}
31+
// 创建临时Canvas
32+
const tempCanvas = document.createElement("canvas");
33+
const deviceScale = window.devicePixelRatio;
34+
const canvasWidth = Math.min(targetRect.width * scaleFactor + 2, maxDimension + 2);
35+
const canvasHeight = Math.min(targetRect.height * scaleFactor + 2, maxDimension + 2);
36+
tempCanvas.width = canvasWidth * deviceScale;
37+
tempCanvas.height = canvasHeight * deviceScale;
38+
tempCanvas.style.width = `${canvasWidth}px`;
39+
tempCanvas.style.height = `${canvasHeight}px`;
40+
const tempCtx = tempCanvas.getContext("2d")!;
41+
tempCtx.scale(deviceScale, deviceScale);
3742

38-
// 6. 调整相机位置到Section
39-
const sectionRect = targetSection.collisionBox.getRectangle();
40-
project.camera.location = sectionRect.center;
43+
// 保存原Canvas和渲染器尺寸
44+
const originalCanvas = project.canvas.element;
45+
const originalRendererWidth = project.renderer.w;
46+
const originalRendererHeight = project.renderer.h;
4147

42-
// 7. 计算缩放比例,确保最终截图宽高不超过1920
43-
const maxDimension = 1920;
44-
let scaleFactor = 1;
45-
if (sectionRect.width > maxDimension || sectionRect.height > maxDimension) {
46-
const widthRatio = maxDimension / sectionRect.width;
47-
const heightRatio = maxDimension / sectionRect.height;
48-
scaleFactor = Math.min(widthRatio, heightRatio);
49-
}
50-
project.camera.currentScale = scaleFactor;
51-
project.camera.targetScale = scaleFactor;
52-
53-
// 8. 创建临时Canvas
54-
const tempCanvas = document.createElement("canvas");
55-
const deviceScale = window.devicePixelRatio;
56-
const canvasWidth = Math.min(sectionRect.width * scaleFactor + 2, maxDimension + 2);
57-
const canvasHeight = Math.min(sectionRect.height * scaleFactor + 2, maxDimension + 2);
58-
tempCanvas.width = canvasWidth * deviceScale;
59-
tempCanvas.height = canvasHeight * deviceScale;
60-
tempCanvas.style.width = `${canvasWidth}px`;
61-
tempCanvas.style.height = `${canvasHeight}px`;
62-
const tempCtx = tempCanvas.getContext("2d")!;
63-
tempCtx.scale(deviceScale, deviceScale);
64-
65-
// 8. 渲染Project到临时Canvas
66-
// 保存原Canvas和渲染器尺寸
67-
const originalCanvas = project.canvas.element;
68-
const originalRendererWidth = project.renderer.w;
69-
const originalRendererHeight = project.renderer.h;
48+
try {
7049
// 设置临时Canvas
7150
project.canvas.element = tempCanvas;
7251
project.canvas.ctx = tempCtx;
@@ -79,7 +58,7 @@ export namespace GenerateScreenshot {
7958
await sleep(1000); // 1s
8059
project.pause();
8160

82-
// 9. 将Canvas内容转换为Blob
61+
// 将Canvas内容转换为Blob
8362
const blob = await new Promise<Blob>((resolve) => {
8463
tempCanvas.toBlob((blob) => {
8564
if (blob) {
@@ -90,22 +69,95 @@ export namespace GenerateScreenshot {
9069
}, "image/png");
9170
});
9271

72+
return blob;
73+
} finally {
9374
// 恢复原Canvas
9475
project.canvas.element = originalCanvas;
9576
project.canvas.ctx = originalCanvas.getContext("2d")!;
9677
// 恢复渲染器尺寸
9778
project.renderer.w = originalRendererWidth;
9879
project.renderer.h = originalRendererHeight;
9980

100-
// 10. 清理临时资源
81+
// 清理临时资源
10182
tempCanvas.remove();
83+
}
84+
}
10285

103-
project.dispose();
86+
/**
87+
* 根据文件名和Section框名生成截图
88+
* @param fileName 文件名
89+
* @param sectionName Section框名
90+
* @returns 截图的Blob对象
91+
*/
92+
export async function generateSection(fileName: string, sectionName: string): Promise<Blob | undefined> {
93+
try {
94+
// 加载项目
95+
const recentFiles = await RecentFileManager.getRecentFiles();
96+
const file = recentFiles.find((file) => PathString.getFileNameFromPath(file.uri.fsPath) === fileName);
97+
if (!file) {
98+
return undefined;
99+
}
104100

101+
const project = new Project(file.uri);
102+
loadAllServicesBeforeInit(project);
103+
await project.init();
104+
105+
// 查找指定名称的Section
106+
const targetSection = project.stage.find((obj) => obj instanceof Section && obj.text === sectionName);
107+
if (!targetSection) {
108+
console.error(`Section框 【${sectionName}】 没有发现 in file ${fileName}`);
109+
return undefined;
110+
}
111+
112+
// 调整相机位置到Section
113+
const sectionRect = targetSection.collisionBox.getRectangle();
114+
project.camera.location = sectionRect.center;
115+
116+
// 渲染并获取截图
117+
const blob = await renderProjectToBlob(project, sectionRect);
118+
119+
project.dispose();
105120
return blob;
106121
} catch (error) {
107122
console.error("根据Section生成截图失败", error);
108123
return undefined;
109124
}
110125
}
126+
127+
/**
128+
* 生成整个文件内容的广视野截图
129+
* @param fileName 文件名
130+
* @returns 截图的Blob对象
131+
*/
132+
export async function generateFullView(fileName: string): Promise<Blob | undefined> {
133+
try {
134+
// 加载项目
135+
const recentFiles = await RecentFileManager.getRecentFiles();
136+
const file = recentFiles.find((file) => PathString.getFileNameFromPath(file.uri.fsPath) === fileName);
137+
if (!file) {
138+
return undefined;
139+
}
140+
141+
const project = new Project(file.uri);
142+
loadAllServicesBeforeInit(project);
143+
await project.init();
144+
145+
// 使用相机的reset方法重置视野,以适应所有内容
146+
project.camera.reset();
147+
148+
// 获取整个舞台的边界矩形
149+
const stageSize = project.stageManager.getSize();
150+
const stageCenter = project.stageManager.getCenter();
151+
const fullRect = new Rectangle(stageCenter.subtract(stageSize.divide(2)), stageSize);
152+
153+
// 渲染并获取截图
154+
const blob = await renderProjectToBlob(project, fullRect);
155+
156+
project.dispose();
157+
return blob;
158+
} catch (error) {
159+
console.error("生成广视野截图失败", error);
160+
return undefined;
161+
}
162+
}
111163
}

app/src/core/service/dataManageService/textNodeSmartTools.tsx

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ export namespace TextNodeSmartTools {
533533
return;
534534
}
535535
const fileName = referenceName.split("#")[0];
536-
const sectionName = referenceName.split("#")[1];
536+
const sectionName = referenceName.split("#")[1] || "";
537537

538538
const referenceBlock = new ReferenceBlockNode(project, {
539539
collisionBox: new CollisionBox([
@@ -568,17 +568,34 @@ export namespace TextNodeSmartTools {
568568
await referencedProject.init();
569569

570570
// 更新引用
571-
if (!referencedProject.references.sections[sectionName]) {
572-
referencedProject.references.sections[sectionName] = [];
573-
}
571+
if (sectionName) {
572+
// 引用特定Section的情况
573+
if (!referencedProject.references.sections[sectionName]) {
574+
referencedProject.references.sections[sectionName] = [];
575+
}
576+
577+
// 确保数组中没有重复的文件名
578+
const index = referencedProject.references.sections[sectionName].indexOf(currentFileName);
579+
if (index === -1) {
580+
referencedProject.references.sections[sectionName].push(currentFileName);
581+
// 保存更新
582+
await referencedProject.save();
583+
}
584+
} else {
585+
// 引用整个文件的情况
586+
if (!referencedProject.references.files) {
587+
referencedProject.references.files = [];
588+
}
574589

575-
// 确保数组中没有重复的文件名
576-
const index = referencedProject.references.sections[sectionName].indexOf(currentFileName);
577-
if (index === -1) {
578-
referencedProject.references.sections[sectionName].push(currentFileName);
579-
// 保存更新
580-
await referencedProject.save();
590+
// 确保数组中没有重复的文件名
591+
const index = referencedProject.references.files.indexOf(currentFileName);
592+
if (index === -1) {
593+
referencedProject.references.files.push(currentFileName);
594+
// 保存更新
595+
await referencedProject.save();
596+
}
581597
}
598+
582599
// 关闭项目
583600
await referencedProject.dispose();
584601
// TODO: 存在隐患,欠考虑如果引用已经被当前软件打开的情况。

app/src/core/stage/stageObject/entity/ReferenceBlockNode.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,17 @@ export class ReferenceBlockNode extends ConnectableEntity {
105105
private async generateScreenshot() {
106106
try {
107107
this.state = "loading";
108-
// 调用API获取截图
109-
// const screenshotBlob = await this.project.generateSectionScreenshot(this.fileName, this.sectionName);
110-
const screenshotBlob = await GenerateScreenshot.generateSection(this.fileName, this.sectionName);
108+
let screenshotBlob;
109+
110+
// 根据sectionName是否为空来决定调用哪个方法
111+
if (this.sectionName) {
112+
// 引用特定的Section
113+
screenshotBlob = await GenerateScreenshot.generateSection(this.fileName, this.sectionName);
114+
} else {
115+
// 引用整个文件
116+
screenshotBlob = await GenerateScreenshot.generateFullView(this.fileName);
117+
}
118+
111119
if (screenshotBlob) {
112120
// 保存到附件
113121
const newAttachmentId = this.project.addAttachment(screenshotBlob);

app/src/sub/ReferencesWindow.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export default function ReferencesWindow(props: { currentProjectFileName: string
1818

1919
function refresh() {
2020
setReferences({ ...project!.references });
21+
// 现在这个刷新作用很小,不能检测已经失效的引用
2122
}
2223

2324
useEffect(() => {
@@ -35,7 +36,7 @@ export default function ReferencesWindow(props: { currentProjectFileName: string
3536

3637
{/* 引用信息展示 */}
3738
<div className="flex-1 overflow-y-auto">
38-
{/* <div className="mb-4">
39+
<div className="mb-4">
3940
<h3 className="mb-2 text-lg font-semibold">直接引用{currentProjectFileName}的文件</h3>
4041
{references.files.length === 0 ? (
4142
<p className="text-muted-foreground text-sm">当前项目中没有引用{currentProjectFileName}的文件</p>
@@ -46,8 +47,8 @@ export default function ReferencesWindow(props: { currentProjectFileName: string
4647
return (
4748
<div
4849
key={filePath}
49-
className="text-select-option-text hover:text-select-option-hover-text hover:bg-icon-button-bg flex cursor-pointer items-center gap-2 rounded p-1 text-sm"
50-
onClick={() => handleOpenReferencedFile(fileName, "")}
50+
className="text-select-option-text flex cursor-pointer items-center gap-2 rounded p-1 text-sm *:cursor-pointer hover:ring"
51+
onClick={() => project.referenceManager.jumpToReferenceLocation(fileName, "")}
5152
>
5253
<span className="font-medium">{fileName}</span>
5354
<span className="text-muted-foreground text-xs">{filePath}</span>
@@ -56,27 +57,25 @@ export default function ReferencesWindow(props: { currentProjectFileName: string
5657
})}
5758
</div>
5859
)}
59-
</div> */}
60+
</div>
6061

6162
<div>
62-
<h3 className="mb-2 text-lg font-semibold">被引用的章节</h3>
63+
<h3 className="mb-2 text-lg font-semibold">引用此文件中一些Section框的文件</h3>
6364
{Object.keys(references.sections).length === 0 ? (
64-
<p className="text-muted-foreground text-sm">{currentProjectFileName}中没有被引用的章节</p>
65+
<p className="text-muted-foreground text-sm">{currentProjectFileName}中没有被引用的Section</p>
6566
) : (
6667
<div className="space-y-3">
6768
{Object.entries(references.sections).map(([referencedSectionName, sections]) => (
6869
<div key={referencedSectionName}>
69-
<div className="text-select-option-text hover:text-select-option-hover-text hover:bg-icon-button-bg cursor-pointer rounded p-1 font-medium">
70-
{referencedSectionName}
71-
</div>
72-
<div className="ml-4 mt-1 space-y-1">
70+
<div className="text-select-option-text rounded p-1 font-medium">{referencedSectionName}</div>
71+
<div className="my-1 ml-4 space-y-1">
7372
{sections.map((fileName) => (
7473
<div
7574
onClick={() =>
7675
project.referenceManager.jumpToReferenceLocation(fileName, referencedSectionName)
7776
}
7877
key={fileName}
79-
className="border-muted text-select-option-text hover:text-select-option-hover-text hover:bg-icon-button-bg cursor-pointer rounded border-l-2 p-1 pl-2 text-sm"
78+
className="border-muted text-select-option-text cursor-pointer rounded border-l-2 p-1 pl-2 text-sm hover:ring"
8079
>
8180
{fileName}
8281
</div>

0 commit comments

Comments
 (0)