From db0e8be5e27e8cc99e1c14979633eff18228d561 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Fri, 15 Nov 2024 21:14:04 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=E2=9C=A8=20rust=E4=B8=AD=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0image=E8=BD=ACbase64=E7=9A=84=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 7 +++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/lib.rs | 14 ++++++++++++++ src/pages/test.tsx | 10 ++++++++++ 4 files changed, 32 insertions(+) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ae4a0a10..d74a4e97 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -254,6 +254,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -2942,6 +2948,7 @@ dependencies = [ name = "project-graph" version = "0.1.0" dependencies = [ + "base64 0.13.1", "serde", "serde_json", "tauri", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index f4c00dfc..01b15976 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -26,3 +26,4 @@ tauri-plugin-store = "2.0.0-rc" tauri-plugin-http = "2" tauri-plugin-gamepad = "0.0.4" +base64 = "0.13" diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index ce6fafb3..7942f856 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -3,6 +3,8 @@ use std::io::Write; use std::env; use tauri::Manager; +use std::fs::read; // 引入 read 函数用于读取文件 +use base64::encode; // 引入 base64 编码函数 // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] @@ -58,6 +60,17 @@ fn check_json_exist(path: String) -> bool { // window.open_devtools(); // } +#[tauri::command] +fn convert_image_to_base64(image_path: String) -> Result { + match read(&image_path) { + Ok(image_data) => { + let base64_str = encode(&image_data); + Ok(base64_str) + }, + Err(e) => Err(format!("无法读取文件: {}", e)), + } +} + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { println!("程序运行了!"); @@ -85,6 +98,7 @@ pub fn run() { set_env_value, open_json_by_path, save_json_by_path, + convert_image_to_base64, check_json_exist // open_dev_tools ]) .run(tauri::generate_context!()) diff --git a/src/pages/test.tsx b/src/pages/test.tsx index efa2cda7..98b048d6 100644 --- a/src/pages/test.tsx +++ b/src/pages/test.tsx @@ -9,6 +9,7 @@ import { useTranslation } from "react-i18next"; import { XML } from "../utils/xml"; import { StageDumper } from "../core/stage/StageDumper"; import { fetch } from "@tauri-apps/plugin-http"; +import { invoke } from "@tauri-apps/api/core"; export default function TestPage() { const [switchValue, setSwitchValue] = React.useState(false); @@ -55,6 +56,15 @@ export default function TestPage() { + ); } + +function handleTestImageBase64() { + invoke("convert_image_to_base64", { + imagePath: "D:\\Projects\\Project-Tools\\project-graph\\src\\assets\\icon.png" + }).then((res) => { + console.log(res); + }); +} From eba1b2dc150c6af682ed7f67090becd337e20f95 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 07:35:37 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0ImageNode?= =?UTF-8?q?=E7=B1=BB=EF=BC=8C=E4=BD=86=E8=BF=98=E9=9C=80=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entityRenderer/EntityRenderer.tsx | 22 +++++++ src/core/stageObject/entity/ImageNode.tsx | 64 +++++++++++++++++++ src/types/node.tsx | 5 +- 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 src/core/stageObject/entity/ImageNode.tsx diff --git a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx index 4695c163..1896ecc9 100644 --- a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx +++ b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx @@ -11,6 +11,7 @@ import { ConnectPoint } from "../../../stageObject/entity/ConnectPoint"; import { replaceTextWhenProtect } from "../../../../utils/font"; import { Random } from "../../../algorithm/random"; import { StageStyleManager } from "../../../stageStyle/StageStyleManager"; +import { ImageNode } from "../../../stageObject/entity/ImageNode"; /** * 处理节点相关的绘制 @@ -185,4 +186,25 @@ export namespace EntityRenderer { 2 * Camera.currentScale, ); } + + export function renderImageNode(imageNode: ImageNode) { + if (imageNode.isSelected) { + // 在外面增加一个框 + CollisionBoxRenderer.render( + imageNode.collisionBox, + StageStyleManager.currentStyle.CollideBoxSelectedColor, + ); + } + // 节点身体矩形 + RenderUtils.renderRect( + new Rectangle( + Renderer.transformWorld2View(imageNode.rectangle.location), + imageNode.rectangle.size.multiply(Camera.currentScale), + ), + Color.Transparent, + StageStyleManager.currentStyle.StageObjectBorderColor, + 2 * Camera.currentScale, + Renderer.NODE_ROUNDED_RADIUS * Camera.currentScale, + ); + } } diff --git a/src/core/stageObject/entity/ImageNode.tsx b/src/core/stageObject/entity/ImageNode.tsx new file mode 100644 index 00000000..4af21933 --- /dev/null +++ b/src/core/stageObject/entity/ImageNode.tsx @@ -0,0 +1,64 @@ +import { Serialized } from "../../../types/node"; +import { Rectangle } from "../../dataStruct/shape/Rectangle"; +import { Vector } from "../../dataStruct/Vector"; +import { CollisionBox } from "../collisionBox/collisionBox"; +import { ConnectableEntity } from "../StageObject"; + +export class ImageNode extends ConnectableEntity { + + isHiddenBySectionCollapse: boolean = false; + public uuid: string; + public collisionBox: CollisionBox; + public path: string; + /** + * 节点是否被选中 + */ + _isSelected: boolean = false; + + /** + * 获取节点的选中状态 + */ + public get isSelected() { + return this._isSelected; + } + constructor( + { + uuid, + location = [0, 0], + path = "" + }: Partial & { uuid: string }, + public unknown = false, + ) { + super(); + this.uuid = uuid; + this.path = path; + this.collisionBox = new CollisionBox([ + new Rectangle(new Vector(...location), new Vector(...[100, 100])), + ]); + } + + /** + * 只读,获取节点的矩形 + * 若要修改节点的矩形,请使用 moveTo等 方法 + */ + public get rectangle(): Rectangle { + return this.collisionBox.shapeList[0] as Rectangle; + } + + public get geometryCenter() { + return this.rectangle.location + .clone() + .add(this.rectangle.size.clone().multiply(0.5)); + } + + move(delta: Vector): void { + const newRectangle = this.rectangle.clone(); + newRectangle.location = newRectangle.location.add(delta); + this.collisionBox.shapeList[0] = newRectangle; + } + moveTo(location: Vector): void { + const newRectangle = this.rectangle.clone(); + newRectangle.location = location.clone(); + this.collisionBox.shapeList[0] = newRectangle; + } +} \ No newline at end of file diff --git a/src/types/node.tsx b/src/types/node.tsx index b479e991..62ef3b90 100644 --- a/src/types/node.tsx +++ b/src/types/node.tsx @@ -34,7 +34,10 @@ export namespace Serialized { export type ConnectPoint = Entity & { type: "core:connect_point"; } - + export type ImageNode = Entity & { + path: string; + type: "core:image_node"; + } export type Edge = StageObject & { type: "core:edge"; source: string; From 753d9a7bcd95aa0d71970e860a79b7af7d4a80ea Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 13:11:56 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=9A=A7=20=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E7=AE=A1=E7=90=86=E6=9E=81=E5=85=B6=E5=9B=B0?= =?UTF-8?q?=E9=9A=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/lib.rs | 25 +++++- .../controller/concrete/ControllerCopy.tsx | 35 ++++++--- src/core/render/canvas2d/ImageRenderer.tsx | 66 ++++++++++++++++ src/core/render/canvas2d/RenderUtils.tsx | 5 ++ .../entityRenderer/EntityRenderer.tsx | 31 ++++++-- src/core/render/canvas2d/renderer.tsx | 6 ++ src/core/stage/stageManager/StageManager.tsx | 7 ++ src/core/stageObject/entity/ImageNode.tsx | 78 +++++++++++++++++-- 8 files changed, 226 insertions(+), 27 deletions(-) create mode 100644 src/core/render/canvas2d/ImageRenderer.tsx diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 7942f856..20a83ee2 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,10 +1,11 @@ +use std::fs::File; use std::io::Read; use std::io::Write; +use base64::{decode, encode}; use std::env; -use tauri::Manager; use std::fs::read; // 引入 read 函数用于读取文件 -use base64::encode; // 引入 base64 编码函数 +use tauri::Manager; // 引入 base64 编码函数 // Learn more about Tauri commands at https://tauri.app/v1/guides/features/command #[tauri::command] @@ -66,11 +67,28 @@ fn convert_image_to_base64(image_path: String) -> Result { Ok(image_data) => { let base64_str = encode(&image_data); Ok(base64_str) - }, + } Err(e) => Err(format!("无法读取文件: {}", e)), } } +/// 将base64编码字符串保存为图片文件 +#[tauri::command] +fn save_base64_to_image(base64_str: &str, file_name: &str) -> Result<(), String> { + // 进行解码 + match decode(base64_str) { + Ok(image_data) => { + // 创建文件并写入数据 + let mut file = File::create(file_name).map_err(|e| format!("无法创建文件: {}", e))?; + file.write_all(&image_data) + .map_err(|e| format!("无法写入文件: {}", e))?; + Ok(()) + } + Err(e) => Err(format!("解码失败: {}", e)), + } +} + + #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { println!("程序运行了!"); @@ -99,6 +117,7 @@ pub fn run() { open_json_by_path, save_json_by_path, convert_image_to_base64, + save_base64_to_image, check_json_exist // open_dev_tools ]) .run(tauri::generate_context!()) diff --git a/src/core/controller/concrete/ControllerCopy.tsx b/src/core/controller/concrete/ControllerCopy.tsx index 55307044..0c3e53ad 100644 --- a/src/core/controller/concrete/ControllerCopy.tsx +++ b/src/core/controller/concrete/ControllerCopy.tsx @@ -10,6 +10,8 @@ import { Controller } from "../Controller"; import { ControllerClass } from "../ControllerClass"; import { v4 as uuidv4 } from "uuid"; import { Entity } from "../../stageObject/StageObject"; +import { ImageNode } from "../../stageObject/entity/ImageNode"; +import { invoke } from "@tauri-apps/api/core"; /** * 关于复制相关的功能 @@ -130,26 +132,37 @@ ControllerCopy.keydown = (event: KeyboardEvent) => { // } async function readClipboardItems(mouseLocation: Vector) { + // const [file] = useRecoilState(fileAtom); + // console.log(file); + // 必须在组件函数里。 + // test try { navigator.clipboard.read().then(async (items) => { for (const item of items) { if ( - item.types.includes("image/png") || - item.types.includes("image/jpeg") + item.types.includes("image/png") ) { const blob = await item.getType(item.types[0]); // 获取 Blob 对象 const base64String = await convertBlobToBase64(blob); // 转换为 Base64 字符串 - console.log("Base64 String:", base64String); + const imageUUID = uuidv4(); + const imagePath = `images/${imageUUID}.png`; - // 显示图像 - // const url = `data:${blob.type};base64,${base64String}`; - // const imageElement = document.getElementById( - // "clipboardImage", - // ) as HTMLImageElement; - // imageElement.src = url; - // imageElement.style.display = "block"; - break; + invoke("save_base64_to_image", { + base64Str: base64String, + fileName: imagePath + }).then(() => { + console.log("save image to file success"); + const imageNode = new ImageNode({ + uuid: imageUUID, + location: [mouseLocation.x, mouseLocation.y], + path: `${imageUUID}.png` + }) + imageNode.setBase64StringForced(base64String); + StageManager.addImageNode(imageNode); + }).catch(error => { + console.error("save image to file error", error); + }) } if (item.types.includes("text/plain")) { const blob = await item.getType("text/plain"); // 获取文本内容 diff --git a/src/core/render/canvas2d/ImageRenderer.tsx b/src/core/render/canvas2d/ImageRenderer.tsx new file mode 100644 index 00000000..aa32d819 --- /dev/null +++ b/src/core/render/canvas2d/ImageRenderer.tsx @@ -0,0 +1,66 @@ +import { StringDict } from "../../dataStruct/StringDict"; +import { Vector } from "../../dataStruct/Vector"; +import { Camera } from "../../stage/Camera"; +import { Canvas } from "../../stage/Canvas"; + +/** + * 图片渲染器 + */ +export namespace ImageRenderer { + // 有待改成长字符串缓存字典,并测试性能对比 + const imageBase64Cache: StringDict = StringDict.create(); + + export function getImageSizeByBase64(imageBase64: string): Vector { + if (imageBase64Cache.hasId(imageBase64)) { + const imageElement = imageBase64Cache.getById(imageBase64); + if (imageElement) { + return new Vector(imageElement.width, imageElement.height); + } + } + return new Vector(0, 0); + } + + /** + * 根据base64编码字符串来渲染出图片 + * @param imageBase64 base64编码的图片字符串,不包含前缀data:image/png;base64, + * @param location + */ + export function renderPngBase64FromLeftTop( + imageBase64: string, + location: Vector, + ) { + if (imageBase64Cache.hasId(imageBase64)) { + const imageElement = imageBase64Cache.getById(imageBase64); + if (imageElement) { + Canvas.ctx.drawImage( + imageElement, + location.x, + location.y, + imageElement.width * Camera.currentScale, + imageElement.height * Camera.currentScale, + ); + } + } else { + // 字典中没有,则创建,并缓存 + const imageElement = new Image(); + imageElement.src = `data:image/png;base64,${imageBase64}`; + imageElement.onload = () => { + imageBase64Cache.setById(imageBase64, imageElement); + // 调整碰撞箱大小 + }; + } + } + + export function renderImageElement( + imageElement: HTMLImageElement, + location: Vector, + ) { + Canvas.ctx.drawImage( + imageElement, + location.x, + location.y, + imageElement.width * Camera.currentScale, + imageElement.height * Camera.currentScale, + ); + } +} diff --git a/src/core/render/canvas2d/RenderUtils.tsx b/src/core/render/canvas2d/RenderUtils.tsx index 95f87e48..e0fae702 100644 --- a/src/core/render/canvas2d/RenderUtils.tsx +++ b/src/core/render/canvas2d/RenderUtils.tsx @@ -5,6 +5,7 @@ import { Vector } from "../../dataStruct/Vector"; import { CubicBezierCurve, SymmetryCurve } from "../../dataStruct/shape/Curve"; import { Camera } from "../../stage/Camera"; + /** * 一些基础的渲染图形 * 注意:这些渲染的参数都是View坐标系下的。 @@ -402,6 +403,7 @@ export namespace RenderUtils { height, ); } + /** * 绘制一个像素点 * @param location @@ -455,4 +457,7 @@ export namespace RenderUtils { Canvas.ctx.fillStyle = color.toString(); Canvas.ctx.fill(); } + + + } diff --git a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx index 1896ecc9..3fc0ee82 100644 --- a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx +++ b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx @@ -12,6 +12,7 @@ import { replaceTextWhenProtect } from "../../../../utils/font"; import { Random } from "../../../algorithm/random"; import { StageStyleManager } from "../../../stageStyle/StageStyleManager"; import { ImageNode } from "../../../stageObject/entity/ImageNode"; +import { ImageRenderer } from "../ImageRenderer"; /** * 处理节点相关的绘制 @@ -39,7 +40,9 @@ export namespace EntityRenderer { section.rectangle.location.add(Vector.same(Renderer.NODE_PADDING)), ), Renderer.FONT_SIZE * Camera.currentScale, - section.color.a === 1 ? colorInvert(section.color) : colorInvert(StageStyleManager.currentStyle.BackgroundColor) + section.color.a === 1 + ? colorInvert(section.color) + : colorInvert(StageStyleManager.currentStyle.BackgroundColor), ); } else { RenderUtils.renderRect( @@ -59,7 +62,9 @@ export namespace EntityRenderer { section.rectangle.location.add(Vector.same(Renderer.NODE_PADDING)), ), Renderer.FONT_SIZE * Camera.currentScale, - section.color.a === 1 ? colorInvert(section.color) : colorInvert(StageStyleManager.currentStyle.BackgroundColor), + section.color.a === 1 + ? colorInvert(section.color) + : colorInvert(StageStyleManager.currentStyle.BackgroundColor), ); } @@ -104,17 +109,23 @@ export namespace EntityRenderer { node.rectangle.location.add(Vector.same(Renderer.NODE_PADDING)), ), Renderer.FONT_SIZE * Camera.currentScale, - node.color.a === 1 ? colorInvert(node.color) : colorInvert(StageStyleManager.currentStyle.BackgroundColor), + node.color.a === 1 + ? colorInvert(node.color) + : colorInvert(StageStyleManager.currentStyle.BackgroundColor), ); } if (node.isSelected) { // 在外面增加一个框 - CollisionBoxRenderer.render(node.collisionBox, StageStyleManager.currentStyle.CollideBoxSelectedColor); + CollisionBoxRenderer.render( + node.collisionBox, + StageStyleManager.currentStyle.CollideBoxSelectedColor, + ); } if (node.isAiGenerating) { - const borderColor = StageStyleManager.currentStyle.CollideBoxSelectedColor.clone(); - borderColor.a = Random.randomFloat(0.2, 1); + const borderColor = + StageStyleManager.currentStyle.CollideBoxSelectedColor.clone(); + borderColor.a = Random.randomFloat(0.2, 1); // 在外面增加一个框 RenderUtils.renderRect( new Rectangle( @@ -146,7 +157,7 @@ export namespace EntityRenderer { ), Renderer.FONT_SIZE_DETAILS * Camera.currentScale, Renderer.NODE_DETAILS_WIDTH * Camera.currentScale, - StageStyleManager.currentStyle.NodeDetailsTextColor + StageStyleManager.currentStyle.NodeDetailsTextColor, ); } export function colorInvert(color: Color): Color { @@ -206,5 +217,11 @@ export namespace EntityRenderer { 2 * Camera.currentScale, Renderer.NODE_ROUNDED_RADIUS * Camera.currentScale, ); + if (imageNode.isLoaded) { + ImageRenderer.renderImageElement( + imageNode.imageElement, + Renderer.transformWorld2View(imageNode.rectangle.location), + ) + } } } diff --git a/src/core/render/canvas2d/renderer.tsx b/src/core/render/canvas2d/renderer.tsx index deea2bba..7b68405b 100644 --- a/src/core/render/canvas2d/renderer.tsx +++ b/src/core/render/canvas2d/renderer.tsx @@ -326,6 +326,12 @@ export namespace Renderer { } EntityRenderer.renderConnectPoint(connectPoint); } + for (const imageNode of StageManager.getImageNodes()) { + if (!viewRectangle.isCollideWith(imageNode.rectangle)) { + continue; + } + EntityRenderer.renderImageNode(imageNode); + } } export function renderEdges(viewRectangle: Rectangle) { diff --git a/src/core/stage/stageManager/StageManager.tsx b/src/core/stage/stageManager/StageManager.tsx index 3dfaf115..6b701855 100644 --- a/src/core/stage/stageManager/StageManager.tsx +++ b/src/core/stage/stageManager/StageManager.tsx @@ -29,6 +29,7 @@ import { StageSectionPackManager } from "./concreteMethods/StageSectionPackManag import { StageNodeTextTransfer } from "./concreteMethods/StageNodeTextTransfer"; import { ConnectPoint } from "../../stageObject/entity/ConnectPoint"; import { StageGeneratorAI } from "./concreteMethods/StageGeneratorAI"; +import { ImageNode } from "../../stageObject/entity/ImageNode"; // littlefean:应该改成类,实例化的对象绑定到舞台上。这成单例模式了 // 开发过程中会造成多开 @@ -57,6 +58,9 @@ export namespace StageManager { export function getSections(): Section[] { return entities.valuesToArray().filter((node) => node instanceof Section); } + export function getImageNodes(): ImageNode[] { + return entities.valuesToArray().filter((node) => node instanceof ImageNode); + } export function getConnectPoints(): ConnectPoint[] { return entities .valuesToArray() @@ -119,6 +123,9 @@ export namespace StageManager { export function addTextNode(node: TextNode) { entities.addValue(node, node.uuid); } + export function addImageNode(node: ImageNode) { + entities.addValue(node, node.uuid); + } export function addSection(section: Section) { entities.addValue(section, section.uuid); } diff --git a/src/core/stageObject/entity/ImageNode.tsx b/src/core/stageObject/entity/ImageNode.tsx index 4af21933..e23e4ba5 100644 --- a/src/core/stageObject/entity/ImageNode.tsx +++ b/src/core/stageObject/entity/ImageNode.tsx @@ -1,3 +1,4 @@ +import { invoke } from "@tauri-apps/api/core"; import { Serialized } from "../../../types/node"; import { Rectangle } from "../../dataStruct/shape/Rectangle"; import { Vector } from "../../dataStruct/Vector"; @@ -5,10 +6,14 @@ import { CollisionBox } from "../collisionBox/collisionBox"; import { ConnectableEntity } from "../StageObject"; export class ImageNode extends ConnectableEntity { - isHiddenBySectionCollapse: boolean = false; public uuid: string; public collisionBox: CollisionBox; + + /** + * 这里的path是相对于工程文件的相对路径 + * 例如:"example.png" + */ public path: string; /** * 节点是否被选中 @@ -21,11 +26,56 @@ export class ImageNode extends ConnectableEntity { public get isSelected() { return this._isSelected; } + + public set isSelected(value: boolean) { + this._isSelected = value; + } + + private _base64String: string = ""; + + public get base64String(): string { + if (this._base64String !== "") { + return this._base64String; + } else { + // 需要通过path获取base64String + return ""; + } + } + + private _imageElement: HTMLImageElement | null = null; + + public get imageElement(): HTMLImageElement { + if (this._imageElement!== null) { + return this._imageElement; + } else { + // 需要通过base64String创建imageElement + const imageElement = new Image(); + imageElement.src = `data:image/png;base64,${this.base64String}`; + imageElement.onload = () => { + // 调整碰撞箱大小 + this.rectangle.size = new Vector(imageElement.width, imageElement.height); + } + return imageElement; + } + } + + /** + * 用于粘贴板强制输入 + * @param base64String + */ + public setBase64StringForced(base64String: string) { + this._base64String = base64String; + } + + public get isLoaded(): boolean { + return this._base64String !== ""; + } + constructor( { uuid, location = [0, 0], - path = "" + path = "", }: Partial & { uuid: string }, public unknown = false, ) { @@ -36,7 +86,23 @@ export class ImageNode extends ConnectableEntity { new Rectangle(new Vector(...location), new Vector(...[100, 100])), ]); } - + + /** + * + * @param path 工程文件所在路径文件夹,不加尾部斜杠 + * @returns + */ + public updateBase64StringByPath(path: string) { + if (this.path === "") { + return; + } + invoke("convert_image_to_base64", { + imagePath: `${path}\\${this.path}` + }).then((res) => { + this._base64String = res; + }); + } + /** * 只读,获取节点的矩形 * 若要修改节点的矩形,请使用 moveTo等 方法 @@ -44,13 +110,13 @@ export class ImageNode extends ConnectableEntity { public get rectangle(): Rectangle { return this.collisionBox.shapeList[0] as Rectangle; } - + public get geometryCenter() { return this.rectangle.location .clone() .add(this.rectangle.size.clone().multiply(0.5)); } - + move(delta: Vector): void { const newRectangle = this.rectangle.clone(); newRectangle.location = newRectangle.location.add(delta); @@ -61,4 +127,4 @@ export class ImageNode extends ConnectableEntity { newRectangle.location = location.clone(); this.collisionBox.shapeList[0] = newRectangle; } -} \ No newline at end of file +} From c7c664821daa6ca860202b8c03d60677f7f7e743 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 14:00:09 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=8E=A8=20=E5=A2=9E=E5=8A=A0Stage.Pa?= =?UTF-8?q?th=EF=BC=8C=E6=8B=93=E5=AE=BD=E8=AF=A5=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E7=9A=84=E8=AF=BB=E5=8F=96=E8=8C=83=E5=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/concrete/ControllerCopy.tsx | 3 -- src/core/render/canvas2d/renderer.tsx | 3 +- src/core/stage/Stage.tsx | 34 +++++++++++++++++++ src/core/stage/StageSaveManager.tsx | 32 +++++++++++++++++ .../stageManager/StageHistoryManager.tsx | 4 +-- src/pages/_app.tsx | 12 +++++-- src/pages/_app_menu.tsx | 1 - src/pages/_recent_files_panel.tsx | 5 +-- src/pages/_start_file_panel.tsx | 2 +- src/pages/_toolbar.tsx | 6 +--- src/pages/test.tsx | 1 + 11 files changed, 85 insertions(+), 18 deletions(-) diff --git a/src/core/controller/concrete/ControllerCopy.tsx b/src/core/controller/concrete/ControllerCopy.tsx index 0c3e53ad..bcbaac56 100644 --- a/src/core/controller/concrete/ControllerCopy.tsx +++ b/src/core/controller/concrete/ControllerCopy.tsx @@ -132,9 +132,6 @@ ControllerCopy.keydown = (event: KeyboardEvent) => { // } async function readClipboardItems(mouseLocation: Vector) { - // const [file] = useRecoilState(fileAtom); - // console.log(file); - // 必须在组件函数里。 // test try { diff --git a/src/core/render/canvas2d/renderer.tsx b/src/core/render/canvas2d/renderer.tsx index 7b68405b..9e8374c7 100644 --- a/src/core/render/canvas2d/renderer.tsx +++ b/src/core/render/canvas2d/renderer.tsx @@ -526,7 +526,8 @@ export namespace Renderer { `历史: ${StageHistoryManager.statusText()}`, `fps: ${(1 / deltaTime).toFixed()}`, `delta: ${deltaTime.toFixed(2)}`, - `Controller.isViewMoveByClickMiddle: ${Controller.isViewMoveByClickMiddle}` + `Controller.isViewMoveByClickMiddle: ${Controller.isViewMoveByClickMiddle}`, + `path: ${Stage.Path.getPath()}` ]; for (const [k, v] of Object.entries(timings)) { detailsData.push(`time:${k}: ${v.toFixed(2)}`); diff --git a/src/core/stage/Stage.tsx b/src/core/stage/Stage.tsx index 23d9d005..c9cd5144 100644 --- a/src/core/stage/Stage.tsx +++ b/src/core/stage/Stage.tsx @@ -23,6 +23,40 @@ import { ControllerGamepad } from "../controller/ControllerGamepad"; * 但这个里面主要存一些动态的属性,以及特效交互等信息 */ export namespace Stage { + + /** + * 此Path存在的意义为摆脱状态管理只能在组件函数中的限制 + */ + export namespace Path { + let currentPath = "Project Graph"; + export const draftName = "Project Graph"; + + /** + * 是否是草稿 + * @returns + */ + export function isDraft() { + return currentPath === "Project Graph"; + } + + /** + * 此函数唯一的调用:只能在app.tsx的useEffect检测函数中调用 + * 为了同步状态管理中的路径。 + * @param path + */ + export function setPathInEffect(path: string) { + currentPath = path; + } + + /** + * 提供一个函数供外部调用,获取当前路径 + * @returns + */ + export function getPath() { + return currentPath; + } + } + export let effects: Effect[] = []; /** * 是否正在框选 diff --git a/src/core/stage/StageSaveManager.tsx b/src/core/stage/StageSaveManager.tsx index 04db637d..591851e6 100644 --- a/src/core/stage/StageSaveManager.tsx +++ b/src/core/stage/StageSaveManager.tsx @@ -39,6 +39,38 @@ export namespace StageSaveManager { }); } + /** + * + * without path 意思是不需要传入path,直接使用当前的path + * @param data + * @param successCallback + * @param errorCallback + */ + export function saveHandleWithoutCurrentPath( + data: Serialized.File, + successCallback: () => void, + errorCallback: (err: any) => void, + ) { + if (Stage.Path.isDraft()) { + errorCallback("当前文档的状态为草稿,请您先保存为文件"); + return; + } + invoke("save_json_by_path", { + path: Stage.Path.getPath(), + content: JSON.stringify(data, null, 2), + }) + .then((res) => { + console.log(res); + Stage.effects.push(ViewFlashEffect.SaveFile()); + StageHistoryManager.reset(data); // 重置历史 + successCallback(); + isCurrentSaved = true; + }) + .catch((err) => { + errorCallback(err); + }); + } + export function saveSvgHandle( path: string, string: string, diff --git a/src/core/stage/stageManager/StageHistoryManager.tsx b/src/core/stage/stageManager/StageHistoryManager.tsx index f9cdcb06..1efb003b 100644 --- a/src/core/stage/stageManager/StageHistoryManager.tsx +++ b/src/core/stage/stageManager/StageHistoryManager.tsx @@ -43,8 +43,8 @@ export namespace StageHistoryManager { }); } - export function reset(file: Serialized.File) { - historyList = [file]; + export function reset(serializedFile: Serialized.File) { + historyList = [serializedFile]; currentIndex = 0; StageSaveManager.setIsCurrentSaved(true); } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ee299109..6ba67015 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -29,6 +29,7 @@ import { StageDumper } from "../core/stage/StageDumper"; import StartFilePanel from "./_start_file_panel"; import { PathString } from "../utils/pathString"; import { useTranslation } from "react-i18next"; +import { Stage } from "../core/stage/Stage"; export default function App() { const [maxmized, setMaxmized] = React.useState(false); @@ -86,11 +87,16 @@ export default function App() { }; }, []); + /** + * 监控路径变化的地方 + */ React.useEffect(() => { - if (file === "Project Graph") { - getCurrentWindow().setTitle("Project Graph"); + if (file === Stage.Path.draftName) { + getCurrentWindow().setTitle(Stage.Path.draftName); + Stage.Path.setPathInEffect(Stage.Path.draftName) } else { getCurrentWindow().setTitle(`${filename} - Project Graph`); + Stage.Path.setPathInEffect(file); } }, [file]); @@ -105,7 +111,7 @@ export default function App() { }, [maxmized]); const handleClose = () => { - if (file === "Project Graph") { + if (file === Stage.Path.draftName) { dialog.show({ title: "真的要关闭吗?", content: "您现在的新建草稿没有保存,是否要关闭项目?", diff --git a/src/pages/_app_menu.tsx b/src/pages/_app_menu.tsx index 5ca9e8c1..c764a84c 100644 --- a/src/pages/_app_menu.tsx +++ b/src/pages/_app_menu.tsx @@ -413,7 +413,6 @@ export default function AppMenu({ onClick={() => { console.log(StageManager.getEntities()); console.log(StageManager.getEdges()); - console.log(file); // localStorage测试 // 尽量不要用这个,端口号一变就没了 localStorage.setItem("_test", "123"); diff --git a/src/pages/_recent_files_panel.tsx b/src/pages/_recent_files_panel.tsx index 4df98db2..9ab1e5d7 100644 --- a/src/pages/_recent_files_panel.tsx +++ b/src/pages/_recent_files_panel.tsx @@ -15,6 +15,7 @@ import { RecentFileManager } from "../core/RecentFileManager"; import { useDialog } from "../utils/dialog"; import { isDesktop } from "../utils/platform"; import { StageSaveManager } from "../core/stage/StageSaveManager"; +import { Stage } from "../core/stage/Stage"; export default function RecentFilesPanel() { const [recentFiles, setRecentFiles] = React.useState< @@ -50,7 +51,7 @@ export default function RecentFilesPanel() { const onClickFile = (file: RecentFileManager.RecentFile) => { return () => { - if (currentFile === "Project Graph") { + if (currentFile === Stage.Path.draftName) { dialog.show({ title: "真的要切换吗?", content: "您现在的新建草稿没有保存,是否要切换项目?", @@ -83,7 +84,7 @@ export default function RecentFilesPanel() { const checkoutFile = (file: RecentFileManager.RecentFile) => { try { const path = file.path; - setFile(decodeURIComponent(path)); + setFile(decodeURIComponent(path)); if (isDesktop && !path.endsWith(".json")) { dialog.show({ title: "请选择一个JSON文件", diff --git a/src/pages/_start_file_panel.tsx b/src/pages/_start_file_panel.tsx index f4fac4d2..6754f82f 100644 --- a/src/pages/_start_file_panel.tsx +++ b/src/pages/_start_file_panel.tsx @@ -156,7 +156,7 @@ export default function StartFilePanel({ open = false }: { open: boolean }) { }; const checkoutFile = (path: string) => { try { - setFile(decodeURIComponent(path)); + setFile(decodeURIComponent(path)); if (isDesktop && !path.endsWith(".json")) { dialog.show({ title: "请选择一个JSON文件", diff --git a/src/pages/_toolbar.tsx b/src/pages/_toolbar.tsx index b5f3a8bc..ccf094c1 100644 --- a/src/pages/_toolbar.tsx +++ b/src/pages/_toolbar.tsx @@ -40,8 +40,6 @@ import { invoke } from "@tauri-apps/api/core"; import { ViewFlashEffect } from "../core/effect/concrete/ViewFlashEffect"; import { save as saveFileDialog } from "@tauri-apps/plugin-dialog"; import { StageSaveManager } from "../core/stage/StageSaveManager"; -import { useRecoilState } from "recoil"; -import { fileAtom } from "../state"; interface ToolbarItemProps { icon: React.ReactNode; // 定义 icon 的类型 @@ -222,7 +220,6 @@ function AlignNodePanel() { */ export default function Toolbar({ className = "" }: { className?: string }) { const popupDialog = usePopupDialog(); - const [file] = useRecoilState(fileAtom); const [isCopyClearShow, setIsCopyClearShow] = useState(false); useEffect(() => { @@ -312,8 +309,7 @@ export default function Toolbar({ className = "" }: { className?: string }) { openBrowserOrFile(); } else { // Stage.effects.push(new TextRiseEffect("请先保存文件")); - StageSaveManager.saveHandle( - file, + StageSaveManager.saveHandleWithoutCurrentPath( StageDumper.dump(), () => { openBrowserOrFile(); diff --git a/src/pages/test.tsx b/src/pages/test.tsx index 98b048d6..69a39cb2 100644 --- a/src/pages/test.tsx +++ b/src/pages/test.tsx @@ -67,4 +67,5 @@ function handleTestImageBase64() { }).then((res) => { console.log(res); }); + } From 8291f45068583da457f48bd78f9c2479396577f3 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 14:32:39 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=8E=A8=20dirPath=E5=87=BD=E6=95=B0?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=B9=B6=E6=B5=8B=E8=AF=95=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + pnpm-lock.yaml | 290 ++++++++++++++++++++++++- src/__tests__/unit/pathString.test.tsx | 52 +++++ src/core/render/canvas2d/renderer.tsx | 2 +- src/core/stage/Stage.tsx | 4 +- src/core/stage/StageSaveManager.tsx | 2 +- src/utils/pathString.tsx | 38 ++++ vitest.config.ts | 2 +- 8 files changed, 384 insertions(+), 7 deletions(-) create mode 100644 src/__tests__/unit/pathString.test.tsx diff --git a/package.json b/package.json index 9bc60d22..710b0000 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react": "^7.37.2", "globals": "^15.11.0", + "jsdom": "^25.0.1", "postcss": "^8.4.47", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.8", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d65bffbd..16db3b1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: globals: specifier: ^15.11.0 version: 15.11.0 + jsdom: + specifier: ^25.0.1 + version: 25.0.1 postcss: specifier: ^8.4.47 version: 8.4.47 @@ -134,7 +137,7 @@ importers: version: 4.2.0(rollup@4.24.0)(typescript@5.6.3)(vite@5.4.8) vitest: specifier: ^2.1.4 - version: 2.1.4 + version: 2.1.4(jsdom@25.0.1) packages: @@ -932,6 +935,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1000,6 +1007,9 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.20: resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} @@ -1096,6 +1106,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1124,9 +1138,17 @@ packages: engines: {node: '>=4'} hasBin: true + cssstyle@4.1.0: + resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} + engines: {node: '>=18'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + data-view-buffer@1.0.1: resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} engines: {node: '>= 0.4'} @@ -1148,6 +1170,9 @@ packages: supports-color: optional: true + decimal.js@10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + deep-eql@5.0.2: resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} @@ -1163,6 +1188,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -1366,6 +1395,10 @@ packages: resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1468,12 +1501,28 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + html-parse-stringify@3.0.1: resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.5: + resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + engines: {node: '>= 14'} + i18next@23.16.2: resolution: {integrity: sha512-dFyxwLXxEQK32f6tITBMaRht25mZPJhQ0WbC0p3bO2mWBal9lABTMqSka5k+GLSRWLzeJBKDpH7BeIA9TZI7Jg==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1563,6 +1612,9 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -1622,6 +1674,15 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^2.11.2 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.0.2: resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} engines: {node: '>=6'} @@ -1705,6 +1766,14 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -1744,6 +1813,9 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + nwsapi@2.2.13: + resolution: {integrity: sha512-cTGB9ptp9dY9A5VbMSe7fQBcl/tt22Vcqdq8+eN93rblOuE0aCFu4aZ2vMwct/2t+lFnosm8RkQW1I0Omb1UtQ==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1799,6 +1871,9 @@ packages: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2056,6 +2131,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + rrweb-cssom@0.7.1: + resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -2067,6 +2145,13 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} @@ -2177,6 +2262,9 @@ packages: svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + synckit@0.9.2: resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} engines: {node: ^14.18.0 || >=16.0.0} @@ -2220,6 +2308,13 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@6.1.61: + resolution: {integrity: sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==} + + tldts@6.1.61: + resolution: {integrity: sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==} + hasBin: true + to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -2232,6 +2327,14 @@ packages: resolution: {integrity: sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==} engines: {node: '>=10'} + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} + + tr46@5.0.0: + resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} + engines: {node: '>=18'} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -2370,6 +2473,26 @@ packages: resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.0.0: + resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + engines: {node: '>=18'} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2407,6 +2530,25 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3177,6 +3319,12 @@ snapshots: acorn@8.13.0: {} + agent-base@7.1.1: + dependencies: + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3267,6 +3415,8 @@ snapshots: assertion-error@2.0.1: {} + asynckit@0.4.0: {} + autoprefixer@10.4.20(postcss@8.4.47): dependencies: browserslist: 4.24.0 @@ -3372,6 +3522,10 @@ snapshots: color-name@1.1.4: {} + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@4.1.1: {} concat-map@0.0.1: {} @@ -3395,8 +3549,17 @@ snapshots: cssesc@3.0.0: {} + cssstyle@4.1.0: + dependencies: + rrweb-cssom: 0.7.1 + csstype@3.1.3: {} + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + data-view-buffer@1.0.1: dependencies: call-bind: 1.0.7 @@ -3419,6 +3582,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.4.3: {} + deep-eql@5.0.2: {} deep-is@0.1.4: {} @@ -3435,6 +3600,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + didyoumean@1.2.2: {} dlv@1.1.3: {} @@ -3747,6 +3914,12 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fraction.js@4.3.7: {} fsevents@2.3.3: @@ -3841,14 +4014,36 @@ snapshots: dependencies: function-bind: 1.1.2 + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + html-parse-stringify@3.0.1: dependencies: void-elements: 3.1.0 + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.1 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.5: + dependencies: + agent-base: 7.1.1 + debug: 4.3.7 + transitivePeerDependencies: + - supports-color + i18next@23.16.2: dependencies: '@babel/runtime': 7.25.7 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} import-fresh@3.3.0: @@ -3928,6 +4123,8 @@ snapshots: is-number@7.0.0: {} + is-potential-custom-element-name@1.0.1: {} + is-regex@1.1.4: dependencies: call-bind: 1.0.7 @@ -3988,6 +4185,34 @@ snapshots: dependencies: argparse: 2.0.1 + jsdom@25.0.1: + dependencies: + cssstyle: 4.1.0 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.13 + parse5: 7.2.1 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.0.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + jsesc@3.0.2: {} json-buffer@3.0.1: {} @@ -4059,6 +4284,12 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -4092,6 +4323,8 @@ snapshots: normalize-range@0.1.2: {} + nwsapi@2.2.13: {} + object-assign@4.1.1: {} object-hash@3.0.0: {} @@ -4156,6 +4389,10 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse5@7.2.1: + dependencies: + entities: 4.5.0 + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -4349,6 +4586,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 + rrweb-cssom@0.7.1: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -4366,6 +4605,12 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.23.2: dependencies: loose-envify: 1.4.0 @@ -4501,6 +4746,8 @@ snapshots: svg-parser@2.0.4: {} + symbol-tree@3.2.4: {} + synckit@0.9.2: dependencies: '@pkgr/core': 0.1.1 @@ -4560,6 +4807,12 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@6.1.61: {} + + tldts@6.1.61: + dependencies: + tldts-core: 6.1.61 + to-fast-properties@2.0.0: {} to-regex-range@5.0.1: @@ -4568,6 +4821,14 @@ snapshots: tosource@2.0.0-alpha.3: {} + tough-cookie@5.0.0: + dependencies: + tldts: 6.1.61 + + tr46@5.0.0: + dependencies: + punycode: 2.3.1 + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: typescript: 5.6.3 @@ -4684,7 +4945,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - vitest@2.1.4: + vitest@2.1.4(jsdom@25.0.1): dependencies: '@vitest/expect': 2.1.4 '@vitest/mocker': 2.1.4(vite@5.4.8) @@ -4706,6 +4967,8 @@ snapshots: vite: 5.4.8 vite-node: 2.1.4 why-is-node-running: 2.3.0 + optionalDependencies: + jsdom: 25.0.1 transitivePeerDependencies: - less - lightningcss @@ -4719,6 +4982,23 @@ snapshots: void-elements@3.1.0: {} + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.0.0: + dependencies: + tr46: 5.0.0 + webidl-conversions: 7.0.0 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -4780,6 +5060,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + ws@8.18.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yallist@3.1.1: {} yaml@2.5.1: {} diff --git a/src/__tests__/unit/pathString.test.tsx b/src/__tests__/unit/pathString.test.tsx new file mode 100644 index 00000000..378698f6 --- /dev/null +++ b/src/__tests__/unit/pathString.test.tsx @@ -0,0 +1,52 @@ +import { describe, it, expect } from "vitest"; +import { PathString } from "../../utils/pathString"; + +describe("测试路径函数", () => { + it("absolute2file", () => { + expect( + PathString.absolute2file("C:\\Users\\Administrator\\Desktop\\test.txt"), + ).toBe("test"); + + expect( + PathString.absolute2file("C:\\Users\\Administrator\\Desktop\\test.json"), + ).toBe("test"); + + expect( + PathString.absolute2file( + "C:\\Users\\Administrator\\Desktop\\test.testing.json", + ), + ).toBe("test.testing"); + + expect( + PathString.absolute2file( + "C:\\Users\\Administrator\\Desktop\\a.b.c.d.json", + ), + ).toBe("a.b.c.d"); + }); + + it("测试获取文件所在文件夹的路径", () => { + expect( + PathString.dirPath("C:\\Users\\Administrator\\Desktop\\test.json"), + ).toBe("C:\\Users\\Administrator\\Desktop"); + + expect( + PathString.dirPath("C:\\Users\\Administrator\\Desktop\\test.txt"), + ).toBe("C:\\Users\\Administrator\\Desktop"); + + expect( + PathString.dirPath("C:\\Users\\Administrator\\Desktop\\file"), + ).toBe("C:\\Users\\Administrator\\Desktop"); + + expect( + PathString.dirPath("D:\\test.json"), + ).toBe("D:"); + + expect( + PathString.dirPath("user/test.json"), + ).toBe("user"); + + // expect( + // PathString.dirPath("/user/test.json"), + // ).toBe("/user"); + }); +}); diff --git a/src/core/render/canvas2d/renderer.tsx b/src/core/render/canvas2d/renderer.tsx index 9e8374c7..efe9f1d9 100644 --- a/src/core/render/canvas2d/renderer.tsx +++ b/src/core/render/canvas2d/renderer.tsx @@ -527,7 +527,7 @@ export namespace Renderer { `fps: ${(1 / deltaTime).toFixed()}`, `delta: ${deltaTime.toFixed(2)}`, `Controller.isViewMoveByClickMiddle: ${Controller.isViewMoveByClickMiddle}`, - `path: ${Stage.Path.getPath()}` + `path: ${Stage.Path.getFilePath()}` ]; for (const [k, v] of Object.entries(timings)) { detailsData.push(`time:${k}: ${v.toFixed(2)}`); diff --git a/src/core/stage/Stage.tsx b/src/core/stage/Stage.tsx index c9cd5144..73068112 100644 --- a/src/core/stage/Stage.tsx +++ b/src/core/stage/Stage.tsx @@ -30,7 +30,7 @@ export namespace Stage { export namespace Path { let currentPath = "Project Graph"; export const draftName = "Project Graph"; - + /** * 是否是草稿 * @returns @@ -52,7 +52,7 @@ export namespace Stage { * 提供一个函数供外部调用,获取当前路径 * @returns */ - export function getPath() { + export function getFilePath() { return currentPath; } } diff --git a/src/core/stage/StageSaveManager.tsx b/src/core/stage/StageSaveManager.tsx index 591851e6..1c3d3732 100644 --- a/src/core/stage/StageSaveManager.tsx +++ b/src/core/stage/StageSaveManager.tsx @@ -56,7 +56,7 @@ export namespace StageSaveManager { return; } invoke("save_json_by_path", { - path: Stage.Path.getPath(), + path: Stage.Path.getFilePath(), content: JSON.stringify(data, null, 2), }) .then((res) => { diff --git a/src/utils/pathString.tsx b/src/utils/pathString.tsx index 641c0677..f5b1f1cf 100644 --- a/src/utils/pathString.tsx +++ b/src/utils/pathString.tsx @@ -1,8 +1,15 @@ import { family } from "@tauri-apps/plugin-os"; export namespace PathString { + /** + * 将绝对路径转换为文件名 + * @param path + * @returns + */ export function absolute2file(path: string): string { const fam = family(); + // const fam = "windows"; // vitest 测试时打开此行注释 + if (fam === "windows") { path = path.replace(/\\/g, "/"); } @@ -17,4 +24,35 @@ export namespace PathString { return file; } } + + /** + * 根据文件的绝对路径,获取当前文件所在目录的路径 + * @param path 必须是一个文件的路径,不能是文件夹的路径 + * @returns + */ + export function dirPath(path: string): string { + const fam = family(); + // const fam = "windows"; // vitest 测试时打开此行注释 + + if (fam === "windows") { + path = path.replace(/\\/g, "/"); // 将反斜杠替换为正斜杠 + } + + const file = path.split("/").pop(); // 获取文件名 + if (!file) { + throw new Error("Invalid path"); + } + + let directory = path.substring(0, path.length - file.length); // 获取目录路径 + if (directory.endsWith("/")) { + directory = directory.slice(0, -1); // 如果目录路径以斜杠结尾,去掉最后的斜杠 + } + + if (fam === "windows") { + // 再换回反斜杠 + return directory.replace(/\//g, "\\"); + } + + return directory; // 返回目录路径 + } } diff --git a/vitest.config.ts b/vitest.config.ts index 5d6aac04..6c06d3d0 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { // 配置测试环境 - environment: 'node', // 环境, node 或 jsdom + environment: 'jsdom', // 环境, node 或 jsdom // 全局设置 globals: true, include: ["src/__tests__/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"], From c2e74deb3d40d4de7adc3478fe4c104008c84585 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 16:35:19 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=E2=9C=A8=20=E5=AE=8C=E6=88=90=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E6=8F=92=E5=85=A5=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/lib.rs | 2 +- .../controller/concrete/ControllerCopy.tsx | 39 ++++---- .../entityRenderer/EntityRenderer.tsx | 18 +++- src/core/stage/Stage.tsx | 17 +++- src/core/stageObject/entity/ImageNode.tsx | 92 ++++++++++++------- 5 files changed, 112 insertions(+), 56 deletions(-) diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 20a83ee2..52453d9a 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -68,7 +68,7 @@ fn convert_image_to_base64(image_path: String) -> Result { let base64_str = encode(&image_data); Ok(base64_str) } - Err(e) => Err(format!("无法读取文件: {}", e)), + Err(e) => Err(format!("无法读取文件: {}, {}", e, image_path)), } } diff --git a/src/core/controller/concrete/ControllerCopy.tsx b/src/core/controller/concrete/ControllerCopy.tsx index bcbaac56..f6c43a5c 100644 --- a/src/core/controller/concrete/ControllerCopy.tsx +++ b/src/core/controller/concrete/ControllerCopy.tsx @@ -12,6 +12,7 @@ import { v4 as uuidv4 } from "uuid"; import { Entity } from "../../stageObject/StageObject"; import { ImageNode } from "../../stageObject/entity/ImageNode"; import { invoke } from "@tauri-apps/api/core"; +import { PathString } from "../../../utils/pathString"; /** * 关于复制相关的功能 @@ -132,34 +133,38 @@ ControllerCopy.keydown = (event: KeyboardEvent) => { // } async function readClipboardItems(mouseLocation: Vector) { - // test try { navigator.clipboard.read().then(async (items) => { for (const item of items) { - if ( - item.types.includes("image/png") - ) { + if (item.types.includes("image/png")) { const blob = await item.getType(item.types[0]); // 获取 Blob 对象 const base64String = await convertBlobToBase64(blob); // 转换为 Base64 字符串 const imageUUID = uuidv4(); - const imagePath = `images/${imageUUID}.png`; + const folder = PathString.dirPath(Stage.Path.getFilePath()); + const imagePath = `${folder}${Stage.Path.getSep()}${imageUUID}.png`; invoke("save_base64_to_image", { base64Str: base64String, - fileName: imagePath - }).then(() => { - console.log("save image to file success"); - const imageNode = new ImageNode({ - uuid: imageUUID, - location: [mouseLocation.x, mouseLocation.y], - path: `${imageUUID}.png` - }) - imageNode.setBase64StringForced(base64String); - StageManager.addImageNode(imageNode); - }).catch(error => { - console.error("save image to file error", error); + fileName: imagePath, }) + .then(() => { + console.log("save image to file success"); + + // 要延迟一下,等待保存完毕 + setTimeout(() => { + const imageNode = new ImageNode({ + uuid: imageUUID, + location: [mouseLocation.x, mouseLocation.y], + path: `${imageUUID}.png`, + }); + // imageNode.setBase64StringForced(base64String); + StageManager.addImageNode(imageNode); + }, 100); + }) + .catch((error) => { + console.error("save image to file error", error); + }); } if (item.types.includes("text/plain")) { const blob = await item.getType("text/plain"); // 获取文本内容 diff --git a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx index 3fc0ee82..053d4b26 100644 --- a/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx +++ b/src/core/render/canvas2d/entityRenderer/EntityRenderer.tsx @@ -217,11 +217,25 @@ export namespace EntityRenderer { 2 * Camera.currentScale, Renderer.NODE_ROUNDED_RADIUS * Camera.currentScale, ); - if (imageNode.isLoaded) { + if (imageNode.state === "loading") { + RenderUtils.renderTextFromCenter( + "loading...", + Renderer.transformWorld2View(imageNode.rectangle.center), + 20 * Camera.currentScale, + Color.White, + ); + } else if (imageNode.state === "success") { ImageRenderer.renderImageElement( imageNode.imageElement, Renderer.transformWorld2View(imageNode.rectangle.location), - ) + ); + } else if (imageNode.state === "error") { + RenderUtils.renderTextFromCenter( + "Error", + Renderer.transformWorld2View(imageNode.rectangle.center), + 20 * Camera.currentScale, + Color.Red, + ); } } } diff --git a/src/core/stage/Stage.tsx b/src/core/stage/Stage.tsx index 73068112..88fbb812 100644 --- a/src/core/stage/Stage.tsx +++ b/src/core/stage/Stage.tsx @@ -12,6 +12,7 @@ import { Controller } from "../controller/Controller"; import { StageManager } from "./stageManager/StageManager"; import { PointDashEffect } from "../effect/concrete/PointDashEffect"; import { ControllerGamepad } from "../controller/ControllerGamepad"; +import { family } from "@tauri-apps/plugin-os"; /** * 舞台对象 @@ -23,7 +24,6 @@ import { ControllerGamepad } from "../controller/ControllerGamepad"; * 但这个里面主要存一些动态的属性,以及特效交互等信息 */ export namespace Stage { - /** * 此Path存在的意义为摆脱状态管理只能在组件函数中的限制 */ @@ -31,9 +31,18 @@ export namespace Stage { let currentPath = "Project Graph"; export const draftName = "Project Graph"; + export function getSep(): string { + const fam = family(); + if (fam === "windows") { + return "\\"; + } else { + return "/"; + } + } + /** * 是否是草稿 - * @returns + * @returns */ export function isDraft() { return currentPath === "Project Graph"; @@ -42,7 +51,7 @@ export namespace Stage { /** * 此函数唯一的调用:只能在app.tsx的useEffect检测函数中调用 * 为了同步状态管理中的路径。 - * @param path + * @param path */ export function setPathInEffect(path: string) { currentPath = path; @@ -50,7 +59,7 @@ export namespace Stage { /** * 提供一个函数供外部调用,获取当前路径 - * @returns + * @returns */ export function getFilePath() { return currentPath; diff --git a/src/core/stageObject/entity/ImageNode.tsx b/src/core/stageObject/entity/ImageNode.tsx index e23e4ba5..af564534 100644 --- a/src/core/stageObject/entity/ImageNode.tsx +++ b/src/core/stageObject/entity/ImageNode.tsx @@ -4,7 +4,23 @@ import { Rectangle } from "../../dataStruct/shape/Rectangle"; import { Vector } from "../../dataStruct/Vector"; import { CollisionBox } from "../collisionBox/collisionBox"; import { ConnectableEntity } from "../StageObject"; +import { Stage } from "../../stage/Stage"; +import { PathString } from "../../../utils/pathString"; +/** + * 一个图片节点 + * 图片的路径字符串决定了这个图片是什么 + * + * 有两个转换过程: + * + * 图片路径 -> base64字符串 -> 图片Element -> 完成 + * gettingBase64 + * | + * v + * fileNotfound + * base64EncodeError + * + */ export class ImageNode extends ConnectableEntity { isHiddenBySectionCollapse: boolean = false; public uuid: string; @@ -38,37 +54,20 @@ export class ImageNode extends ConnectableEntity { return this._base64String; } else { // 需要通过path获取base64String + return ""; } } - - private _imageElement: HTMLImageElement | null = null; - - public get imageElement(): HTMLImageElement { - if (this._imageElement!== null) { - return this._imageElement; - } else { - // 需要通过base64String创建imageElement - const imageElement = new Image(); - imageElement.src = `data:image/png;base64,${this.base64String}`; - imageElement.onload = () => { - // 调整碰撞箱大小 - this.rectangle.size = new Vector(imageElement.width, imageElement.height); - } - return imageElement; - } - } /** - * 用于粘贴板强制输入 - * @param base64String + * 图片的三种状态 */ - public setBase64StringForced(base64String: string) { - this._base64String = base64String; - } + public state: "loading" | "success" | "error" = "loading"; + + private _imageElement: HTMLImageElement = new Image(); - public get isLoaded(): boolean { - return this._base64String !== ""; + public get imageElement(): HTMLImageElement { + return this._imageElement; } constructor( @@ -85,22 +84,51 @@ export class ImageNode extends ConnectableEntity { this.collisionBox = new CollisionBox([ new Rectangle(new Vector(...location), new Vector(...[100, 100])), ]); + this.state = "loading"; + // 初始化创建的时候,开始获取base64String + this.updateBase64StringByPath(PathString.dirPath(Stage.Path.getFilePath())); } /** - * - * @param path 工程文件所在路径文件夹,不加尾部斜杠 - * @returns + * + * @param folderPath 工程文件所在路径文件夹,不加尾部斜杠 + * @returns */ - public updateBase64StringByPath(path: string) { + public updateBase64StringByPath(folderPath: string) { if (this.path === "") { return; } + invoke("convert_image_to_base64", { - imagePath: `${path}\\${this.path}` - }).then((res) => { - this._base64String = res; - }); + imagePath: `${folderPath}\\${this.path}`, + }) + .then((res) => { + // 获取base64String成功 + + this._base64String = res; + const imageElement = new Image(); + this._imageElement = imageElement; + imageElement.src = `data:image/png;base64,${this.base64String}`; + imageElement.onload = () => { + // 图片加载成功 + console.log("图片加载成功"); + // 调整碰撞箱大小 + this.rectangle.size = new Vector( + imageElement.width, + imageElement.height, + ); + this.state = "success"; + }; + imageElement.onerror = () => { + console.log("图片加载失败"); + this.state = "error"; + }; + }) + .catch((err) => { + // 获取base64String失败 + console.log("图片可能不存在?", err); + this.state = "error"; + }); } /** From c97bad40d4f2da8fb54bad55602b4c5637ad5ddb Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 16:36:45 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=F0=9F=8E=A8=20=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E4=B8=80=E5=A4=84=E6=97=A0=E7=94=A8=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/stageObject/entity/ImageNode.tsx | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/core/stageObject/entity/ImageNode.tsx b/src/core/stageObject/entity/ImageNode.tsx index af564534..5c308396 100644 --- a/src/core/stageObject/entity/ImageNode.tsx +++ b/src/core/stageObject/entity/ImageNode.tsx @@ -49,15 +49,6 @@ export class ImageNode extends ConnectableEntity { private _base64String: string = ""; - public get base64String(): string { - if (this._base64String !== "") { - return this._base64String; - } else { - // 需要通过path获取base64String - - return ""; - } - } /** * 图片的三种状态 @@ -108,7 +99,7 @@ export class ImageNode extends ConnectableEntity { this._base64String = res; const imageElement = new Image(); this._imageElement = imageElement; - imageElement.src = `data:image/png;base64,${this.base64String}`; + imageElement.src = `data:image/png;base64,${this._base64String}`; imageElement.onload = () => { // 图片加载成功 console.log("图片加载成功"); From b82b763653937f2b280616dc506f6fd6cf6b035a Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 16:58:51 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=E2=9C=A8=20=E5=9B=BE=E7=89=87=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=8F=AF=E4=BB=A5=E4=BF=9D=E5=AD=98=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/RecentFileManager.tsx | 7 ++++--- src/core/stage/StageDumper.tsx | 18 ++++++++++++++++++ src/core/stage/StageLoader.tsx | 1 + src/core/stageObject/entity/ImageNode.tsx | 19 ++++++++++++++++--- src/types/node.tsx | 3 ++- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/core/RecentFileManager.tsx b/src/core/RecentFileManager.tsx index 33af2e3d..7dd6b7f0 100644 --- a/src/core/RecentFileManager.tsx +++ b/src/core/RecentFileManager.tsx @@ -12,6 +12,7 @@ import { Serialized } from "../types/node"; import { StageHistoryManager } from "./stage/stageManager/StageHistoryManager"; import { Section } from "./stageObject/entity/Section"; import { ConnectPoint } from "./stageObject/entity/ConnectPoint"; +import { ImageNode } from "./stageObject/entity/ImageNode"; /** * 管理最近打开的文件列表 @@ -90,7 +91,7 @@ export namespace RecentFileManager { export async function validAndRefreshRecentFiles() { const recentFiles = await getRecentFiles(); const recentFilesValid: RecentFile[] = []; - + // 是否存在文件丢失情况 let isFileLost = false; @@ -106,8 +107,6 @@ export namespace RecentFileManager { console.log("文件不存在,删除记录", file.path); isFileLost = true; } - - } catch (e) { console.error("无法检测文件是否存在:", file.path); console.error(e); @@ -177,6 +176,8 @@ export namespace RecentFileManager { StageManager.addSection(new Section(entity)); } else if (entity.type === "core:connect_point") { StageManager.addConnectPoint(new ConnectPoint(entity)); + } else if (entity.type === "core:image_node") { + StageManager.addImageNode(new ImageNode(entity)); } else { console.warn("加载文件时,出现未知的实体类型:" + entity); } diff --git a/src/core/stage/StageDumper.tsx b/src/core/stage/StageDumper.tsx index 72015d28..6dd3167f 100644 --- a/src/core/stage/StageDumper.tsx +++ b/src/core/stage/StageDumper.tsx @@ -1,6 +1,7 @@ import { Serialized } from "../../types/node"; import { Edge } from "../stageObject/association/Edge"; import { ConnectPoint } from "../stageObject/entity/ConnectPoint"; +import { ImageNode } from "../stageObject/entity/ImageNode"; import { Section } from "../stageObject/entity/Section"; import { TextNode } from "../stageObject/entity/TextNode"; import { Entity } from "../stageObject/StageObject"; @@ -46,6 +47,19 @@ export namespace StageDumper { }; } + export function dumpImageNode(imageNode: ImageNode): Serialized.ImageNode { + return { + location: [ + imageNode.rectangle.location.x, + imageNode.rectangle.location.y, + ], + size: [imageNode.rectangle.size.x, imageNode.rectangle.size.y], + path: imageNode.path, + uuid: imageNode.uuid, + type: "core:image_node", + }; + } + export function dumpSection(section: Section): Serialized.Section { return { location: [section.rectangle.location.x, section.rectangle.location.y], @@ -69,6 +83,7 @@ export namespace StageDumper { | Serialized.Section | Serialized.Node | Serialized.ConnectPoint + | Serialized.ImageNode )[] = StageManager.getTextNodes().map((node) => dumpTextNode(node)); nodes.push( @@ -79,6 +94,9 @@ export namespace StageDumper { dumpConnectPoint(connectPoint), ), ); + nodes.push( + ...StageManager.getImageNodes().map((node) => dumpImageNode(node)), + ); return { version: latestVersion, diff --git a/src/core/stage/StageLoader.tsx b/src/core/stage/StageLoader.tsx index d1fcaedb..355a57e6 100644 --- a/src/core/stage/StageLoader.tsx +++ b/src/core/stage/StageLoader.tsx @@ -165,4 +165,5 @@ export namespace StageLoader { data.version = 9; return data; } + // 增加了ImageNode } diff --git a/src/core/stageObject/entity/ImageNode.tsx b/src/core/stageObject/entity/ImageNode.tsx index 5c308396..80b40f51 100644 --- a/src/core/stageObject/entity/ImageNode.tsx +++ b/src/core/stageObject/entity/ImageNode.tsx @@ -49,7 +49,6 @@ export class ImageNode extends ConnectableEntity { private _base64String: string = ""; - /** * 图片的三种状态 */ @@ -65,6 +64,7 @@ export class ImageNode extends ConnectableEntity { { uuid, location = [0, 0], + size = [100, 100], path = "", }: Partial & { uuid: string }, public unknown = false, @@ -73,11 +73,24 @@ export class ImageNode extends ConnectableEntity { this.uuid = uuid; this.path = path; this.collisionBox = new CollisionBox([ - new Rectangle(new Vector(...location), new Vector(...[100, 100])), + new Rectangle(new Vector(...location), new Vector(...size)), ]); this.state = "loading"; // 初始化创建的时候,开始获取base64String - this.updateBase64StringByPath(PathString.dirPath(Stage.Path.getFilePath())); + if (!Stage.Path.isDraft()) { + this.updateBase64StringByPath( + PathString.dirPath(Stage.Path.getFilePath()), + ); + } else { + // 一般只有在粘贴板粘贴时和初次打开文件时才调用这里 + // 所以这里只可能时初次打开文件时还是草稿的状态 + + setTimeout(() => { + this.updateBase64StringByPath( + PathString.dirPath(Stage.Path.getFilePath()), + ); + }, 1000); + } } /** diff --git a/src/types/node.tsx b/src/types/node.tsx index 62ef3b90..7cc4d6c2 100644 --- a/src/types/node.tsx +++ b/src/types/node.tsx @@ -36,6 +36,7 @@ export namespace Serialized { } export type ImageNode = Entity & { path: string; + size: Vector; type: "core:image_node"; } export type Edge = StageObject & { @@ -47,7 +48,7 @@ export namespace Serialized { export type File = { version: 9; // 最新版本 src\core\stage\StageDumper.tsx latestVersion - nodes: (Node | Section | ConnectPoint)[]; + nodes: (Node | Section | ConnectPoint | ImageNode)[]; edges: Edge[]; }; } From 5ec8c7204226533abb123b8f62126b2f1553cfea Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 17:31:08 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=E2=9C=A8=20=E5=9B=BE=E7=89=87=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E7=A7=BB=E5=8A=A8=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../concrete/ControllerNodeMove.tsx | 45 ++++--------------- src/core/stage/stageManager/StageManager.tsx | 12 +++++ .../StageEntityMoveManager.tsx | 8 +++- 3 files changed, 28 insertions(+), 37 deletions(-) diff --git a/src/core/controller/concrete/ControllerNodeMove.tsx b/src/core/controller/concrete/ControllerNodeMove.tsx index 65ebf97c..7544e5b9 100644 --- a/src/core/controller/concrete/ControllerNodeMove.tsx +++ b/src/core/controller/concrete/ControllerNodeMove.tsx @@ -20,46 +20,17 @@ ControllerNodeMove.mousedown = (event: MouseEvent) => { const pressWorldLocation = Renderer.transformView2World( new Vector(event.clientX, event.clientY), ); - const isHaveNodeSelected = StageManager.getTextNodes().some( - (node) => node.isSelected, - ); - const isHaveSectionSelected = StageManager.getSections().some( - (section) => section.isSelected, - ); ControllerNodeMove.lastMoveLocation = pressWorldLocation.clone(); - const clickedNode = StageManager.findTextNodeByLocation(pressWorldLocation); - const clickedSection = StageManager.findSectionByLocation(pressWorldLocation); - const clickedConnectPoint = - StageManager.findConnectPointByLocation(pressWorldLocation); - - if (clickedSection !== null) { - Controller.isMovingEntity = true; - if (isHaveSectionSelected && !clickedSection.isSelected) { - StageManager.getSections().forEach((section) => { - section.isSelected = false; - }); - } - clickedSection.isSelected = true; - } - - if (clickedConnectPoint !== null) { - Controller.isMovingEntity = true; - if (clickedConnectPoint && !clickedConnectPoint.isSelected) { - StageManager.getConnectPoints().forEach((point) => { - point.isSelected = false; - }); - } - clickedConnectPoint.isSelected = true; - } - - if (clickedNode !== null) { + const clickedEntity = + StageManager.findConnectableEntityByLocation(pressWorldLocation); + if (clickedEntity !== null) { Controller.isMovingEntity = true; - if (isHaveNodeSelected && !clickedNode.isSelected) { - StageManager.getTextNodes().forEach((node) => { - node.isSelected = false; + if (clickedEntity && !clickedEntity.isSelected) { + StageManager.getEntities().forEach((entity) => { + entity.isSelected = false; }); + clickedEntity.isSelected = true; } - clickedNode.isSelected = true; // 同时清空所有边的选中状态 StageManager.getAssociations().forEach((edge) => { edge.isSelected = false; @@ -77,6 +48,7 @@ ControllerNodeMove.mousemove = (event: MouseEvent) => { const worldLocation = Renderer.transformView2World( new Vector(event.clientX, event.clientY), ); + console.log(worldLocation.toString()); const diffLocation = worldLocation.subtract( ControllerNodeMove.lastMoveLocation, ); @@ -93,6 +65,7 @@ ControllerNodeMove.mousemove = (event: MouseEvent) => { } StageManager.moveSections(diffLocation); StageManager.moveConnectPoints(diffLocation); + StageManager.moveImageNodes(diffLocation); ControllerNodeMove.lastMoveLocation = worldLocation.clone(); } diff --git a/src/core/stage/stageManager/StageManager.tsx b/src/core/stage/stageManager/StageManager.tsx index 6b701855..ac6db7f2 100644 --- a/src/core/stage/stageManager/StageManager.tsx +++ b/src/core/stage/stageManager/StageManager.tsx @@ -323,6 +323,15 @@ export namespace StageManager { return null; } + export function findImageNodeByLocation(location: Vector): ImageNode | null { + for (const node of getImageNodes()) { + if (node.collisionBox.isPointInCollisionBox(location)) { + return node; + } + } + return null; + } + export function findConnectableEntityByLocation( location: Vector, ): ConnectableEntity | null { @@ -424,6 +433,9 @@ export namespace StageManager { export function moveConnectPoints(delta: Vector) { StageEntityMoveManager.moveConnectPoints(delta); // 连续过程,不记录历史,只在结束时记录 } + export function moveImageNodes(delta: Vector) { + StageEntityMoveManager.moveImageNodes(delta); // 连续过程,不记录历史,只在结束时记录 + } export function moveNodesWithChildren(delta: Vector) { StageEntityMoveManager.moveNodesWithChildren(delta); // 连续过程,不记录历史,只在结束时记录 } diff --git a/src/core/stage/stageManager/concreteMethods/StageEntityMoveManager.tsx b/src/core/stage/stageManager/concreteMethods/StageEntityMoveManager.tsx index be5e7128..08a6e3eb 100644 --- a/src/core/stage/stageManager/concreteMethods/StageEntityMoveManager.tsx +++ b/src/core/stage/stageManager/concreteMethods/StageEntityMoveManager.tsx @@ -60,13 +60,19 @@ export namespace StageEntityMoveManager { } } export function moveConnectPoints(delta: Vector) { - console.log("moveConnectPoints"); for (const point of StageManager.getConnectPoints()) { if (point.isSelected) { moveEntityUtils(point, delta); } } } + export function moveImageNodes(delta: Vector) { + for (const node of StageManager.getImageNodes()) { + if (node.isSelected) { + moveEntityUtils(node, delta); + } + } + } export function moveNodesWithChildren(delta: Vector) { for (const node of StageManager.getTextNodes()) { From 0592f8f2b1c1da47096563e97eec5cbefe83ab76 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 17:49:30 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=E2=9C=A8=20=E5=A2=9E=E5=8A=A0=E7=BC=A9?= =?UTF-8?q?=E6=94=BE=E4=B8=BA1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/stage/Camera.tsx | 9 +++++++-- src/locales/en.yml | 1 + src/locales/zh-CN.yml | 1 + src/locales/zh-TW.yml | 1 + src/pages/_app_menu.tsx | 7 +++++++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/core/stage/Camera.tsx b/src/core/stage/Camera.tsx index 27f08d4c..9b6100f4 100644 --- a/src/core/stage/Camera.tsx +++ b/src/core/stage/Camera.tsx @@ -145,9 +145,9 @@ export namespace Camera { Settings.watch("moveAmplitude", (value) => { moveAmplitude = value; }); - Settings.watch("moveFriction", value => { + Settings.watch("moveFriction", (value) => { frictionCoefficient = value; - }) + }); } /** @@ -182,4 +182,9 @@ export namespace Camera { ); Camera.targetScale = Camera.currentScale; } + + export function resetScale() { + Camera.currentScale = 1; + Camera.targetScale = 1; + } } diff --git a/src/locales/en.yml b/src/locales/en.yml index e6d43c13..35faa479 100644 --- a/src/locales/en.yml +++ b/src/locales/en.yml @@ -140,6 +140,7 @@ appMenu: items: resetByAll: Reset View by All Content resetBySelect: Reset View by Selected Content + resetScale: Reset the scale to the standard size more: title: More items: diff --git a/src/locales/zh-CN.yml b/src/locales/zh-CN.yml index 7e17bf31..05141a93 100644 --- a/src/locales/zh-CN.yml +++ b/src/locales/zh-CN.yml @@ -118,6 +118,7 @@ appMenu: items: resetByAll: 根据全部内容重置视野 resetBySelect: 根据选中内容重置视野 + resetScale: 将缩放重置为标准大小 more: title: 更多 items: diff --git a/src/locales/zh-TW.yml b/src/locales/zh-TW.yml index 902df868..9e5c09e1 100644 --- a/src/locales/zh-TW.yml +++ b/src/locales/zh-TW.yml @@ -117,6 +117,7 @@ appMenu: items: resetByAll: 根據全部內容重置視野 resetBySelect: 根據選中內容重置視野 + resetScale: 將縮放重置為標準大小 more: title: 更多 items: diff --git a/src/pages/_app_menu.tsx b/src/pages/_app_menu.tsx index c764a84c..a965e2f6 100644 --- a/src/pages/_app_menu.tsx +++ b/src/pages/_app_menu.tsx @@ -22,6 +22,7 @@ import { Folder, FolderCog, FolderOpen, + Scaling, } from "lucide-react"; import { useNavigate } from "react-router-dom"; import { @@ -356,6 +357,12 @@ export default function AppMenu({ > {t("view.items.resetBySelect")} + } + onClick={() => Camera.resetScale()} + > + {t("view.items.resetScale")} + } title={t("more.title")}> Date: Sat, 16 Nov 2024 18:02:14 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=92=84=20=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E5=9B=BE=E7=89=87=E6=B8=B2=E6=9F=93=E8=BF=87=E5=A4=A7=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/render/canvas2d/ImageRenderer.tsx | 4 ++-- src/core/stageObject/entity/ImageNode.tsx | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/core/render/canvas2d/ImageRenderer.tsx b/src/core/render/canvas2d/ImageRenderer.tsx index aa32d819..1e06628c 100644 --- a/src/core/render/canvas2d/ImageRenderer.tsx +++ b/src/core/render/canvas2d/ImageRenderer.tsx @@ -59,8 +59,8 @@ export namespace ImageRenderer { imageElement, location.x, location.y, - imageElement.width * Camera.currentScale, - imageElement.height * Camera.currentScale, + (imageElement.width / (window.devicePixelRatio || 1)) * Camera.currentScale, + (imageElement.height / (window.devicePixelRatio || 1)) * Camera.currentScale, ); } } diff --git a/src/core/stageObject/entity/ImageNode.tsx b/src/core/stageObject/entity/ImageNode.tsx index 80b40f51..1ab7eb7d 100644 --- a/src/core/stageObject/entity/ImageNode.tsx +++ b/src/core/stageObject/entity/ImageNode.tsx @@ -116,10 +116,12 @@ export class ImageNode extends ConnectableEntity { imageElement.onload = () => { // 图片加载成功 console.log("图片加载成功"); + // 调整碰撞箱大小 + this.rectangle.size = new Vector( - imageElement.width, - imageElement.height, + imageElement.width / (window.devicePixelRatio || 1), + imageElement.height / (window.devicePixelRatio || 1), ); this.state = "success"; }; From ff3bd68993ae9b29c97f7034ab03fd1a84d6f758 Mon Sep 17 00:00:00 2001 From: littlefean <2028140990@qq.com> Date: Sat, 16 Nov 2024 18:42:03 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=90=9B=20=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E7=9A=84=E5=88=A0=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/core/stage/stageManager/StageManager.tsx | 3 +++ .../concreteMethods/StageDeleteManager.tsx | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/core/stage/stageManager/StageManager.tsx b/src/core/stage/stageManager/StageManager.tsx index ac6db7f2..d9902b62 100644 --- a/src/core/stage/stageManager/StageManager.tsx +++ b/src/core/stage/stageManager/StageManager.tsx @@ -93,6 +93,9 @@ export namespace StageManager { export function deleteOneTextNode(node: TextNode) { entities.deleteValue(node); } + export function deleteOneImage(node: ImageNode) { + entities.deleteValue(node); + } export function deleteOneSection(section: Section) { entities.deleteValue(section); } diff --git a/src/core/stage/stageManager/concreteMethods/StageDeleteManager.tsx b/src/core/stage/stageManager/concreteMethods/StageDeleteManager.tsx index 77ca2e66..db5294e5 100644 --- a/src/core/stage/stageManager/concreteMethods/StageDeleteManager.tsx +++ b/src/core/stage/stageManager/concreteMethods/StageDeleteManager.tsx @@ -3,6 +3,7 @@ import { ProgressNumber } from "../../../dataStruct/ProgressNumber"; import { ExplodeAshEffect } from "../../../effect/concrete/ExplodeDashEffect"; import { Edge } from "../../../stageObject/association/Edge"; import { ConnectPoint } from "../../../stageObject/entity/ConnectPoint"; +import { ImageNode } from "../../../stageObject/entity/ImageNode"; import { Section } from "../../../stageObject/entity/Section"; import { TextNode } from "../../../stageObject/entity/TextNode"; import { Entity } from "../../../stageObject/StageObject"; @@ -21,6 +22,8 @@ export namespace StageDeleteManager { deleteSection(entity); } else if (entity instanceof ConnectPoint) { deleteConnectPoint(entity); + } else if (entity instanceof ImageNode) { + deleteImageNode(entity); } } StageManager.updateReferences(); @@ -44,7 +47,20 @@ export namespace StageDeleteManager { // 再删除自己 StageManager.deleteOneSection(entity); } - + function deleteImageNode(entity: ImageNode) { + if (StageManager.getImageNodes().includes(entity)) { + StageManager.deleteOneImage(entity); + Stage.effects.push( + new ExplodeAshEffect( + new ProgressNumber(0, 30), + entity.collisionBox.getRectangle(), + Color.White, + ), + ); + // 删除所有相关的边 + deleteEntityAfterClearEdges(entity); + } + } function deleteConnectPoint(entity: ConnectPoint) { // 先判断这个node是否在nodes里 if (StageManager.getConnectPoints().includes(entity)) {