Skip to content

Commit 18ba74b

Browse files
committed
fix: 优化直接粘贴绝对路径到舞台或拖拽文件到舞台生成文件路径的文本节点时候的长度:将过长的信息自动压入到详细信息中,且表面信息增加emoji标识。(不影响打开文件功能)
1 parent de1c010 commit 18ba74b

File tree

3 files changed

+247
-4
lines changed

3 files changed

+247
-4
lines changed

app/src/core/service/dataManageService/copyEngine/copyEngineText.tsx

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,31 @@ export class CopyEngineText {
6868
entity.move(
6969
new Vector(-entity.collisionBox.getRectangle().width / 2, -entity.collisionBox.getRectangle().height / 2),
7070
);
71+
} else if (PathString.isAbsolutePath(item)) {
72+
// 处理 Windows 11 下复制文件路径可能带有的双引号
73+
let processedItem = item.trim();
74+
if (
75+
(processedItem.startsWith('"') && processedItem.endsWith('"')) ||
76+
(processedItem.startsWith("'") && processedItem.endsWith("'"))
77+
) {
78+
if (processedItem.length >= 2) {
79+
processedItem = processedItem.slice(1, -1).trim();
80+
}
81+
}
82+
83+
// 是绝对文件路径类型
84+
const { emoji, name } = PathString.getEmojiAndNameByPath(processedItem);
85+
const collisionBox = new CollisionBox([
86+
new Rectangle(this.project.renderer.transformView2World(MouseLocation.vector()), Vector.getZero()),
87+
]);
88+
entity = new TextNode(this.project, {
89+
text: `${emoji}${name}`,
90+
details: DetailsManager.markdownToDetails(processedItem),
91+
collisionBox,
92+
});
93+
entity.move(
94+
new Vector(-entity.collisionBox.getRectangle().width / 2, -entity.collisionBox.getRectangle().height / 2),
95+
);
7196
} else {
7297
if (item === "") {
7398
toast.warning("粘贴板中没有内容,若想快速复制多个文本节点,请交替按ctrl c、ctrl v");

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

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { toast } from "sonner";
1111
import { URI } from "vscode-uri";
1212
import { onOpenFile } from "../../GlobalMenu";
1313
import { PathString } from "@/utils/pathString";
14+
import { DetailsManager } from "@/core/stage/stageObject/tools/entityDetailsManager";
1415

1516
/**
1617
* 处理文件拖拽到舞台的引擎
@@ -55,9 +56,22 @@ export namespace DragFileIntoStageEngine {
5556
*/
5657
export async function handleDropFileAbsolutePath(project: Project, pathList: string[]) {
5758
for (const filePath of pathList) {
59+
let processedItem = filePath.trim();
60+
if (
61+
(processedItem.startsWith('"') && processedItem.endsWith('"')) ||
62+
(processedItem.startsWith("'") && processedItem.endsWith("'"))
63+
) {
64+
if (processedItem.length >= 2) {
65+
processedItem = processedItem.slice(1, -1).trim();
66+
}
67+
}
68+
69+
const { emoji, name } = PathString.getEmojiAndNameByPath(processedItem);
70+
5871
const textNode = new TextNode(project, {
59-
text: filePath,
60-
collisionBox: new CollisionBox([new Rectangle(project.camera.location.clone(), new Vector(300, 150))]),
72+
text: `${emoji}${name}`,
73+
details: DetailsManager.markdownToDetails(processedItem),
74+
collisionBox: new CollisionBox([new Rectangle(project.camera.location.clone(), Vector.getZero())]),
6175
});
6276

6377
project.stageManager.add(textNode);
@@ -79,9 +93,26 @@ export namespace DragFileIntoStageEngine {
7993

8094
for (const filePath of pathList) {
8195
const relativePath = PathString.getRelativePath(currentProjectPath, filePath);
96+
97+
let processedItem = filePath.trim();
98+
if (
99+
(processedItem.startsWith('"') && processedItem.endsWith('"')) ||
100+
(processedItem.startsWith("'") && processedItem.endsWith("'"))
101+
) {
102+
if (processedItem.length >= 2) {
103+
processedItem = processedItem.slice(1, -1).trim();
104+
}
105+
}
106+
107+
const { emoji, name } = PathString.getEmojiAndNameByPath(processedItem);
108+
109+
// 如果生成的相对路径为空或没有意义,降级处理
110+
const displayText = relativePath ? `${emoji}${relativePath}` : `${emoji}${name}`;
111+
82112
const textNode = new TextNode(project, {
83-
text: relativePath,
84-
collisionBox: new CollisionBox([new Rectangle(project.camera.location.clone(), new Vector(300, 150))]),
113+
text: displayText,
114+
details: DetailsManager.markdownToDetails(processedItem),
115+
collisionBox: new CollisionBox([new Rectangle(project.camera.location.clone(), Vector.getZero())]),
85116
});
86117

87118
project.stageManager.add(textNode);

app/src/utils/pathString.tsx

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,191 @@ export namespace PathString {
347347
fileName = getShortedFileName(fileName, 20);
348348
return fileName;
349349
}
350+
351+
/**
352+
* 判断一个字符串是否是绝对文件路径格式
353+
* 兼容 Windows (例如: D:/a/b/c 或 D:\a\b\c) 和 Unix/Mac (例如: /a/b/c)
354+
*/
355+
export function isAbsolutePath(path: string): boolean {
356+
if (typeof path !== "string") return false;
357+
let p = path.trim();
358+
// 处理可能带有的双引号或单引号
359+
if ((p.startsWith('"') && p.endsWith('"')) || (p.startsWith("'") && p.endsWith("'"))) {
360+
if (p.length >= 2) {
361+
p = p.slice(1, -1).trim();
362+
}
363+
}
364+
// 排除含有换行符的情况
365+
if (p.includes("\n") || p.includes("\r")) return false;
366+
367+
// Windows 绝对路径匹配
368+
if (/^[a-zA-Z]:[\\/]/.test(p)) {
369+
// 简单验证不包含非法字符
370+
return !/[<>:"|?*]/.test(p.substring(3));
371+
}
372+
// Unix/Mac 绝对路径匹配
373+
if (p.startsWith("/")) {
374+
return !/[<>:"|?*]/.test(p);
375+
}
376+
return false;
377+
}
378+
379+
/**
380+
* 根据文件路径获取对应的 emoji 和文件名
381+
*/
382+
export function getEmojiAndNameByPath(path: string): { emoji: string; name: string } {
383+
let p = path.trim();
384+
// 处理可能带有的双引号或单引号
385+
if ((p.startsWith('"') && p.endsWith('"')) || (p.startsWith("'") && p.endsWith("'"))) {
386+
if (p.length >= 2) {
387+
p = p.slice(1, -1).trim();
388+
}
389+
}
390+
const normalized = p.replace(/\\/g, "/");
391+
const withoutTrailingSlash = normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
392+
393+
// 对于根目录情况(如 "D:/" 或 "/")
394+
const name = withoutTrailingSlash.split("/").pop() || withoutTrailingSlash;
395+
396+
// 如果原路径以斜杠结尾,或者是根目录,直接认为是文件夹
397+
if (p.endsWith("/") || p.endsWith("\\") || name === p || /^[a-zA-Z]:$/.test(name)) {
398+
return { emoji: "📁", name };
399+
}
400+
401+
const parts = name.split(".");
402+
// 判断是否有后缀名 (不以.开头且有多个部分,或者以.开头且部分大于2)
403+
const hasExtension = parts.length > 1 && (!name.startsWith(".") || parts.length > 2);
404+
405+
if (hasExtension) {
406+
const ext = parts[parts.length - 1].toLowerCase();
407+
let emoji = "📃"; // 默认文件
408+
switch (ext) {
409+
// 办公
410+
case "doc":
411+
case "docx":
412+
case "pages":
413+
emoji = "📄";
414+
break;
415+
case "xls":
416+
case "xlsx":
417+
case "csv":
418+
case "numbers":
419+
emoji = "📊";
420+
break;
421+
case "ppt":
422+
case "pptx":
423+
case "key":
424+
emoji = "📈";
425+
break;
426+
case "pdf":
427+
emoji = "📕";
428+
break;
429+
case "txt":
430+
case "md":
431+
case "rtf":
432+
emoji = "📝";
433+
break;
434+
// 图片
435+
case "jpg":
436+
case "jpeg":
437+
case "png":
438+
case "gif":
439+
case "webp":
440+
case "bmp":
441+
case "tiff":
442+
case "svg":
443+
case "ico":
444+
emoji = "🖼️";
445+
break;
446+
// 视频
447+
case "mp4":
448+
case "mov":
449+
case "avi":
450+
case "mkv":
451+
case "wmv":
452+
case "flv":
453+
case "webm":
454+
emoji = "🎬";
455+
break;
456+
// 音频
457+
case "mp3":
458+
case "wav":
459+
case "ogg":
460+
case "flac":
461+
case "aac":
462+
case "m4a":
463+
emoji = "🎵";
464+
break;
465+
// 设计
466+
case "psd":
467+
case "ai":
468+
case "xd":
469+
case "fig":
470+
case "sketch":
471+
case "blend":
472+
case "c4d":
473+
case "prproj":
474+
case "aep":
475+
emoji = "🎨";
476+
break;
477+
// 开发
478+
case "js":
479+
case "ts":
480+
case "jsx":
481+
case "tsx":
482+
case "json":
483+
case "html":
484+
case "css":
485+
case "scss":
486+
case "less":
487+
case "vue":
488+
case "py":
489+
case "java":
490+
case "c":
491+
case "cpp":
492+
case "cs":
493+
case "go":
494+
case "rs":
495+
case "php":
496+
case "rb":
497+
case "swift":
498+
case "sql":
499+
case "sh":
500+
case "bat":
501+
case "xml":
502+
case "yaml":
503+
case "yml":
504+
case "ini":
505+
case "toml":
506+
case "env":
507+
case "dockerfile":
508+
emoji = "💻";
509+
break;
510+
// 压缩包
511+
case "zip":
512+
case "rar":
513+
case "7z":
514+
case "tar":
515+
case "gz":
516+
emoji = "📦";
517+
break;
518+
// 可执行文件
519+
case "exe":
520+
case "app":
521+
case "dmg":
522+
case "apk":
523+
case "msi":
524+
case "deb":
525+
case "rpm":
526+
emoji = "🚀";
527+
break;
528+
default:
529+
emoji = "📃";
530+
}
531+
return { emoji, name };
532+
} else {
533+
// 没有后缀名,认为是文件夹
534+
return { emoji: "📁", name };
535+
}
536+
}
350537
}

0 commit comments

Comments
 (0)