From 198ba44ceb2d954ecfb21418f670263688a588dc Mon Sep 17 00:00:00 2001 From: landaiqing Date: Wed, 30 Apr 2025 01:52:30 +0800 Subject: [PATCH] :art: Refactor save service --- .../voidraft/internal/models/models.ts | 70 ++- .../internal/services/configservice.ts | 68 ++- .../voidraft/internal/services/fileservice.ts | 73 --- .../voidraft/internal/services/index.ts | 4 +- frontend/package-lock.json | 305 +++++------ frontend/package.json | 4 - frontend/src/components/toolbar/Toolbar.vue | 89 +++- frontend/src/i18n/index.ts | 2 - frontend/src/main.ts | 10 - frontend/src/stores/configStore.ts | 178 ++++--- frontend/src/stores/editorStore.ts | 3 - frontend/vite.config.ts | 3 +- internal/models/config.go | 38 +- internal/services/config_service.go | 484 +++++------------- internal/services/file_service.go | 195 ------- internal/services/service_manager.go | 13 +- internal/services/store_service.go | 232 +++++++++ 17 files changed, 846 insertions(+), 925 deletions(-) delete mode 100644 frontend/bindings/voidraft/internal/services/fileservice.ts delete mode 100644 internal/services/file_service.go create mode 100644 internal/services/store_service.go diff --git a/frontend/bindings/voidraft/internal/models/models.ts b/frontend/bindings/voidraft/internal/models/models.ts index 59591a4..e4eafd4 100644 --- a/frontend/bindings/voidraft/internal/models/models.ts +++ b/frontend/bindings/voidraft/internal/models/models.ts @@ -28,11 +28,6 @@ export class AppConfig { */ "metadata": ConfigMetadata; - /** - * 界面语言 - */ - "language": LanguageType; - /** Creates a new AppConfig instance. */ constructor($$source: Partial = {}) { if (!("editor" in $$source)) { @@ -44,9 +39,6 @@ export class AppConfig { if (!("metadata" in $$source)) { this["metadata"] = (new ConfigMetadata()); } - if (!("language" in $$source)) { - this["language"] = ("" as LanguageType); - } Object.assign(this, $$source); } @@ -119,7 +111,7 @@ export class EditorConfig { /** * 文件保存的编码 */ - "encoding": string; + "encoding": EncodingType; /** * 是否启用Tab缩进 @@ -136,13 +128,18 @@ export class EditorConfig { */ "tabType": TabType; + /** + * 界面语言 + */ + "language": LanguageType; + /** Creates a new EditorConfig instance. */ constructor($$source: Partial = {}) { if (!("fontSize" in $$source)) { this["fontSize"] = 0; } if (!("encoding" in $$source)) { - this["encoding"] = ""; + this["encoding"] = ("" as EncodingType); } if (!("enableTabIndent" in $$source)) { this["enableTabIndent"] = false; @@ -153,6 +150,9 @@ export class EditorConfig { if (!("tabType" in $$source)) { this["tabType"] = ("" as TabType); } + if (!("language" in $$source)) { + this["language"] = ("" as LanguageType); + } Object.assign(this, $$source); } @@ -166,6 +166,56 @@ export class EditorConfig { } } +/** + * EncodingType 定义文件编码格式类型 + */ +export enum EncodingType { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * EncodingUTF8 UTF-8编码 + */ + EncodingUTF8 = "UTF-8", + + /** + * EncodingUTF8BOM UTF-8带BOM编码 + */ + EncodingUTF8BOM = "UTF-8-BOM", + + /** + * EncodingUTF16LE UTF-16小端编码 + */ + EncodingUTF16LE = "UTF-16 LE", + + /** + * EncodingUTF16BE UTF-16大端编码 + */ + EncodingUTF16BE = "UTF-16 BE", + + /** + * EncodingISO88591 ISO-8859-1编码 + */ + EncodingISO88591 = "ISO-8859-1", + + /** + * EncodingGB18030 GB18030编码 + */ + EncodingGB18030 = "GB18030", + + /** + * EncodingGBK GBK编码 + */ + EncodingGBK = "GBK", + + /** + * EncodingBig5 Big5编码 + */ + EncodingBig5 = "Big5", +}; + /** * LanguageType 语言类型定义 */ diff --git a/frontend/bindings/voidraft/internal/services/configservice.ts b/frontend/bindings/voidraft/internal/services/configservice.ts index 2cdb08a..e3358c8 100644 --- a/frontend/bindings/voidraft/internal/services/configservice.ts +++ b/frontend/bindings/voidraft/internal/services/configservice.ts @@ -15,10 +15,10 @@ import {Call as $Call, Create as $Create} from "@wailsio/runtime"; import * as models$0 from "../models/models.js"; /** - * GetAppConfig 获取应用配置 + * GetConfig 获取完整应用配置 */ -export function GetAppConfig(): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(3361428829) as any; +export function GetConfig(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1013336538) as any; let $typingPromise = $resultPromise.then(($result: any) => { return $$createType1($result); }) as any; @@ -38,14 +38,6 @@ export function GetEditorConfig(): Promise & { cancel(): return $typingPromise; } -/** - * GetFullConfigPath 获取完整的配置文件路径 - */ -export function GetFullConfigPath(): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(38527092) as any; - return $resultPromise; -} - /** * GetLanguage 获取当前语言设置 */ @@ -55,18 +47,42 @@ export function GetLanguage(): Promise & { cancel(): void } /** - * ResetToDefault 重置为默认配置 + * GetMetadata 获取配置元数据 */ -export function ResetToDefault(): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(4057687351) as any; +export function GetMetadata(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3276720617) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType3($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * GetPathConfig 获取路径配置 + */ +export function GetPathConfig(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(2053285689) as any; + let $typingPromise = $resultPromise.then(($result: any) => { + return $$createType4($result); + }) as any; + $typingPromise.cancel = $resultPromise.cancel.bind($resultPromise); + return $typingPromise; +} + +/** + * ResetConfig 重置为默认配置 + */ +export function ResetConfig(): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3593047389) as any; return $resultPromise; } /** - * SaveAppConfig 保存应用配置 + * SaveConfig 保存完整应用配置 */ -export function SaveAppConfig(config: models$0.AppConfig | null): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(2077587650, config) as any; +export function SaveConfig(config: models$0.AppConfig | null): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(616684383, config) as any; return $resultPromise; } @@ -86,7 +102,25 @@ export function UpdateEditorConfig(editorConfig: models$0.EditorConfig): Promise return $resultPromise; } +/** + * UpdateMetadata 更新配置元数据 + */ +export function UpdateMetadata(metadata: models$0.ConfigMetadata): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(3353893284, metadata) as any; + return $resultPromise; +} + +/** + * UpdatePathConfig 更新路径配置 + */ +export function UpdatePathConfig(pathConfig: models$0.PathConfig): Promise & { cancel(): void } { + let $resultPromise = $Call.ByID(1492772004, pathConfig) as any; + return $resultPromise; +} + // Private type creation functions const $$createType0 = models$0.AppConfig.createFrom; const $$createType1 = $Create.Nullable($$createType0); const $$createType2 = models$0.EditorConfig.createFrom; +const $$createType3 = models$0.ConfigMetadata.createFrom; +const $$createType4 = models$0.PathConfig.createFrom; diff --git a/frontend/bindings/voidraft/internal/services/fileservice.ts b/frontend/bindings/voidraft/internal/services/fileservice.ts deleted file mode 100644 index df39b65..0000000 --- a/frontend/bindings/voidraft/internal/services/fileservice.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -/** - * FileService 提供文件操作 - * @module - */ - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: Unused imports -import {Call as $Call, Create as $Create} from "@wailsio/runtime"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: Unused imports -import * as time$0 from "../../../time/models.js"; - -/** - * DeleteFile 删除文件 - */ -export function DeleteFile(filePath: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(1771867857, filePath) as any; - return $resultPromise; -} - -/** - * EnsureDir 确保目录存在,如不存在则创建 - */ -export function EnsureDir(dirPath: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(2291976369, dirPath) as any; - return $resultPromise; -} - -/** - * FileExists 检查文件是否存在 - */ -export function FileExists(filePath: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(4264173930, filePath) as any; - return $resultPromise; -} - -/** - * GetFileModTime 获取文件的修改时间 - */ -export function GetFileModTime(filePath: string): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(2240854203, filePath) as any; - return $resultPromise; -} - -/** - * LoadJSON 从文件加载JSON数据 - */ -export function LoadJSON(filePath: string, target: any): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(1385779418, filePath, target) as any; - return $resultPromise; -} - -/** - * SaveJSON 保存JSON数据到文件 - */ -export function SaveJSON(filePath: string, data: any): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(3646933935, filePath, data) as any; - return $resultPromise; -} - -/** - * SaveJSONWithCheck 保存JSON数据到文件,带并发检查 - * expectedModTime是期望的文件修改时间,如果为零值则不检查 - * onConflict是冲突处理函数,如果文件已被修改且此函数不为nil,则调用此函数合并数据 - */ -export function SaveJSONWithCheck(filePath: string, data: any, expectedModTime: time$0.Time, onConflict: any): Promise & { cancel(): void } { - let $resultPromise = $Call.ByID(4074237977, filePath, data, expectedModTime, onConflict) as any; - return $resultPromise; -} diff --git a/frontend/bindings/voidraft/internal/services/index.ts b/frontend/bindings/voidraft/internal/services/index.ts index 85e1b09..46882e0 100644 --- a/frontend/bindings/voidraft/internal/services/index.ts +++ b/frontend/bindings/voidraft/internal/services/index.ts @@ -2,8 +2,6 @@ // This file is automatically generated. DO NOT EDIT import * as ConfigService from "./configservice.js"; -import * as FileService from "./fileservice.js"; export { - ConfigService, - FileService + ConfigService }; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9aaed30..fa6db70 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -38,13 +38,10 @@ "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.36.6", "@lezer/highlight": "^1.2.1", - "@primeuix/themes": "^1.0.3", "@types/uuid": "^10.0.0", "@vueuse/core": "^13.1.0", "codemirror": "^6.0.1", "pinia": "^3.0.2", - "pinia-plugin-persistedstate": "^4.2.0", - "primevue": "^4.3.3", "sass": "^1.87.0", "uuid": "^11.1.0", "vue": "^3.5.13", @@ -53,7 +50,6 @@ }, "devDependencies": { "@eslint/js": "^9.25.1", - "@primevue/auto-import-resolver": "^4.3.3", "@types/node": "^22.15.2", "@vitejs/plugin-vue": "^5.2.3", "@wailsio/runtime": "latest", @@ -1376,6 +1372,7 @@ "version": "2.1.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -1389,6 +1386,7 @@ "version": "2.0.5", "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -1398,6 +1396,7 @@ "version": "1.2.8", "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -1411,7 +1410,10 @@ "version": "3.16.2", "resolved": "https://registry.npmmirror.com/@nuxt/kit/-/kit-3.16.2.tgz", "integrity": "sha512-K1SAUo2vweTfudKZzjKsZ5YJoxPLTspR5qz5+G61xtZreLpsdpDYfBseqsIAl5VFLJuszeRpWQ01jP9LfQ6Ksw==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "c12": "^3.0.2", "consola": "^3.4.2", @@ -1444,7 +1446,10 @@ "version": "7.0.3", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.3.tgz", "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 4" } @@ -1745,97 +1750,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/@primeuix/styled": { - "version": "0.5.1", - "resolved": "https://registry.npmmirror.com/@primeuix/styled/-/styled-0.5.1.tgz", - "integrity": "sha512-5Ftw/KSauDPClQ8F2qCyCUF7cIUEY4yLNikf0rKV7Vsb8zGYNK0dahQe7CChaR6M2Kn+NA2DSBSk76ZXqj6Uog==", - "license": "MIT", - "dependencies": { - "@primeuix/utils": "^0.5.3" - }, - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/@primeuix/styles": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@primeuix/styles/-/styles-1.0.3.tgz", - "integrity": "sha512-yHj/Q+fosJ1736Ty5lRbpqhKa9piou+xZPPppNHUDshq0+XhrFwDGggvPGmDAJyUIM+ChM/Nj8lPY/AwTNXAkg==", - "license": "MIT", - "dependencies": { - "@primeuix/styled": "^0.5.1" - } - }, - "node_modules/@primeuix/themes": { - "version": "1.0.3", - "resolved": "https://registry.npmmirror.com/@primeuix/themes/-/themes-1.0.3.tgz", - "integrity": "sha512-f/1qadrv5TFMHfvtVv4Y9zjrkeDP2BO/cuzbHBO9DYxKL6YBIPT9BjKec2K4Kg8PcfGm6CAvxAvICadJSWejRw==", - "license": "MIT", - "dependencies": { - "@primeuix/styled": "^0.5.1" - } - }, - "node_modules/@primeuix/utils": { - "version": "0.5.3", - "resolved": "https://registry.npmmirror.com/@primeuix/utils/-/utils-0.5.3.tgz", - "integrity": "sha512-7SGh7734wcF1/uK6RzO6Z6CBjGQ97GDHfpyl2F1G/c7R0z9hkT/V72ypDo82AWcCS7Ta07oIjDpOCTkSVZuEGQ==", - "license": "MIT", - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/@primevue/auto-import-resolver": { - "version": "4.3.3", - "resolved": "https://registry.npmmirror.com/@primevue/auto-import-resolver/-/auto-import-resolver-4.3.3.tgz", - "integrity": "sha512-CwQPlG8IzDySOwF8N0Q0rLv76awCH7SzYt+RHu1AO/HZEsdCorclIoJO6TrJYevenlhxialprRqpIV52McoRpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@primevue/metadata": "4.3.3" - }, - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/@primevue/core": { - "version": "4.3.3", - "resolved": "https://registry.npmmirror.com/@primevue/core/-/core-4.3.3.tgz", - "integrity": "sha512-kSkN5oourG7eueoFPIqiNX3oDT/f0I5IRK3uOY/ytz+VzTZp5yuaCN0Nt42ZQpVXjDxMxDvUhIdaXVrjr58NhQ==", - "license": "MIT", - "dependencies": { - "@primeuix/styled": "^0.5.0", - "@primeuix/utils": "^0.5.1" - }, - "engines": { - "node": ">=12.11.0" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@primevue/icons": { - "version": "4.3.3", - "resolved": "https://registry.npmmirror.com/@primevue/icons/-/icons-4.3.3.tgz", - "integrity": "sha512-ouQaxHyeFB6MSfEGGbjaK5Qv9efS1xZGetZoU5jcPm090MSYLFtroP1CuK3lZZAQals06TZ6T6qcoNukSHpK5w==", - "license": "MIT", - "dependencies": { - "@primeuix/utils": "^0.5.1", - "@primevue/core": "4.3.3" - }, - "engines": { - "node": ">=12.11.0" - } - }, - "node_modules/@primevue/metadata": { - "version": "4.3.3", - "resolved": "https://registry.npmmirror.com/@primevue/metadata/-/metadata-4.3.3.tgz", - "integrity": "sha512-R1IBTGsYsmOlAy4/dytJW699Iie9B8p8PqbIxDY0GWiMGk8O0tVBYfuCs50w0QtHqmThhLRtSS+eCSOP1ybwSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.11.0" - } - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.40.0", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", @@ -2120,7 +2034,10 @@ "version": "2.3.0", "resolved": "https://registry.npmmirror.com/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=18" }, @@ -2132,6 +2049,7 @@ "version": "1.0.7", "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz", "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -2656,6 +2574,7 @@ "version": "8.14.1", "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -2786,6 +2705,7 @@ "version": "3.0.3", "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "devOptional": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -2798,7 +2718,10 @@ "version": "3.0.3", "resolved": "https://registry.npmmirror.com/c12/-/c12-3.0.3.tgz", "integrity": "sha512-uC3MacKBb0Z15o5QWCHvHWj5Zv34pGQj9P+iXKSpTuSGFS0KKhUWf4t9AJ+gWjYOdmWCPEGpEzm8sS0iqbpo1w==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", @@ -2868,7 +2791,10 @@ "version": "0.1.6", "resolved": "https://registry.npmmirror.com/citty/-/citty-0.1.6.tgz", "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "consola": "^3.2.3" } @@ -2919,13 +2845,17 @@ "version": "0.2.2", "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.2.tgz", "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "dev": true, "license": "MIT" }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmmirror.com/consola/-/consola-3.4.2.tgz", "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": "^14.18.0 || >=16.10.0" } @@ -3017,23 +2947,23 @@ "dev": true, "license": "MIT" }, - "node_modules/deep-pick-omit": { - "version": "1.2.1", - "resolved": "https://registry.npmmirror.com/deep-pick-omit/-/deep-pick-omit-1.2.1.tgz", - "integrity": "sha512-2J6Kc/m3irCeqVG42T+SaUMesaK7oGWaedGnQQK/+O0gYc+2SP5bKh/KKTE7d7SJ+GCA9UUE1GRzh6oDe0EnGw==", - "license": "MIT" - }, "node_modules/defu": { "version": "6.1.4", "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz", "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmmirror.com/destr/-/destr-2.0.5.tgz", "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/detect-libc": { "version": "1.0.3", @@ -3052,7 +2982,10 @@ "version": "16.5.0", "resolved": "https://registry.npmmirror.com/dotenv/-/dotenv-16.5.0.tgz", "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "dev": true, "license": "BSD-2-Clause", + "optional": true, + "peer": true, "engines": { "node": ">=12" }, @@ -3076,7 +3009,10 @@ "version": "0.1.0", "resolved": "https://registry.npmmirror.com/errx/-/errx-0.1.0.tgz", "integrity": "sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/esbuild": { "version": "0.25.2", @@ -3319,6 +3255,7 @@ "version": "1.0.5", "resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.0.5.tgz", "integrity": "sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==", + "dev": true, "license": "MIT" }, "node_modules/fast-deep-equal": { @@ -3332,6 +3269,7 @@ "version": "3.3.3", "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -3348,6 +3286,7 @@ "version": "5.1.2", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -3374,6 +3313,7 @@ "version": "1.19.1", "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -3396,6 +3336,7 @@ "version": "7.1.1", "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "devOptional": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -3461,7 +3402,10 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/giget/-/giget-2.0.0.tgz", "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", @@ -3504,7 +3448,10 @@ "version": "14.1.0", "resolved": "https://registry.npmmirror.com/globby/-/globby-14.1.0.tgz", "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", "fast-glob": "^3.3.3", @@ -3524,7 +3471,10 @@ "version": "7.0.3", "resolved": "https://registry.npmmirror.com/ignore/-/ignore-7.0.3.tgz", "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 4" } @@ -3622,6 +3572,7 @@ "version": "2.1.1", "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3631,6 +3582,7 @@ "version": "4.0.3", "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -3643,6 +3595,7 @@ "version": "7.0.0", "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -3671,7 +3624,10 @@ "version": "2.4.2", "resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "bin": { "jiti": "lib/jiti-cli.mjs" } @@ -3680,7 +3636,10 @@ "version": "9.0.1", "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-9.0.1.tgz", "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -3730,7 +3689,10 @@ "version": "2.0.6", "resolved": "https://registry.npmmirror.com/klona/-/klona-2.0.6.tgz", "integrity": "sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">= 8" } @@ -3739,7 +3701,10 @@ "version": "1.2.0", "resolved": "https://registry.npmmirror.com/knitwork/-/knitwork-1.2.0.tgz", "integrity": "sha512-xYSH7AvuQ6nXkq42x0v5S8/Iry+cfulBz/DJQzhIyESdLD7425jXsPy4vn5cCXU+HhRN2kVw51Vd1K6/By4BQg==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/levn": { "version": "0.4.1", @@ -3759,6 +3724,7 @@ "version": "1.1.1", "resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.1.tgz", "integrity": "sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==", + "dev": true, "license": "MIT", "dependencies": { "mlly": "^1.7.4", @@ -3815,6 +3781,7 @@ "version": "1.4.1", "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -3824,6 +3791,7 @@ "version": "4.0.8", "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "devOptional": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -3856,6 +3824,7 @@ "version": "1.7.4", "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.7.4.tgz", "integrity": "sha512-qmdSIPC4bDJXgZTCR7XosJiNKySV7O215tsPtDN9iEO/7q/76b/ijtgRu/+epFXSJhijtTCCGp3DWS549P3xKw==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.14.0", @@ -3868,12 +3837,14 @@ "version": "0.1.8", "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, "license": "MIT" }, "node_modules/mlly/node_modules/pkg-types": { "version": "1.3.1", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.1.8", @@ -3931,7 +3902,10 @@ "version": "1.6.6", "resolved": "https://registry.npmmirror.com/node-fetch-native/-/node-fetch-native-1.6.6.tgz", "integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/normalize-path": { "version": "3.0.0", @@ -3960,7 +3934,10 @@ "version": "0.6.0", "resolved": "https://registry.npmmirror.com/nypm/-/nypm-0.6.0.tgz", "integrity": "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", @@ -3979,7 +3956,10 @@ "version": "2.0.11", "resolved": "https://registry.npmmirror.com/ohash/-/ohash-2.0.11.tgz", "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/optionator": { "version": "0.9.4", @@ -4075,7 +4055,10 @@ "version": "6.0.0", "resolved": "https://registry.npmmirror.com/path-type/-/path-type-6.0.0.tgz", "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=18" }, @@ -4087,6 +4070,7 @@ "version": "2.0.3", "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/perfect-debounce": { @@ -4105,6 +4089,7 @@ "version": "2.3.1", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -4134,34 +4119,11 @@ } } }, - "node_modules/pinia-plugin-persistedstate": { - "version": "4.2.0", - "resolved": "https://registry.npmmirror.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-4.2.0.tgz", - "integrity": "sha512-3buhA7ac+ssbOIx3VRCC8oHkoFwhDM9oHRCjo7nj+O8WUqnW+jRqh7eYT5eS/DNa3H28zp3dYf/nd/Vc8zj8eQ==", - "license": "MIT", - "dependencies": { - "@nuxt/kit": "^3.14.1592", - "deep-pick-omit": "^1.2.1", - "defu": "^6.1.4", - "destr": "^2.0.3" - }, - "peerDependencies": { - "@pinia/nuxt": ">=0.9.0", - "pinia": ">=2.3.0" - }, - "peerDependenciesMeta": { - "@pinia/nuxt": { - "optional": true - }, - "pinia": { - "optional": true - } - } - }, "node_modules/pkg-types": { "version": "2.1.0", "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.1.0.tgz", "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "dev": true, "license": "MIT", "dependencies": { "confbox": "^0.2.1", @@ -4221,22 +4183,6 @@ "node": ">= 0.8.0" } }, - "node_modules/primevue": { - "version": "4.3.3", - "resolved": "https://registry.npmmirror.com/primevue/-/primevue-4.3.3.tgz", - "integrity": "sha512-nooYVoEz5CdP3EhUkD6c3qTdRmpLHZh75fBynkUkl46K8y5rksHTjdSISiDijwTA5STQIOkyqLb+RM+HQ6nC1Q==", - "license": "MIT", - "dependencies": { - "@primeuix/styled": "^0.5.0", - "@primeuix/styles": "^1.0.0", - "@primeuix/utils": "^0.5.1", - "@primevue/core": "4.3.3", - "@primevue/icons": "4.3.3" - }, - "engines": { - "node": ">=12.11.0" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", @@ -4251,6 +4197,7 @@ "version": "0.2.10", "resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.10.tgz", "integrity": "sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==", + "dev": true, "funding": [ { "type": "individual", @@ -4267,6 +4214,7 @@ "version": "1.2.3", "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -4287,7 +4235,10 @@ "version": "2.1.2", "resolved": "https://registry.npmmirror.com/rc9/-/rc9-2.1.2.tgz", "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" @@ -4320,6 +4271,7 @@ "version": "1.1.0", "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -4376,6 +4328,7 @@ "version": "1.2.0", "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -4419,12 +4372,16 @@ "version": "1.3.0", "resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz", "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/semver": { "version": "7.7.1", "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4460,7 +4417,10 @@ "version": "5.1.0", "resolved": "https://registry.npmmirror.com/slash/-/slash-5.1.0.tgz", "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=14.16" }, @@ -4490,7 +4450,10 @@ "version": "3.9.0", "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.9.0.tgz", "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/strip-json-comments": { "version": "3.1.1", @@ -4509,7 +4472,10 @@ "version": "3.0.0", "resolved": "https://registry.npmmirror.com/strip-literal/-/strip-literal-3.0.0.tgz", "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "js-tokens": "^9.0.1" }, @@ -4552,12 +4518,16 @@ "version": "0.3.2", "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "license": "MIT" + "dev": true, + "license": "MIT", + "optional": true, + "peer": true }, "node_modules/tinyglobby": { "version": "0.2.13", "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.13.tgz", "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.4.4", @@ -4574,6 +4544,7 @@ "version": "6.4.4", "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.4.4.tgz", "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, "license": "MIT", "peerDependencies": { "picomatch": "^3 || ^4" @@ -4588,6 +4559,7 @@ "version": "4.0.2", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4600,6 +4572,7 @@ "version": "5.0.1", "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "devOptional": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -4675,13 +4648,17 @@ "version": "1.6.1", "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.1.tgz", "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "dev": true, "license": "MIT" }, "node_modules/unctx": { "version": "2.4.1", "resolved": "https://registry.npmmirror.com/unctx/-/unctx-2.4.1.tgz", "integrity": "sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "acorn": "^8.14.0", "estree-walker": "^3.0.3", @@ -4693,7 +4670,10 @@ "version": "3.0.3", "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -4709,7 +4689,10 @@ "version": "0.3.0", "resolved": "https://registry.npmmirror.com/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=18" }, @@ -4721,7 +4704,10 @@ "version": "4.2.0", "resolved": "https://registry.npmmirror.com/unimport/-/unimport-4.2.0.tgz", "integrity": "sha512-mYVtA0nmzrysnYnyb3ALMbByJ+Maosee2+WyE0puXl+Xm2bUwPorPaaeZt0ETfuroPOtG8jj1g/qeFZ6buFnag==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "acorn": "^8.14.1", "escape-string-regexp": "^5.0.0", @@ -4746,7 +4732,10 @@ "version": "5.0.0", "resolved": "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" }, @@ -4758,7 +4747,10 @@ "version": "3.0.3", "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.0" } @@ -4767,7 +4759,10 @@ "version": "4.0.2", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=12" }, @@ -4779,6 +4774,7 @@ "version": "2.3.2", "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.2.tgz", "integrity": "sha512-3n7YA46rROb3zSj8fFxtxC/PqoyvYQ0llwz9wtUPUutr9ig09C8gGo5CWCwHrUzlqC1LLR43kxp5vEIyH1ac1w==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.14.1", @@ -4793,6 +4789,7 @@ "version": "0.2.4", "resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz", "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==", + "dev": true, "license": "MIT", "dependencies": { "pathe": "^2.0.2", @@ -4809,6 +4806,7 @@ "version": "4.0.2", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4908,6 +4906,7 @@ "version": "4.0.2", "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz", "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -4920,7 +4919,10 @@ "version": "2.0.0", "resolved": "https://registry.npmmirror.com/untyped/-/untyped-2.0.0.tgz", "integrity": "sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==", + "dev": true, "license": "MIT", + "optional": true, + "peer": true, "dependencies": { "citty": "^0.1.6", "defu": "^6.1.4", @@ -5192,6 +5194,7 @@ "version": "0.6.2", "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, "license": "MIT" }, "node_modules/which": { diff --git a/frontend/package.json b/frontend/package.json index a039e01..8b6e93b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,13 +42,10 @@ "@codemirror/state": "^6.5.2", "@codemirror/view": "^6.36.6", "@lezer/highlight": "^1.2.1", - "@primeuix/themes": "^1.0.3", "@types/uuid": "^10.0.0", "@vueuse/core": "^13.1.0", "codemirror": "^6.0.1", "pinia": "^3.0.2", - "pinia-plugin-persistedstate": "^4.2.0", - "primevue": "^4.3.3", "sass": "^1.87.0", "uuid": "^11.1.0", "vue": "^3.5.13", @@ -57,7 +54,6 @@ }, "devDependencies": { "@eslint/js": "^9.25.1", - "@primevue/auto-import-resolver": "^4.3.3", "@types/node": "^22.15.2", "@vitejs/plugin-vue": "^5.2.3", "@wailsio/runtime": "latest", diff --git a/frontend/src/components/toolbar/Toolbar.vue b/frontend/src/components/toolbar/Toolbar.vue index a08d970..c9d4153 100644 --- a/frontend/src/components/toolbar/Toolbar.vue +++ b/frontend/src/components/toolbar/Toolbar.vue @@ -5,6 +5,7 @@ import {useLogStore} from '@/stores/logStore'; import { useI18n } from 'vue-i18n'; import { ref } from 'vue'; import {SUPPORTED_LOCALES, setLocale, SupportedLocaleType} from '@/i18n'; +import { EncodingType } from '@/../bindings/voidraft/internal/models/models'; const editorStore = useEditorStore(); const configStore = useConfigStore(); @@ -13,6 +14,20 @@ const { t, locale } = useI18n(); // 语言下拉菜单 const showLanguageMenu = ref(false); +// 编码下拉菜单 +const showEncodingMenu = ref(false); + +// 支持的编码格式 +const SUPPORTED_ENCODINGS = [ + { code: EncodingType.EncodingUTF8, name: 'UTF-8' }, + { code: EncodingType.EncodingUTF8BOM, name: 'UTF-8 with BOM' }, + { code: EncodingType.EncodingUTF16LE, name: 'UTF-16 LE' }, + { code: EncodingType.EncodingUTF16BE, name: 'UTF-16 BE' }, + { code: EncodingType.EncodingISO88591, name: 'ISO-8859-1' }, + { code: EncodingType.EncodingGB18030, name: 'GB18030' }, + { code: EncodingType.EncodingGBK, name: 'GBK' }, + { code: EncodingType.EncodingBig5, name: 'Big5' } +]; // 切换语言 const changeLanguage = (localeCode: SupportedLocaleType) => { @@ -23,6 +38,29 @@ const changeLanguage = (localeCode: SupportedLocaleType) => { // 切换语言菜单显示 const toggleLanguageMenu = () => { showLanguageMenu.value = !showLanguageMenu.value; + if (showLanguageMenu.value) { + showEncodingMenu.value = false; + } +}; + +// 切换编码 +const changeEncoding = (encoding: EncodingType) => { + configStore.setEncoding(encoding); + showEncodingMenu.value = false; +}; + +// 切换编码菜单显示 +const toggleEncodingMenu = () => { + showEncodingMenu.value = !showEncodingMenu.value; + if (showEncodingMenu.value) { + showLanguageMenu.value = false; + } +}; + +// 获取编码名称 +const getEncodingDisplayName = (encoding: EncodingType) => { + const encodingItem = SUPPORTED_ENCODINGS.find(item => item.code === encoding); + return encodingItem ? encodingItem.name : encoding; }; @@ -61,19 +99,37 @@ const toggleLanguageMenu = () => { - {{ t('toolbar.encoding') }} + + +
+ +
+
+ {{ encoding.name }} +
+
+
-
- -
+
@@ -200,17 +256,12 @@ const toggleLanguageMenu = () => { } } } - - .encoding { - color: var(--text-muted); - font-size: 11px; - } - /* 语言切换样式 */ - .language-selector { + /* 通用下拉选择器样式 */ + .selector-dropdown { position: relative; - .language-btn { + .selector-btn { background: none; border: none; color: var(--text-muted); @@ -226,13 +277,13 @@ const toggleLanguageMenu = () => { background-color: rgba(255, 255, 255, 0.05); } - .arrow-up { + .arrow { font-size: 8px; margin-left: 2px; } } - .language-menu { + .selector-menu { position: absolute; bottom: 100%; right: 0; @@ -240,11 +291,13 @@ const toggleLanguageMenu = () => { border: 1px solid var(--border-color); border-radius: 3px; margin-bottom: 4px; - min-width: 100px; + min-width: 120px; + max-height: 200px; + overflow-y: auto; z-index: 1000; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); - .language-option { + .selector-option { padding: 4px 8px; cursor: pointer; font-size: 11px; diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts index c499cf7..9fd80f6 100644 --- a/frontend/src/i18n/index.ts +++ b/frontend/src/i18n/index.ts @@ -43,14 +43,12 @@ const i18n = createI18n({ GetLanguage().then(lang => { if (lang) { i18n.global.locale = lang as any; - document.documentElement.setAttribute('lang', lang); } }).catch(error => { console.error('Failed to get language from backend:', error); // 如果获取失败,使用浏览器语言作为后备 const browserLang = getBrowserLanguage(); i18n.global.locale = browserLang as any; - document.documentElement.setAttribute('lang', browserLang); }); // 切换语言的方法 diff --git a/frontend/src/main.ts b/frontend/src/main.ts index bf686a8..9c604a2 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,21 +1,11 @@ import {createApp} from 'vue'; import App from './App.vue'; import '@/assets/styles/index.css'; -import PrimeVue from 'primevue/config'; -import Aura from '@primeuix/themes/aura'; import {createPinia} from 'pinia'; -import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; import i18n from './i18n'; - const pinia = createPinia() -pinia.use(piniaPluginPersistedstate) const app = createApp(App); app.use(pinia) -app.use(PrimeVue, { - theme: { - preset: Aura - } -}); app.use(i18n); app.mount('#app'); diff --git a/frontend/src/stores/configStore.ts b/frontend/src/stores/configStore.ts index 1420b59..48bf61c 100644 --- a/frontend/src/stores/configStore.ts +++ b/frontend/src/stores/configStore.ts @@ -3,10 +3,10 @@ import {ref, watch} from 'vue'; import {useDebounceFn} from '@vueuse/core'; import { GetEditorConfig, - ResetToDefault, + ResetConfig, UpdateEditorConfig } from '@/../bindings/voidraft/internal/services/configservice'; -import {EditorConfig, TabType} from '@/../bindings/voidraft/internal/models/models'; +import {EditorConfig, TabType, EncodingType} from '@/../bindings/voidraft/internal/models/models'; import {useLogStore} from './logStore'; import { useI18n } from 'vue-i18n'; @@ -20,6 +20,26 @@ const DEFAULT_TAB_SIZE = 4; const MIN_TAB_SIZE = 2; const MAX_TAB_SIZE = 8; +// 支持的编码 +const SUPPORTED_ENCODINGS = [ + EncodingType.EncodingUTF8, + EncodingType.EncodingUTF8BOM, + EncodingType.EncodingUTF16LE, + EncodingType.EncodingUTF16BE, + EncodingType.EncodingISO88591, + EncodingType.EncodingGB18030, + EncodingType.EncodingGBK, + EncodingType.EncodingBig5 +]; + +// 配置项限制定义 +const CONFIG_LIMITS = { + fontSize: { min: MIN_FONT_SIZE, max: MAX_FONT_SIZE, default: DEFAULT_FONT_SIZE }, + tabSize: { min: MIN_TAB_SIZE, max: MAX_TAB_SIZE, default: DEFAULT_TAB_SIZE }, + tabType: { values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces }, + encoding: { values: SUPPORTED_ENCODINGS, default: EncodingType.EncodingUTF8 } +}; + export const useConfigStore = defineStore('config', () => { // 获取日志store const logStore = useLogStore(); @@ -28,7 +48,7 @@ export const useConfigStore = defineStore('config', () => { // 配置状态 const config = ref(new EditorConfig({ fontSize: DEFAULT_FONT_SIZE, - encoding: 'UTF-8', + encoding: EncodingType.EncodingUTF8, enableTabIndent: true, tabSize: DEFAULT_TAB_SIZE, tabType: TabType.TabTypeSpaces @@ -58,42 +78,44 @@ export const useConfigStore = defineStore('config', () => { let hasChanges = false; // 验证字体大小 - if (config.value.fontSize < MIN_FONT_SIZE || config.value.fontSize > MAX_FONT_SIZE) { + if (config.value.fontSize < CONFIG_LIMITS.fontSize.min || config.value.fontSize > CONFIG_LIMITS.fontSize.max) { const oldValue = config.value.fontSize; - config.value.fontSize = oldValue < MIN_FONT_SIZE ? MIN_FONT_SIZE : - oldValue > MAX_FONT_SIZE ? MAX_FONT_SIZE : - DEFAULT_FONT_SIZE; + config.value.fontSize = oldValue < CONFIG_LIMITS.fontSize.min ? CONFIG_LIMITS.fontSize.min : + oldValue > CONFIG_LIMITS.fontSize.max ? CONFIG_LIMITS.fontSize.max : + CONFIG_LIMITS.fontSize.default; - logStore.warning(t('config.fontSizeFixed', { - value: oldValue, - fixed: config.value.fontSize - })); + logStore.warning(t('config.fontSizeFixed')); hasChanges = true; } // 验证Tab大小 - if (config.value.tabSize < MIN_TAB_SIZE || config.value.tabSize > MAX_TAB_SIZE) { + if (config.value.tabSize < CONFIG_LIMITS.tabSize.min || config.value.tabSize > CONFIG_LIMITS.tabSize.max) { const oldValue = config.value.tabSize; - config.value.tabSize = oldValue < MIN_TAB_SIZE ? MIN_TAB_SIZE : - oldValue > MAX_TAB_SIZE ? MAX_TAB_SIZE : - DEFAULT_TAB_SIZE; + config.value.tabSize = oldValue < CONFIG_LIMITS.tabSize.min ? CONFIG_LIMITS.tabSize.min : + oldValue > CONFIG_LIMITS.tabSize.max ? CONFIG_LIMITS.tabSize.max : + CONFIG_LIMITS.tabSize.default; - logStore.warning(t('config.tabSizeFixed', { - value: oldValue, - fixed: config.value.tabSize - })); + logStore.warning(t('config.tabSizeFixed')); hasChanges = true; } // 验证TabType是否合法 - const validTabTypes = [TabType.TabTypeSpaces, TabType.TabTypeTab]; - if (!validTabTypes.includes(config.value.tabType)) { + if (!CONFIG_LIMITS.tabType.values.includes(config.value.tabType)) { const oldValue = config.value.tabType; - config.value.tabType = TabType.TabTypeSpaces; + config.value.tabType = CONFIG_LIMITS.tabType.default; - logStore.warning(t('config.tabTypeFixed', { value: oldValue })); + logStore.warning(t('config.tabTypeFixed')); + hasChanges = true; + } + + // 验证编码类型是否合法 + if (!CONFIG_LIMITS.encoding.values.includes(config.value.encoding)) { + const oldValue = config.value.encoding; + config.value.encoding = CONFIG_LIMITS.encoding.default; + + logStore.warning(t('config.encodingFixed')); hasChanges = true; } @@ -121,67 +143,69 @@ export const useConfigStore = defineStore('config', () => { } }, {deep: true}); - // 字体缩放 - function increaseFontSize() { - if (config.value.fontSize < MAX_FONT_SIZE) { - config.value.fontSize += 1; + // 更新特定配置项的类型安全方法 + function updateConfig( + key: K, + value: EditorConfig[K] | ((currentValue: EditorConfig[K]) => EditorConfig[K]) + ) { + if (typeof value === 'function') { + const currentValue = config.value[key]; + const fn = value as (val: EditorConfig[K]) => EditorConfig[K]; + config.value[key] = fn(currentValue); + } else { + config.value[key] = value; } } - // 字体缩小 - function decreaseFontSize() { - if (config.value.fontSize > MIN_FONT_SIZE) { - config.value.fontSize -= 1; - } + // 用于数字类型配置的增减方法 + function adjustFontSize(amount: number) { + let newValue = config.value.fontSize + amount; + + if (newValue < MIN_FONT_SIZE) newValue = MIN_FONT_SIZE; + if (newValue > MAX_FONT_SIZE) newValue = MAX_FONT_SIZE; + + config.value.fontSize = newValue; } - // 重置字体大小 - function resetFontSize() { - config.value.fontSize = DEFAULT_FONT_SIZE; + function adjustTabSize(amount: number) { + let newValue = config.value.tabSize + amount; + + if (newValue < MIN_TAB_SIZE) newValue = MIN_TAB_SIZE; + if (newValue > MAX_TAB_SIZE) newValue = MAX_TAB_SIZE; + + config.value.tabSize = newValue; } - // 设置编码 - function setEncoding(newEncoding: string) { - config.value.encoding = newEncoding; - } - - // Tab相关方法 - function toggleTabIndent() { - config.value.enableTabIndent = !config.value.enableTabIndent; - } - - // 增加Tab大小 - function increaseTabSize() { - if (config.value.tabSize < MAX_TAB_SIZE) { - config.value.tabSize += 1; - } - } - - // 减少Tab大小 - function decreaseTabSize() { - if (config.value.tabSize > MIN_TAB_SIZE) { - config.value.tabSize -= 1; - } - } - - // 切换Tab类型(空格或制表符) + // Tab相关类型安全的配置切换 function toggleTabType() { - config.value.tabType = config.value.tabType === TabType.TabTypeSpaces - ? TabType.TabTypeTab + config.value.tabType = config.value.tabType === TabType.TabTypeSpaces + ? TabType.TabTypeTab : TabType.TabTypeSpaces; } + + // 设置编码类型 + function setEncoding(encoding: string) { + // 验证编码是否有效的EncodingType + const encodingType = encoding as EncodingType; + if (SUPPORTED_ENCODINGS.includes(encodingType)) { + config.value.encoding = encodingType; + } else { + logStore.warning(t('config.invalidEncoding')); + } + } // 重置为默认配置 async function resetToDefaults() { try { - await ResetToDefault(); - await loadConfigFromBackend(); + await ResetConfig(); + await loadConfigFromBackend(); logStore.info(t('config.resetSuccess')); } catch (error) { console.error('Failed to reset configuration:', error); logStore.error(t('config.resetFailed')); } } + return { // 状态 config, @@ -193,18 +217,26 @@ export const useConfigStore = defineStore('config', () => { DEFAULT_FONT_SIZE, MIN_TAB_SIZE, MAX_TAB_SIZE, + SUPPORTED_ENCODINGS, - // 方法 + // 核心方法 loadConfigFromBackend, saveConfigToBackend, + updateConfig, + resetToDefaults, + + // 字体大小方法 + increaseFontSize: () => adjustFontSize(1), + decreaseFontSize: () => adjustFontSize(-1), + resetFontSize: () => updateConfig('fontSize', DEFAULT_FONT_SIZE), + + // 编码操作 setEncoding, - increaseFontSize, - decreaseFontSize, - resetFontSize, - toggleTabIndent, - increaseTabSize, - decreaseTabSize, - toggleTabType, - resetToDefaults + + // Tab操作 + toggleTabIndent: () => updateConfig('enableTabIndent', val => !val), + increaseTabSize: () => adjustTabSize(1), + decreaseTabSize: () => adjustTabSize(-1), + toggleTabType }; }); \ No newline at end of file diff --git a/frontend/src/stores/editorStore.ts b/frontend/src/stores/editorStore.ts index e1daad4..41db7eb 100644 --- a/frontend/src/stores/editorStore.ts +++ b/frontend/src/stores/editorStore.ts @@ -43,9 +43,6 @@ export const useEditorStore = defineStore('editor', () => { documentStats, editorView, - // 配置引用 - config: configStore.config, - // 方法 setEditorView, updateDocumentStats, diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 566ae37..b1f4eb2 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,7 +1,6 @@ import {defineConfig, loadEnv} from 'vite'; import vue from '@vitejs/plugin-vue'; import Components from 'unplugin-vue-components/vite'; -import {PrimeVueResolver} from '@primevue/auto-import-resolver'; import * as path from 'path'; export default defineConfig(({mode}: { mode: string }): object => { @@ -19,7 +18,7 @@ export default defineConfig(({mode}: { mode: string }): object => { Components({ dts: true, dirs: ['src/components'], - resolvers: [PrimeVueResolver()], + resolvers: [], }) ], esbuild: { diff --git a/internal/models/config.go b/internal/models/config.go index 1610e5f..5a95b7c 100644 --- a/internal/models/config.go +++ b/internal/models/config.go @@ -14,13 +14,36 @@ const ( TabTypeTab TabType = "tab" ) +// EncodingType 定义文件编码格式类型 +type EncodingType string + +const ( + // EncodingUTF8 UTF-8编码 + EncodingUTF8 EncodingType = "UTF-8" + // EncodingUTF8BOM UTF-8带BOM编码 + EncodingUTF8BOM EncodingType = "UTF-8-BOM" + // EncodingUTF16LE UTF-16小端编码 + EncodingUTF16LE EncodingType = "UTF-16 LE" + // EncodingUTF16BE UTF-16大端编码 + EncodingUTF16BE EncodingType = "UTF-16 BE" + // EncodingISO88591 ISO-8859-1编码 + EncodingISO88591 EncodingType = "ISO-8859-1" + // EncodingGB18030 GB18030编码 + EncodingGB18030 EncodingType = "GB18030" + // EncodingGBK GBK编码 + EncodingGBK EncodingType = "GBK" + // EncodingBig5 Big5编码 + EncodingBig5 EncodingType = "Big5" +) + // EditorConfig 定义编辑器配置 type EditorConfig struct { - FontSize int `json:"fontSize"` // 字体大小 - Encoding string `json:"encoding"` // 文件保存的编码 - EnableTabIndent bool `json:"enableTabIndent"` // 是否启用Tab缩进 - TabSize int `json:"tabSize"` // Tab大小 - TabType TabType `json:"tabType"` // Tab类型(空格或Tab) + FontSize int `json:"fontSize"` // 字体大小 + Encoding EncodingType `json:"encoding"` // 文件保存的编码 + EnableTabIndent bool `json:"enableTabIndent"` // 是否启用Tab缩进 + TabSize int `json:"tabSize"` // Tab大小 + TabType TabType `json:"tabType"` // Tab类型(空格或Tab) + Language LanguageType `json:"language"` // 界面语言 } // LanguageType 语言类型定义 @@ -44,7 +67,6 @@ type AppConfig struct { Editor EditorConfig `json:"editor"` // 编辑器配置 Paths PathConfig `json:"paths"` // 路径配置 Metadata ConfigMetadata `json:"metadata"` // 配置元数据 - Language LanguageType `json:"language"` // 界面语言 } // ConfigMetadata 配置元数据 @@ -58,10 +80,11 @@ func NewDefaultAppConfig() *AppConfig { return &AppConfig{ Editor: EditorConfig{ FontSize: 13, - Encoding: "UTF-8", + Encoding: EncodingUTF8, EnableTabIndent: true, TabSize: 4, TabType: TabTypeSpaces, + Language: LangZhCN, }, Paths: PathConfig{ RootDir: ".voidraft", @@ -71,6 +94,5 @@ func NewDefaultAppConfig() *AppConfig { Version: "1.0.0", LastUpdated: time.Now(), }, - Language: LangZhCN, } } diff --git a/internal/services/config_service.go b/internal/services/config_service.go index b0d8fb9..98e69b6 100644 --- a/internal/services/config_service.go +++ b/internal/services/config_service.go @@ -1,340 +1,150 @@ package services import ( - "encoding/json" "fmt" - "log" "os" "path/filepath" - "strconv" - "strings" "time" "voidraft/internal/models" + + "github.com/wailsapp/wails/v3/pkg/services/log" ) // ConfigService 提供配置管理功能 type ConfigService struct { - fileService *FileService - configPath string - rootDir string - homePath string - config *models.AppConfig - lastModTime time.Time // 上次读取配置文件的修改时间 + store *Store[models.AppConfig] + homePath string + configPath string + logger *log.LoggerService } +type Service struct{} // NewConfigService 创建新的配置服务实例 -func NewConfigService(fileService *FileService) *ConfigService { +func NewConfigService() *ConfigService { + // 初始化日志服务 + logger := log.New() + // 获取用户主目录 homePath, err := os.UserHomeDir() if err != nil { - log.Printf("Failed to get user home directory: %v", err) + logger.Error("Config: Failed to get user home directory", "error", err) homePath = "." } - log.Printf("User home directory: %s", homePath) - // 创建默认配置 defaultConfig := models.NewDefaultAppConfig() - // 构造服务实例 - service := &ConfigService{ - fileService: fileService, - rootDir: defaultConfig.Paths.RootDir, - configPath: defaultConfig.Paths.ConfigPath, - homePath: homePath, - config: defaultConfig, + // 构建完整的配置文件路径 + configFilePath := filepath.Join(homePath, defaultConfig.Paths.RootDir, defaultConfig.Paths.ConfigPath) + + // 创建Store选项 + storeOption := StoreOption{ + FilePath: configFilePath, + AutoSave: true, + Logger: logger, } - // 初始化配置目录和文件 - service.initializeConfig() + // 创建存储服务 + store := NewStore[models.AppConfig](storeOption) + + // 构造配置服务实例 + service := &ConfigService{ + store: store, + homePath: homePath, + configPath: configFilePath, + logger: logger, + } + + // 检查是否需要设置默认配置 + config := store.Get() + if isEmptyConfig(config) { + err := store.Set(*defaultConfig) + if err != nil { + logger.Error("Config: Failed to set default config", "error", err) + } + } return service } -// initializeConfig 初始化配置目录和文件 -func (cs *ConfigService) initializeConfig() { - // 确保配置目录存在 - dirPath := filepath.Join(cs.homePath, cs.rootDir) - log.Printf("Creating config directory: %s", dirPath) - - // 确保主目录存在 - if err := cs.fileService.EnsureDir(dirPath); err != nil { - log.Printf("Failed to create config directory: %v", err) - return +// isEmptyConfig 检查配置是否为空 +func isEmptyConfig(config models.AppConfig) bool { + // 检查基本字段 + if config.Editor.FontSize == 0 && config.Paths.RootDir == "" { + return true } + return false +} - // 确保配置文件所在目录存在 - configDir := filepath.Dir(cs.GetFullConfigPath()) - if configDir != dirPath { - if err := cs.fileService.EnsureDir(configDir); err != nil { - log.Printf("Failed to create config file directory: %v", err) - return - } - } +// GetConfig 获取完整应用配置 +func (cs *ConfigService) GetConfig() (*models.AppConfig, error) { + config := cs.store.Get() - // 检查配置文件是否存在 - configFilePath := cs.GetFullConfigPath() - log.Printf("Config file path: %s", configFilePath) - - if !cs.fileService.FileExists(configFilePath) { - log.Printf("Config file not found, creating default config") - // 创建默认配置文件 + // 如果配置为空,返回默认配置 + if isEmptyConfig(config) { defaultConfig := models.NewDefaultAppConfig() - if err := cs.saveAppConfig(defaultConfig); err != nil { - log.Printf("Failed to save default config: %v", err) - } - } else { - // 加载现有配置 - log.Printf("Loading existing config file") - existingConfig, modTime, err := cs.loadAppConfigWithModTime() - if err != nil { - log.Printf("Failed to load existing config, using default: %v", err) - } else { - cs.config = existingConfig - cs.lastModTime = modTime + if err := cs.store.Set(*defaultConfig); err != nil { + cs.logger.Error("Config: Failed to save default config", "error", err) } + return defaultConfig, nil } + + return &config, nil } -// GetFullConfigPath 获取完整的配置文件路径 -func (cs *ConfigService) GetFullConfigPath() string { - return filepath.Join(cs.homePath, cs.rootDir, cs.configPath) -} - -// mergeConfigs 合并两个配置,实现智能合并策略 -func (cs *ConfigService) mergeConfigs(currentConfig *models.AppConfig, existingData []byte) (*models.AppConfig, error) { - // 解析磁盘配置 - var diskConfig models.AppConfig - if err := json.Unmarshal(existingData, &diskConfig); err != nil { - return nil, fmt.Errorf("failed to parse existing config: %w", err) - } - - // 创建合并后的配置(基于磁盘配置) - mergedConfig := diskConfig - - // ===== 1. 编辑器配置合并策略 ===== - // 编辑器配置是用户最直接的操作,总是保留当前配置 - log.Printf("Merge: Preserving current editor config") - mergedConfig.Editor = currentConfig.Editor - - // ===== 2. 路径配置合并策略 ===== - // 如果当前实例已经修改了根目录(非默认值),保留当前配置 - defaultConfig := models.NewDefaultAppConfig() - - if currentConfig.Paths.RootDir != defaultConfig.Paths.RootDir && - currentConfig.Paths.RootDir != diskConfig.Paths.RootDir { - log.Printf("Merge: Using custom root directory from current config: %s", currentConfig.Paths.RootDir) - mergedConfig.Paths.RootDir = currentConfig.Paths.RootDir - } else { - log.Printf("Merge: Using root directory from disk config: %s", diskConfig.Paths.RootDir) - } - - // 对配置路径采用相同策略 - if currentConfig.Paths.ConfigPath != defaultConfig.Paths.ConfigPath && - currentConfig.Paths.ConfigPath != diskConfig.Paths.ConfigPath { - log.Printf("Merge: Using custom config path from current config: %s", currentConfig.Paths.ConfigPath) - mergedConfig.Paths.ConfigPath = currentConfig.Paths.ConfigPath - } else { - log.Printf("Merge: Using config path from disk config: %s", diskConfig.Paths.ConfigPath) - } - - // ===== 3. 元数据合并策略 ===== - // 版本号使用较高的版本 - if compareVersions(currentConfig.Metadata.Version, diskConfig.Metadata.Version) > 0 { - log.Printf("Merge: Using higher version from current config: %s", currentConfig.Metadata.Version) - mergedConfig.Metadata.Version = currentConfig.Metadata.Version - } else { - log.Printf("Merge: Using version from disk config: %s", diskConfig.Metadata.Version) - } - - // 更新时间总是使用当前时间 - mergedConfig.Metadata.LastUpdated = time.Now() - - // ===== 4. 其他配置项 ===== - - log.Printf("Merge: Completed using comprehensive merge strategy") - return &mergedConfig, nil -} - -// compareVersions 比较两个版本号 -// 返回: 1 如果v1>v2, 0 如果v1=v2, -1 如果v1 parts2[i] { - return 1 - } else if parts1[i] < parts2[i] { - return -1 - } - continue - } - - // 数值比较 - if num1 > num2 { - return 1 - } else if num1 < num2 { - return -1 - } - } - - // 所有部分都相等 - return 0 -} - -// loadAppConfigWithModTime 从文件加载配置,同时返回文件修改时间 -func (cs *ConfigService) loadAppConfigWithModTime() (*models.AppConfig, time.Time, error) { - config := &models.AppConfig{} - configPath := cs.GetFullConfigPath() - - // 先获取文件修改时间 - modTime, err := cs.fileService.GetFileModTime(configPath) - if err != nil { - return nil, time.Time{}, fmt.Errorf("failed to get config file modification time: %w", err) - } - - // 然后加载文件内容 - if err := cs.fileService.LoadJSON(configPath, config); err != nil { - return nil, time.Time{}, fmt.Errorf("failed to load config file: %w", err) - } - - return config, modTime, nil -} - -// handleConfigConflict 处理配置冲突的回调函数 -func (cs *ConfigService) handleConfigConflict(existingData []byte) (interface{}, error) { - // 如果没有当前配置,无法合并 - if cs.config == nil { - return nil, fmt.Errorf("no current config to merge") - } - - // 合并配置 - mergedConfig, err := cs.mergeConfigs(cs.config, existingData) - if err != nil { - return nil, err - } - - // 更新内存中的配置 - cs.config = mergedConfig - - return mergedConfig, nil -} - -// 内部方法:从文件加载配置 -func (cs *ConfigService) loadAppConfig() (*models.AppConfig, error) { - config, modTime, err := cs.loadAppConfigWithModTime() - if err != nil { - return nil, err - } - - // 更新最后修改时间 - cs.lastModTime = modTime - return config, nil -} - -// 内部方法:保存配置到文件 -func (cs *ConfigService) saveAppConfig(config *models.AppConfig) error { +// SaveConfig 保存完整应用配置 +func (cs *ConfigService) SaveConfig(config *models.AppConfig) error { // 更新配置元数据 config.Metadata.LastUpdated = time.Now() - // 保存到文件,使用乐观并发控制 - configPath := cs.GetFullConfigPath() - log.Printf("saveAppConfig: Saving to %s with last mod time: %s", - configPath, cs.lastModTime.Format(time.RFC3339)) + return cs.store.Set(*config) +} - err := cs.fileService.SaveJSONWithCheck( - configPath, - config, - cs.lastModTime, - cs.handleConfigConflict, - ) +// ResetConfig 重置为默认配置 +func (cs *ConfigService) ResetConfig() error { + defaultConfig := models.NewDefaultAppConfig() + err := cs.store.Set(*defaultConfig) if err != nil { - return fmt.Errorf("failed to save config file: %w", err) - } - - // 更新内存中的配置和最后修改时间 - cs.config = config - - // 获取最新的修改时间 - newModTime, err := cs.fileService.GetFileModTime(configPath) - if err == nil { - cs.lastModTime = newModTime + cs.logger.Error("Config: Failed to save default config", "error", err) + return fmt.Errorf("failed to reset config: %w", err) } return nil } -// GetAppConfig 获取应用配置 -func (cs *ConfigService) GetAppConfig() (*models.AppConfig, error) { - // 返回内存中的配置副本 - if cs.config != nil { - return cs.config, nil - } - - // 从文件加载 - config, err := cs.loadAppConfig() +// GetEditorConfig 获取编辑器配置 +func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) { + config, err := cs.GetConfig() if err != nil { - log.Printf("GetAppConfig: Failed to load config: %v", err) - // 使用默认配置 - defaultConfig := models.NewDefaultAppConfig() - cs.config = defaultConfig - - // 保存默认配置到文件 - if err := cs.saveAppConfig(defaultConfig); err != nil { - log.Printf("GetAppConfig: Failed to save default config: %v", err) - } - - return defaultConfig, nil + return models.EditorConfig{}, err } - - // 更新内存中的配置 - cs.config = config - return config, nil + return config.Editor, nil } -// SaveAppConfig 保存应用配置 -func (cs *ConfigService) SaveAppConfig(config *models.AppConfig) error { - return cs.saveAppConfig(config) +// UpdateEditorConfig 更新编辑器配置 +func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error { + // 获取当前配置 + config, err := cs.GetConfig() + if err != nil { + return err + } + + // 更新编辑器配置 + config.Editor = editorConfig + config.Metadata.LastUpdated = time.Now() + + // 保存更新后的配置 + return cs.store.Set(*config) } // GetLanguage 获取当前语言设置 func (cs *ConfigService) GetLanguage() (models.LanguageType, error) { - // 如果内存中已有配置,直接返回 - if cs.config != nil { - return cs.config.Language, nil - } - - // 否则从文件加载 - config, err := cs.loadAppConfig() + editorConfig, err := cs.GetEditorConfig() if err != nil { - log.Printf("GetLanguage: Failed to load config: %v", err) - // 使用默认配置 - defaultConfig := models.NewDefaultAppConfig() - cs.config = defaultConfig - return defaultConfig.Language, nil + return "", err } - - // 更新内存中的配置 - cs.config = config - log.Printf("GetLanguage: Retrieved language: %s", config.Language) - return config.Language, nil + return editorConfig.Language, nil } // SetLanguage 设置语言 @@ -344,89 +154,75 @@ func (cs *ConfigService) SetLanguage(language models.LanguageType) error { return fmt.Errorf("unsupported language: %s", language) } - // 如果内存中已有配置,直接更新 - if cs.config != nil { - log.Printf("SetLanguage: Updating language to: %s", language) - cs.config.Language = language - return cs.saveAppConfig(cs.config) - } - - // 没有内存中的配置,需要先加载 - config, err := cs.loadAppConfig() + // 获取当前配置 + config, err := cs.GetConfig() if err != nil { - log.Printf("SetLanguage: Failed to load config: %v", err) - // 使用默认配置 - config = models.NewDefaultAppConfig() + return err } - // 更新语言配置 - config.Language = language + // 更新语言设置 + config.Editor.Language = language + config.Metadata.LastUpdated = time.Now() - // 保存到文件 - return cs.saveAppConfig(config) + // 保存更新后的配置 + return cs.store.Set(*config) } -// UpdateEditorConfig 更新编辑器配置 -func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error { - // 如果内存中已有配置,直接更新 - if cs.config != nil { - log.Printf("UpdateEditorConfig: Updating in-memory editor config: %+v", editorConfig) - cs.config.Editor = editorConfig - return cs.saveAppConfig(cs.config) - } - - // 没有内存中的配置,需要先加载 - config, err := cs.loadAppConfig() +// GetPathConfig 获取路径配置 +func (cs *ConfigService) GetPathConfig() (models.PathConfig, error) { + config, err := cs.GetConfig() if err != nil { - log.Printf("UpdateEditorConfig: Failed to load config: %v", err) - // 使用默认配置 - config = models.NewDefaultAppConfig() + return models.PathConfig{}, err } - - // 更新编辑器配置 - config.Editor = editorConfig - - // 保存到文件 - return cs.saveAppConfig(config) + return config.Paths, nil } -// GetEditorConfig 获取编辑器配置 -func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) { - // 如果内存中已有配置,直接返回 - if cs.config != nil { - return cs.config.Editor, nil - } - - // 否则从文件加载 - config, err := cs.loadAppConfig() +// UpdatePathConfig 更新路径配置 +func (cs *ConfigService) UpdatePathConfig(pathConfig models.PathConfig) error { + // 获取当前配置 + config, err := cs.GetConfig() if err != nil { - log.Printf("GetEditorConfig: Failed to load config: %v", err) - // 使用默认配置 - defaultConfig := models.NewDefaultAppConfig() - cs.config = defaultConfig - return defaultConfig.Editor, nil + return err } - // 更新内存中的配置 - cs.config = config - log.Printf("GetEditorConfig: Retrieved editor config: %+v", config.Editor) - return config.Editor, nil + // 更新路径配置 + config.Paths = pathConfig + config.Metadata.LastUpdated = time.Now() + + // 保存更新后的配置 + return cs.store.Set(*config) } -// ResetToDefault 重置为默认配置 -func (cs *ConfigService) ResetToDefault() error { - // 创建默认配置 - defaultConfig := models.NewDefaultAppConfig() - - log.Printf("ResetToDefault: Resetting to default config") - - // 保存到文件 - err := cs.saveAppConfig(defaultConfig) +// GetMetadata 获取配置元数据 +func (cs *ConfigService) GetMetadata() (models.ConfigMetadata, error) { + config, err := cs.GetConfig() if err != nil { - log.Printf("ResetToDefault: Failed to save default config: %v", err) - return fmt.Errorf("failed to save default config: %w", err) + return models.ConfigMetadata{}, err + } + return config.Metadata, nil +} + +// UpdateMetadata 更新配置元数据 +func (cs *ConfigService) UpdateMetadata(metadata models.ConfigMetadata) error { + // 获取当前配置 + config, err := cs.GetConfig() + if err != nil { + return err } - log.Printf("ResetToDefault: Successfully reset to default config") + // 更新元数据 + config.Metadata = metadata + config.Metadata.LastUpdated = time.Now() + + // 保存更新后的配置 + return cs.store.Set(*config) +} + +// OnShutdown 服务关闭时调用 +func (cs *ConfigService) OnShutdown() error { + // 如果有未保存的更改,保存数据 + if cs.store.HasUnsavedChanges() { + return cs.store.Save() + } return nil } diff --git a/internal/services/file_service.go b/internal/services/file_service.go deleted file mode 100644 index 87c6467..0000000 --- a/internal/services/file_service.go +++ /dev/null @@ -1,195 +0,0 @@ -package services - -import ( - "encoding/json" - "fmt" - "log" - "os" - "path/filepath" - "time" -) - -// FileService 提供文件操作 -type FileService struct{} - -// NewFileService 创建新的文件服务实例 -func NewFileService() *FileService { - return &FileService{} -} - -// EnsureDir 确保目录存在,如不存在则创建 -func (fs *FileService) EnsureDir(dirPath string) error { - log.Printf("EnsureDir: Checking directory: %s", dirPath) - - if _, err := os.Stat(dirPath); os.IsNotExist(err) { - log.Printf("EnsureDir: Directory does not exist, creating: %s", dirPath) - err := os.MkdirAll(dirPath, 0755) - if err != nil { - log.Printf("EnsureDir: Failed to create directory: %v", err) - return err - } - log.Printf("EnsureDir: Directory created successfully: %s", dirPath) - } else { - log.Printf("EnsureDir: Directory already exists: %s", dirPath) - } - return nil -} - -// GetFileModTime 获取文件的修改时间 -func (fs *FileService) GetFileModTime(filePath string) (time.Time, error) { - log.Printf("GetFileModTime: Getting modification time for file: %s", filePath) - - fileInfo, err := os.Stat(filePath) - if err != nil { - if os.IsNotExist(err) { - log.Printf("GetFileModTime: File does not exist: %s", filePath) - return time.Time{}, fmt.Errorf("file does not exist: %w", err) - } - log.Printf("GetFileModTime: Failed to get file info: %v", err) - return time.Time{}, fmt.Errorf("failed to get file info: %w", err) - } - - modTime := fileInfo.ModTime() - log.Printf("GetFileModTime: File modification time: %s", modTime.Format(time.RFC3339)) - return modTime, nil -} - -// SaveJSONWithCheck 保存JSON数据到文件,带并发检查 -// expectedModTime是期望的文件修改时间,如果为零值则不检查 -// onConflict是冲突处理函数,如果文件已被修改且此函数不为nil,则调用此函数合并数据 -func (fs *FileService) SaveJSONWithCheck(filePath string, data interface{}, expectedModTime time.Time, onConflict func(existingData []byte) (interface{}, error)) error { - log.Printf("SaveJSONWithCheck: Saving to file with concurrency check: %s", filePath) - - // 检查文件是否存在且已被修改 - if !expectedModTime.IsZero() && fs.FileExists(filePath) { - currentModTime, err := fs.GetFileModTime(filePath) - if err == nil { - // 如果文件修改时间与预期不符,说明文件已被其他进程修改 - if !currentModTime.Equal(expectedModTime) { - log.Printf("SaveJSONWithCheck: File has been modified since last read. Expected: %s, Current: %s", - expectedModTime.Format(time.RFC3339), currentModTime.Format(time.RFC3339)) - - // 如果提供了冲突处理函数,尝试解决冲突 - if onConflict != nil { - log.Printf("SaveJSONWithCheck: Attempting to resolve conflict") - - // 读取当前文件内容 - existingData, err := os.ReadFile(filePath) - if err != nil { - log.Printf("SaveJSONWithCheck: Failed to read existing file: %v", err) - return fmt.Errorf("failed to read existing file for conflict resolution: %w", err) - } - - // 调用冲突处理函数合并数据 - mergedData, err := onConflict(existingData) - if err != nil { - log.Printf("SaveJSONWithCheck: Conflict resolution failed: %v", err) - return fmt.Errorf("conflict resolution failed: %w", err) - } - - // 使用合并后的数据继续保存 - data = mergedData - log.Printf("SaveJSONWithCheck: Conflict resolved, proceeding with merged data") - } else { - // 没有提供冲突处理函数,返回错误 - return fmt.Errorf("concurrent modification detected") - } - } - } else { - log.Printf("SaveJSONWithCheck: Could not check file modification time: %v", err) - // 继续执行,不中断操作 - } - } - - // 确保目录存在 - dir := filepath.Dir(filePath) - if err := fs.EnsureDir(dir); err != nil { - log.Printf("SaveJSONWithCheck: Failed to create directory: %v", err) - return fmt.Errorf("failed to create directory: %w", err) - } - - // 将数据编码为JSON - jsonData, err := json.MarshalIndent(data, "", " ") - if err != nil { - log.Printf("SaveJSONWithCheck: Failed to encode JSON: %v", err) - return fmt.Errorf("failed to encode JSON: %w", err) - } - - // 先写入临时文件 - tempFile := filePath + ".tmp" - log.Printf("SaveJSONWithCheck: Writing to temporary file: %s", tempFile) - if err := os.WriteFile(tempFile, jsonData, 0644); err != nil { - log.Printf("SaveJSONWithCheck: Failed to write temporary file: %v", err) - return fmt.Errorf("failed to write temporary file: %w", err) - } - - // 原子替换原文件 - log.Printf("SaveJSONWithCheck: Replacing original file with temporary file") - if err := os.Rename(tempFile, filePath); err != nil { - os.Remove(tempFile) // 清理临时文件 - log.Printf("SaveJSONWithCheck: Failed to replace file: %v", err) - return fmt.Errorf("failed to replace file: %w", err) - } - - log.Printf("SaveJSONWithCheck: File saved successfully: %s", filePath) - return nil -} - -// SaveJSON 保存JSON数据到文件 -func (fs *FileService) SaveJSON(filePath string, data interface{}) error { - // 调用带并发检查的版本,但不提供冲突解决函数(保持原有行为) - return fs.SaveJSONWithCheck(filePath, data, time.Time{}, nil) -} - -// LoadJSON 从文件加载JSON数据 -func (fs *FileService) LoadJSON(filePath string, target interface{}) error { - log.Printf("LoadJSON: Loading from file: %s", filePath) - - // 检查文件是否存在 - if _, err := os.Stat(filePath); os.IsNotExist(err) { - log.Printf("LoadJSON: File does not exist: %s", filePath) - return fmt.Errorf("file does not exist: %w", err) - } - - // 读取文件内容 - data, err := os.ReadFile(filePath) - if err != nil { - log.Printf("LoadJSON: Failed to read file: %v", err) - return fmt.Errorf("failed to read file: %w", err) - } - - // 解析JSON数据 - if err := json.Unmarshal(data, target); err != nil { - log.Printf("LoadJSON: Failed to parse JSON: %v", err) - return fmt.Errorf("failed to parse JSON: %w", err) - } - - log.Printf("LoadJSON: File loaded successfully: %s", filePath) - return nil -} - -// FileExists 检查文件是否存在 -func (fs *FileService) FileExists(filePath string) bool { - _, err := os.Stat(filePath) - exists := !os.IsNotExist(err) - log.Printf("FileExists: Checking if file exists: %s, exists: %v", filePath, exists) - return exists -} - -// DeleteFile 删除文件 -func (fs *FileService) DeleteFile(filePath string) error { - log.Printf("DeleteFile: Deleting file: %s", filePath) - - if !fs.FileExists(filePath) { - log.Printf("DeleteFile: File does not exist, nothing to delete: %s", filePath) - return nil // 文件不存在视为删除成功 - } - - err := os.Remove(filePath) - if err != nil { - log.Printf("DeleteFile: Failed to delete file: %v", err) - } else { - log.Printf("DeleteFile: File deleted successfully: %s", filePath) - } - return err -} diff --git a/internal/services/service_manager.go b/internal/services/service_manager.go index e712e5e..3eb3955 100644 --- a/internal/services/service_manager.go +++ b/internal/services/service_manager.go @@ -6,20 +6,15 @@ import ( // ServiceManager 服务管理器,负责协调各个服务 type ServiceManager struct { - fileService *FileService configService *ConfigService } // NewServiceManager 创建新的服务管理器实例 func NewServiceManager() *ServiceManager { - // 初始化文件服务 - fileService := NewFileService() - // 初始化配置服务 - configService := NewConfigService(fileService) + configService := NewConfigService() return &ServiceManager{ - fileService: fileService, configService: configService, } } @@ -27,16 +22,10 @@ func NewServiceManager() *ServiceManager { // GetServices 获取所有wails服务列表 func (sm *ServiceManager) GetServices() []application.Service { return []application.Service{ - application.NewService(sm.fileService), application.NewService(sm.configService), } } -// GetFileService 获取文件服务实例 -func (sm *ServiceManager) GetFileService() *FileService { - return sm.fileService -} - // GetConfigService 获取配置服务实例 func (sm *ServiceManager) GetConfigService() *ConfigService { return sm.configService diff --git a/internal/services/store_service.go b/internal/services/store_service.go new file mode 100644 index 0000000..e0c839e --- /dev/null +++ b/internal/services/store_service.go @@ -0,0 +1,232 @@ +package services + +import ( + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + "sync" + + "github.com/wailsapp/wails/v3/pkg/services/log" +) + +// StoreOption 存储服务配置选项 +type StoreOption struct { + FilePath string // 存储文件路径 + AutoSave bool // 是否自动保存 + Logger *log.LoggerService +} + +// Store 泛型存储服务 +type Store[T any] struct { + option StoreOption + data T + dataMap map[string]any + unsaved bool + lock sync.RWMutex + logger *log.LoggerService +} + +// NewStore 存储服务 +func NewStore[T any](option StoreOption) *Store[T] { + logger := option.Logger + if logger == nil { + logger = log.New() + } + + // 确保目录存在 + if option.FilePath != "" { + dir := filepath.Dir(option.FilePath) + if err := os.MkdirAll(dir, 0755); err != nil { + logger.Error("store: Failed to create directory", "path", dir, "error", err) + } + } + + store := &Store[T]{ + option: option, + dataMap: make(map[string]any), + logger: logger, + } + + // 加载数据 + if err := store.load(); err != nil { + logger.Error("store: Failed to load data", "error", err) + } + + return store +} + +// load 加载数据 +func (s *Store[T]) load() error { + if s.option.FilePath == "" { + return fmt.Errorf("store: FilePath not set") + } + + // 如果文件不存在 + if _, err := os.Stat(s.option.FilePath); os.IsNotExist(err) { + return nil + } + + file, err := os.Open(s.option.FilePath) + if err != nil { + return fmt.Errorf("store: Failed to open file: %w", err) + } + defer file.Close() + + bytes, err := io.ReadAll(file) + if err != nil { + return fmt.Errorf("store: Failed to read file: %w", err) + } + + if len(bytes) == 0 { + return nil + } + + s.lock.Lock() + defer s.lock.Unlock() + + if err := json.Unmarshal(bytes, &s.data); err != nil { + // 尝试加载为map格式 + if err := json.Unmarshal(bytes, &s.dataMap); err != nil { + return fmt.Errorf("store: Failed to parse data: %w", err) + } + } + + return nil +} + +// Save 保存数据 +func (s *Store[T]) Save() error { + s.lock.Lock() + defer s.lock.Unlock() + + err := s.saveInternal() + if err != nil { + s.logger.Error("store: Failed to save", "error", err) + return fmt.Errorf("store: Failed to save: %w", err) + } + + s.unsaved = false + return nil +} + +// saveInternal 内部保存实现 +func (s *Store[T]) saveInternal() error { + if s.option.FilePath == "" { + return fmt.Errorf("store: FilePath not set") + } + + // 创建临时文件 + dir := filepath.Dir(s.option.FilePath) + tempFile, err := os.CreateTemp(dir, "store-*.tmp") + if err != nil { + return fmt.Errorf("store: Failed to create temp file: %w", err) + } + tempFilePath := tempFile.Name() + defer func() { + tempFile.Close() + // 如果出错,删除临时文件 + if err != nil { + os.Remove(tempFilePath) + } + }() + + // 序列化数据 + bytes, err := json.MarshalIndent(s.data, "", " ") + if err != nil { + return fmt.Errorf("store: Failed to serialize data: %w", err) + } + + // 写入临时文件 + if _, err = tempFile.Write(bytes); err != nil { + return fmt.Errorf("store: Failed to write temp file: %w", err) + } + + // 确保所有数据已写入磁盘 + if err = tempFile.Sync(); err != nil { + return fmt.Errorf("store: Failed to sync file: %w", err) + } + + // 关闭临时文件 + if err = tempFile.Close(); err != nil { + return fmt.Errorf("store: Failed to close temp file: %w", err) + } + + // 原子替换文件 + if err = os.Rename(tempFilePath, s.option.FilePath); err != nil { + return fmt.Errorf("store: Failed to rename file: %w", err) + } + + return nil +} + +// Get 获取数据 +func (s *Store[T]) Get() T { + s.lock.RLock() + defer s.lock.RUnlock() + return s.data +} + +// GetProperty 获取指定属性 +func (s *Store[T]) GetProperty(key string) any { + s.lock.RLock() + defer s.lock.RUnlock() + + if key == "" { + return s.dataMap + } + return s.dataMap[key] +} + +// Set 设置数据 +func (s *Store[T]) Set(data T) error { + s.lock.Lock() + defer s.lock.Unlock() + + s.data = data + s.unsaved = true + + if s.option.AutoSave { + return s.saveInternal() + } + + return nil +} + +// SetProperty 设置指定属性 +func (s *Store[T]) SetProperty(key string, value any) error { + s.lock.Lock() + defer s.lock.Unlock() + + s.dataMap[key] = value + s.unsaved = true + + if s.option.AutoSave { + return s.saveInternal() + } + + return nil +} + +// Delete 删除指定属性 +func (s *Store[T]) Delete(key string) error { + s.lock.Lock() + defer s.lock.Unlock() + + delete(s.dataMap, key) + s.unsaved = true + + if s.option.AutoSave { + return s.saveInternal() + } + + return nil +} + +// HasUnsavedChanges 是否有未保存的更改 +func (s *Store[T]) HasUnsavedChanges() bool { + s.lock.RLock() + defer s.lock.RUnlock() + return s.unsaved +}