Skip to content

Commit bf275cb

Browse files
committed
✨ 增加了导出svg面板并增加自定义图片路径设置
1 parent 8156bf6 commit bf275cb

File tree

5 files changed

+110
-55
lines changed

5 files changed

+110
-55
lines changed

app/src/core/service/dataGenerateService/stageExportEngine/StageExportSvg.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,24 @@ import { ImageNode } from "../../../stage/stageObject/entity/ImageNode";
1616
import { Stage } from "../../../stage/Stage";
1717
import { PathString } from "../../../../utils/pathString";
1818

19+
export interface SvgExportConfig {
20+
imageMode: "absolutePath" | "relativePath" | "base64";
21+
}
22+
1923
/**
2024
* 将舞台当前内容导出为SVG
2125
*
2226
*
2327
*/
2428
export namespace StageExportSvg {
29+
let svgConfig: SvgExportConfig = {
30+
imageMode: "relativePath",
31+
};
32+
33+
export function setConfig(config: SvgExportConfig) {
34+
svgConfig = config;
35+
}
36+
2537
export function dumpNode(node: TextNode) {
2638
if (node.isHiddenBySectionCollapse) {
2739
return <></>;
@@ -68,10 +80,15 @@ export namespace StageExportSvg {
6880
* @param absolutePath 是否使用绝对路径
6981
* @returns
7082
*/
71-
export function dumpImageNode(node: ImageNode, absolutePath: boolean = false) {
83+
export function dumpImageNode(node: ImageNode, svgConfigObject: SvgExportConfig) {
7284
if (node.isHiddenBySectionCollapse) {
7385
return <></>;
7486
}
87+
let imagePath = node.path;
88+
if (svgConfigObject.imageMode === "absolutePath") {
89+
imagePath = PathString.dirPath(Stage.path.getFilePath()) + PathString.getSep() + node.path;
90+
}
91+
7592
return (
7693
<>
7794
{SvgUtils.rectangle(
@@ -81,9 +98,7 @@ export namespace StageExportSvg {
8198
2,
8299
)}
83100
<image
84-
href={
85-
absolutePath ? PathString.dirPath(Stage.path.getFilePath()) + PathString.getSep() + node.path : node.path
86-
}
101+
href={imagePath}
87102
x={node.rectangle.leftTop.x}
88103
y={node.rectangle.leftTop.y}
89104
width={node.rectangle.size.x}
@@ -147,7 +162,7 @@ export namespace StageExportSvg {
147162
} else if (entity instanceof Section) {
148163
return dumpSection(entity);
149164
} else if (entity instanceof ImageNode) {
150-
return dumpImageNode(entity); // true 表示使用绝对路径
165+
return dumpImageNode(entity, svgConfig);
151166
}
152167
})}
153168
{/* 构建连线 */}
@@ -184,7 +199,7 @@ export namespace StageExportSvg {
184199
{StageManager.getTextNodes().map((node) => dumpNode(node))}
185200
{StageManager.getLineEdges().map((edge) => dumpEdge(edge))}
186201
{StageManager.getSections().map((section) => dumpSection(section))}
187-
{StageManager.getImageNodes().map((imageNode) => dumpImageNode(imageNode))}
202+
{StageManager.getImageNodes().map((imageNode) => dumpImageNode(imageNode, svgConfig))}
188203
</svg>
189204
);
190205
}

app/src/locales/zh_CN.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ appMenu:
517517
export:
518518
title: 导出
519519
items:
520+
exportAsSvg: 导出为SVG
520521
exportAsSVGByAll: 导出为SVG(全部)
521522
exportAsMarkdownBySelected: 导出为Markdown(选中)
522523
exportAsPlainText: 导出为纯文本

app/src/pages/_app_menu.tsx

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ import { Dialog } from "../components/dialog";
4747
import { Settings } from "../core/service/Settings";
4848
import { RecentFileManager } from "../core/service/dataFileService/RecentFileManager";
4949
import { StageSaveManager } from "../core/service/dataFileService/StageSaveManager";
50-
import { StageExportSvg } from "../core/service/dataGenerateService/stageExportEngine/StageExportSvg";
5150
import { CopyEngine } from "../core/service/dataManageService/copyEngine/copyEngine";
5251
import { Stage } from "../core/stage/Stage";
5352
import { GraphMethods } from "../core/stage/stageManager/basicMethods/GraphMethods";
@@ -61,6 +60,7 @@ import { Popup } from "../components/popup";
6160
import { Panel } from "../components/panel";
6261
import { ComplexityDetector } from "../core/service/dataManageService/ComplexityDetector";
6362
import ComplexityResultPanel from "./_fixed_panel/_complexity_result_panel";
63+
import ExportSvgPanel from "./_popup_panel/_export_svg_panel";
6464

6565
export default function AppMenu({ className = "", open = false }: { className?: string; open: boolean }) {
6666
const navigate = useNavigate();
@@ -252,34 +252,6 @@ export default function AppMenu({ className = "", open = false }: { className?:
252252
});
253253
}
254254
};
255-
const onSaveSVGNew = async () => {
256-
const path = isWeb
257-
? "file.svg"
258-
: await saveFileDialog({
259-
title: "另存为",
260-
defaultPath: "新文件.svg", // 提供一个默认的文件名
261-
filters: [
262-
{
263-
name: "Project Graph",
264-
extensions: ["svg"],
265-
},
266-
],
267-
});
268-
269-
if (!path) {
270-
return;
271-
}
272-
273-
const data = StageExportSvg.dumpStageToSVGString();
274-
try {
275-
await Stage.exportEngine.saveSvgHandle(path, data);
276-
} catch {
277-
await Dialog.show({
278-
title: "保存失败",
279-
content: "保存失败,请重试",
280-
});
281-
}
282-
};
283255

284256
const onExportTreeText = async () => {
285257
const selectedNodes = StageManager.getSelectedEntities().filter((entity) => entity instanceof TextNode);
@@ -445,8 +417,8 @@ export default function AppMenu({ className = "", open = false }: { className?:
445417
</Row>
446418
)}
447419
<Row icon={<File />} title={t("export.title")}>
448-
<Col icon={<FileCode />} onClick={onSaveSVGNew}>
449-
{t("export.items.exportAsSVGByAll")}
420+
<Col icon={<FileCode />} onClick={() => Popup.show(<ExportSvgPanel />, false)}>
421+
{t("export.items.exportAsSvg")}
450422
</Col>
451423
<Col icon={<FileType />} onClick={onSaveMarkdownNew}>
452424
{t("export.items.exportAsMarkdownBySelected")}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { useState } from "react";
2+
import Button from "../../components/Button";
3+
import { Dialog } from "../../components/dialog";
4+
import Select from "../../components/Select";
5+
import {
6+
StageExportSvg,
7+
SvgExportConfig,
8+
} from "../../core/service/dataGenerateService/stageExportEngine/StageExportSvg";
9+
import { Stage } from "../../core/stage/Stage";
10+
import { isWeb } from "../../utils/platform";
11+
import { save as saveFileDialog } from "@tauri-apps/plugin-dialog";
12+
13+
export default function ExportSvgPanel() {
14+
const onSaveSVGNew = async () => {
15+
const path = isWeb
16+
? "file.svg"
17+
: await saveFileDialog({
18+
title: "另存为",
19+
defaultPath: "新文件.svg", // 提供一个默认的文件名
20+
filters: [
21+
{
22+
name: "Project Graph",
23+
extensions: ["svg"],
24+
},
25+
],
26+
});
27+
28+
if (!path) {
29+
return;
30+
}
31+
StageExportSvg.setConfig(configObject);
32+
const data = StageExportSvg.dumpStageToSVGString();
33+
try {
34+
await Stage.exportEngine.saveSvgHandle(path, data);
35+
} catch {
36+
await Dialog.show({
37+
title: "保存失败",
38+
content: "保存失败,请重试",
39+
});
40+
}
41+
};
42+
43+
const onSaveSVGSelected = () => {
44+
StageExportSvg.setConfig(configObject);
45+
const svgString = StageExportSvg.dumpSelectedToSVGString();
46+
Dialog.show({
47+
title: "导出SVG",
48+
content:
49+
"SVG的本质是一堆标签代码,如果您是在写markdown格式的博客,可以直接把下面的标签代码粘贴在您的文章中。如果您想保存成文件,可以把这段代码复制到txt中并改后缀名成svg",
50+
code: svgString,
51+
type: "info",
52+
});
53+
};
54+
55+
const [configObject, setConfigObject] = useState<SvgExportConfig>({
56+
imageMode: "relativePath",
57+
});
58+
59+
return (
60+
<div className="bg-panel-bg p-4">
61+
<h2 className="mb-4 text-center text-2xl font-bold">导出SVG</h2>
62+
{/* 导出图片的svg使用绝对路径,使用相对路径,使用base64编码 */}
63+
<div className="mb-4">
64+
<p>图片节点导出模式:</p>
65+
<Select
66+
value={configObject.imageMode}
67+
options={[
68+
{ label: "绝对路径", value: "absolutePath" },
69+
{ label: "相对路径", value: "relativePath" },
70+
// { label: "base64编码", value: "base64" },
71+
]}
72+
onChange={(value) => {
73+
if (value === "absolutePath" || value === "relativePath" || value === "base64") {
74+
setConfigObject({ ...configObject, imageMode: value });
75+
}
76+
}}
77+
></Select>
78+
</div>
79+
<div className="flex flex-col gap-2">
80+
<Button onClick={onSaveSVGNew}>导出全部内容为SVG</Button>
81+
<Button onClick={onSaveSVGSelected}>仅导出选中的内容为SVG</Button>
82+
</div>
83+
</div>
84+
);
85+
}

app/src/pages/_toolbar.tsx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@ import {
1919
} from "lucide-react";
2020
import React, { useEffect, useState } from "react";
2121
import Box from "../components/Box";
22-
import { Dialog } from "../components/dialog";
2322
import { Popup } from "../components/popup";
2423
import { Color } from "../core/dataStruct/Color";
25-
import { StageExportSvg } from "../core/service/dataGenerateService/stageExportEngine/StageExportSvg";
2624
import { CopyEngine } from "../core/service/dataManageService/copyEngine/copyEngine";
2725
import { TextRiseEffect } from "../core/service/feedbackService/effectEngine/concrete/TextRiseEffect";
2826
import { ViewFlashEffect } from "../core/service/feedbackService/effectEngine/concrete/ViewFlashEffect";
@@ -195,22 +193,6 @@ export default function Toolbar({ className = "" }: { className?: string }) {
195193
}}
196194
/>
197195
)}
198-
{isHaveSelectedNode && (
199-
<ToolbarItem
200-
description="将选中内容导出SVG"
201-
icon={<SaveAll />}
202-
handleFunction={() => {
203-
const svgString = StageExportSvg.dumpSelectedToSVGString();
204-
Dialog.show({
205-
title: "导出SVG",
206-
content:
207-
"SVG的本质是一堆标签代码,如果您是在写markdown格式的博客,可以直接把下面的标签代码粘贴在您的文章中。如果您想保存成文件,可以把这段代码复制到txt中并改后缀名成svg",
208-
code: svgString,
209-
type: "info",
210-
});
211-
}}
212-
/>
213-
)}
214196
<ToolbarItem
215197
description="刷新选中内容(图片加载失败了可以选中图片然后点这个按钮)"
216198
icon={<RefreshCcw />}

0 commit comments

Comments
 (0)