diff --git a/packages/origine2/src/api/Api.ts b/packages/origine2/src/api/Api.ts index 3138d4e58..451b59b53 100644 --- a/packages/origine2/src/api/Api.ts +++ b/packages/origine2/src/api/Api.ts @@ -55,6 +55,11 @@ export interface EditTextFileDto { textFile: string; } +export interface CopyFileWithIncrementDto { + /** The source path of the file to be copied */ + source: string; +} + export interface TemplateConfigDto { /** The name of the template */ name: string; @@ -552,6 +557,26 @@ export class Api< ...params, }), + /** + * No description + * + * @tags Assets + * @name AssetsControllerCopyFileWithIncrement + * @summary Copy File With Increment + * @request POST:/api/assets/copyFileWithIncrement + */ + assetsControllerCopyFileWithIncrement: ( + data: CopyFileWithIncrementDto, + params: RequestParams = {}, + ) => + this.request({ + path: `/api/assets/copyFileWithIncrement`, + method: "POST", + body: data, + type: ContentType.Json, + ...params, + }), + /** * No description * diff --git a/packages/origine2/src/locales/en.po b/packages/origine2/src/locales/en.po index 2136546e2..3f4862390 100644 --- a/packages/origine2/src/locales/en.po +++ b/packages/origine2/src/locales/en.po @@ -761,6 +761,10 @@ msgstr "Scenes and branches" msgid "场景文件" msgstr "Scene file" +#: src/pages/editor/MainArea/TagsManager.tsx:167 +msgid "增量保存" +msgstr "Incremental Save" + #: src/pages/templateEditor/TemplateEditorSidebar/ComponentTree/ComponentTree.tsx:41 msgid "外层文本" msgstr "Outer Text" @@ -1491,6 +1495,10 @@ msgstr "Play BGM normally" msgid "此指令将结束游戏" msgstr "This command will end the game" +#: src/pages/editor/MainArea/TagsManager.tsx:142 +msgid "此选项可将当前文件另存备份,防止因原文件意外损坏而丢失所有数据。" +msgstr "This option allows you to save the current file as a backup to prevent data loss if the original file is accidentally damaged." + #: src/pages/editor/Topbar/tabs/Settings/SettingsTab.tsx:130 msgid "永不换行" msgstr "Never wrap" diff --git a/packages/origine2/src/locales/ja.po b/packages/origine2/src/locales/ja.po index 945621d5a..8b295dac6 100644 --- a/packages/origine2/src/locales/ja.po +++ b/packages/origine2/src/locales/ja.po @@ -764,6 +764,10 @@ msgstr "シーンとブランチ" msgid "场景文件" msgstr "シーンファイル" +#: src/pages/editor/MainArea/TagsManager.tsx:167 +msgid "增量保存" +msgstr "インクリメンタルセーブ" + #: src/pages/templateEditor/TemplateEditorSidebar/ComponentTree/ComponentTree.tsx:41 msgid "外层文本" msgstr "外側テキスト" @@ -1490,6 +1494,10 @@ msgstr "BGMを再生" msgid "此指令将结束游戏" msgstr "このコマンドはすべてのゲームが終わるときに使用" +#: src/pages/editor/MainArea/TagsManager.tsx:142 +msgid "此选项可将当前文件另存备份,防止因原文件意外损坏而丢失所有数据。" +msgstr "このオプションは現在のファイルをバックアップとして別名保存し、元のファイルが予期せず破損した場合でもすべてのデータが失われるのを防ぎます。" + #: src/pages/editor/Topbar/tabs/Settings/SettingsTab.tsx:130 msgid "永不换行" msgstr "折り返しなし" diff --git a/packages/origine2/src/locales/zhCn.po b/packages/origine2/src/locales/zhCn.po index 4eeb90c15..5705b196e 100644 --- a/packages/origine2/src/locales/zhCn.po +++ b/packages/origine2/src/locales/zhCn.po @@ -763,6 +763,10 @@ msgstr "场景与分支" msgid "场景文件" msgstr "场景文件" +#: src/pages/editor/MainArea/TagsManager.tsx:167 +msgid "增量保存" +msgstr "增量保存" + #: src/pages/templateEditor/TemplateEditorSidebar/ComponentTree/ComponentTree.tsx:41 msgid "外层文本" msgstr "外层文本" @@ -1489,6 +1493,10 @@ msgstr "正常播放 BGM" msgid "此指令将结束游戏" msgstr "此指令将结束游戏" +#: src/pages/editor/MainArea/TagsManager.tsx:142 +msgid "此选项可将当前文件另存备份,防止因原文件意外损坏而丢失所有数据。" +msgstr "此选项可将当前文件另存备份,防止因原文件意外损坏而丢失所有数据。" + #: src/pages/editor/Topbar/tabs/Settings/SettingsTab.tsx:130 msgid "永不换行" msgstr "永不换行" diff --git a/packages/origine2/src/pages/editor/MainArea/TagsManager.tsx b/packages/origine2/src/pages/editor/MainArea/TagsManager.tsx index db1531fe4..8c6a7824e 100644 --- a/packages/origine2/src/pages/editor/MainArea/TagsManager.tsx +++ b/packages/origine2/src/pages/editor/MainArea/TagsManager.tsx @@ -4,10 +4,14 @@ import { cloneDeep } from "lodash"; import { CloseSmall } from "@icon-park/react"; import IconWrapper from "@/components/iconWrapper/IconWrapper"; import { getFileIcon } from "@/utils/getFileIcon"; -import React, { useRef } from "react"; +import React, { useMemo, useRef } from "react"; import { useGameEditorContext } from "@/store/useGameEditorStore"; import { ITag } from "@/types/gameEditor"; -import { Tooltip } from "@fluentui/react-components"; +import { Button, Tooltip } from "@fluentui/react-components"; +import { api } from "@/api"; +import useEditorStore from "@/store/useEditorStore"; +import { useSWRConfig } from "swr"; +import { t } from "@lingui/macro"; export default function TagsManager() { // 获取 Tags 数据 @@ -77,58 +81,94 @@ export default function TagsManager() { const containerRef = useRef(null); + const { mutate } = useSWRConfig(); + const handleRefresh = (path: string) => mutate(path); + const gameDir = useEditorStore.use.subPage(); + const basePath = useMemo(() => ['games', gameDir, 'game'], [gameDir]); + return ( <> - { - (tags.length > 0) && - - - {(provided, snapshot) => ( - // 下面开始书写容器 -
- {tags.map((item, index) => ( - - {(provided, snapshot) => ( - // 下面开始书写可拖拽的元素 - -
selectTag(item)} - onMouseDown={(event: any) => { - if (event.button === 1) { - closeTag(event, item); - } - }} - className={item.path === currentTag?.path ? `${styles.tag} ${styles.tag_active}` : styles.tag} - ref={provided.innerRef} - {...provided.draggableProps} - {...provided.dragHandleProps} - > - -
- {item.name} -
-
closeTag(event, item)}> - + { (tags.length > 0) && ( +
+ + + {(provided, snapshot) => ( + // 下面开始书写容器 +
+ {tags.map((item, index) => ( + + {(provided, snapshot) => ( + // 下面开始书写可拖拽的元素 + +
selectTag(item)} + onMouseDown={(event: any) => { + if (event.button === 1) { + closeTag(event, item); + } + }} + className={item.path === currentTag?.path ? `${styles.tag} ${styles.tag_active}` : styles.tag} + ref={provided.innerRef} + {...provided.draggableProps} + {...provided.dragHandleProps} + > + +
+ {item.name} +
+
closeTag(event, item)}> + +
-
- - )} - - ))} - {provided.placeholder} -
- )} - - - } + + )} + + ))} + {provided.placeholder} +
+ )} + + + + {t`此选项可将当前文件另存备份,防止因原文件意外损坏而丢失所有数据。`} +
} + relationship='description' + showDelay={750} + hideDelay={0} + > + +
+
+ )} - ); } diff --git a/packages/origine2/src/pages/editor/MainArea/tagsManager.module.scss b/packages/origine2/src/pages/editor/MainArea/tagsManager.module.scss index 07fd2e1aa..ede59485d 100644 --- a/packages/origine2/src/pages/editor/MainArea/tagsManager.module.scss +++ b/packages/origine2/src/pages/editor/MainArea/tagsManager.module.scss @@ -1,3 +1,9 @@ +.tagsManager { + display: flex; + width: 100%; + max-width: 100%; +} + .tagsContainer { display: flex; overflow: auto; @@ -83,3 +89,9 @@ .tag_active>.closeIcon { visibility: visible; } + +.tooltip { + font-size: 120%; + line-height: 150%; + color: var(--primary); +} diff --git a/packages/terre2/src/Modules/assets/assets.controller.ts b/packages/terre2/src/Modules/assets/assets.controller.ts index 727c2c6b2..ebb131ae0 100644 --- a/packages/terre2/src/Modules/assets/assets.controller.ts +++ b/packages/terre2/src/Modules/assets/assets.controller.ts @@ -21,6 +21,7 @@ import { RenameFileDto, UploadFilesDto, EditTextFileDto, + CopyFileWithIncrementDto, } from './assets.dto'; import { FilesInterceptor } from '@nestjs/platform-express'; import { _open } from '../../util/open'; @@ -159,4 +160,14 @@ export class AssetsController { const filePath = this.webgalFs.getPathFromRoot(`public/${path}`); return this.webgalFs.updateTextFile(filePath, editTextFileData.textFile); } + + @Post('copyFileWithIncrement') + @ApiOperation({ summary: 'Copy File With Increment' }) + @ApiResponse({ status: 200, description: 'File copied successfully.' }) + @ApiResponse({ status: 400, description: 'Failed to copy the file.' }) + async copyFileWithIncrement(@Body() copyFileDto: CopyFileWithIncrementDto) { + const { source } = copyFileDto; + const sourcePath = this.webgalFs.getPathFromRoot(`public/${source}`); + return this.webgalFs.copyFileWithIncrement(sourcePath); + } } diff --git a/packages/terre2/src/Modules/assets/assets.dto.ts b/packages/terre2/src/Modules/assets/assets.dto.ts index 187ed2f87..a7a201799 100644 --- a/packages/terre2/src/Modules/assets/assets.dto.ts +++ b/packages/terre2/src/Modules/assets/assets.dto.ts @@ -42,6 +42,11 @@ export class RenameFileDto { newName: string; } +export class CopyFileWithIncrementDto { + @ApiProperty({ description: 'The source path of the file to be copied' }) + source: string; +} + export class EditTextFileDto { @ApiProperty({ description: 'The path of textfile' }) path: string; diff --git a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts index 5a243b396..6ce971abc 100644 --- a/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts +++ b/packages/terre2/src/Modules/webgal-fs/webgal-fs.service.ts @@ -1,6 +1,6 @@ import { ConsoleLogger, Injectable } from '@nestjs/common'; import * as fs from 'fs/promises'; -import { dirname, extname, join } from 'path'; +import { basename, dirname, extname, join } from 'path'; export interface IFileInfo { name: string; @@ -335,4 +335,32 @@ export class WebgalFsService { return false; } } + + /** + * 复制文件并以“原文件名_编号.扩展名”方式增量保存 + */ + async copyFileWithIncrement(filePath: string): Promise { + const dir = dirname(filePath); + const ext = extname(filePath); + const base = basename(filePath, ext); + + // 读取目录下所有文件 + const files = await fs.readdir(dir); + // 匹配类似 xxx_序号.txt 的文件 + const regex = new RegExp(`^${base}_(\\d+)${ext.replace('.', '\\.')}$`); + let maxNum = 0; + for (const file of files) { + const match = file.match(regex); + if (match) { + const num = parseInt(match[1], 10); + if (num > maxNum) maxNum = num; + } + } + const nextNum = (maxNum + 1).toString().padStart(3, '0'); + const newName = `${base}_${nextNum}${ext}`; + const newPath = join(dir, newName); + + await fs.copyFile(filePath, newPath); + return newPath; + } }