✨ Add configuration information file storage
This commit is contained in:
4
frontend/bindings/time/index.ts
Normal file
4
frontend/bindings/time/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export * from "./models.js";
|
51
frontend/bindings/time/models.ts
Normal file
51
frontend/bindings/time/models.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Time represents an instant in time with nanosecond precision.
|
||||||
|
*
|
||||||
|
* Programs using times should typically store and pass them as values,
|
||||||
|
* not pointers. That is, time variables and struct fields should be of
|
||||||
|
* type [time.Time], not *time.Time.
|
||||||
|
*
|
||||||
|
* A Time value can be used by multiple goroutines simultaneously except
|
||||||
|
* that the methods [Time.GobDecode], [Time.UnmarshalBinary], [Time.UnmarshalJSON] and
|
||||||
|
* [Time.UnmarshalText] are not concurrency-safe.
|
||||||
|
*
|
||||||
|
* Time instants can be compared using the [Time.Before], [Time.After], and [Time.Equal] methods.
|
||||||
|
* The [Time.Sub] method subtracts two instants, producing a [Duration].
|
||||||
|
* The [Time.Add] method adds a Time and a Duration, producing a Time.
|
||||||
|
*
|
||||||
|
* The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
|
||||||
|
* As this time is unlikely to come up in practice, the [Time.IsZero] method gives
|
||||||
|
* a simple way of detecting a time that has not been initialized explicitly.
|
||||||
|
*
|
||||||
|
* Each time has an associated [Location]. The methods [Time.Local], [Time.UTC], and Time.In return a
|
||||||
|
* Time with a specific Location. Changing the Location of a Time value with
|
||||||
|
* these methods does not change the actual instant it represents, only the time
|
||||||
|
* zone in which to interpret it.
|
||||||
|
*
|
||||||
|
* Representations of a Time value saved by the [Time.GobEncode], [Time.MarshalBinary], [Time.AppendBinary],
|
||||||
|
* [Time.MarshalJSON], [Time.MarshalText] and [Time.AppendText] methods store the [Time.Location]'s offset,
|
||||||
|
* but not the location name. They therefore lose information about Daylight Saving Time.
|
||||||
|
*
|
||||||
|
* In addition to the required “wall clock” reading, a Time may contain an optional
|
||||||
|
* reading of the current process's monotonic clock, to provide additional precision
|
||||||
|
* for comparison or subtraction.
|
||||||
|
* See the “Monotonic Clocks” section in the package documentation for details.
|
||||||
|
*
|
||||||
|
* Note that the Go == operator compares not just the time instant but also the
|
||||||
|
* Location and the monotonic clock reading. Therefore, Time values should not
|
||||||
|
* be used as map or database keys without first guaranteeing that the
|
||||||
|
* identical Location has been set for all values, which can be achieved
|
||||||
|
* through use of the UTC or Local method, and that the monotonic clock reading
|
||||||
|
* has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u)
|
||||||
|
* to t == u, since t.Equal uses the most accurate comparison available and
|
||||||
|
* correctly handles the case when only one of its arguments has a monotonic
|
||||||
|
* clock reading.
|
||||||
|
*/
|
||||||
|
export type Time = any;
|
4
frontend/bindings/voidraft/internal/models/index.ts
Normal file
4
frontend/bindings/voidraft/internal/models/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
export * from "./models.js";
|
219
frontend/bindings/voidraft/internal/models/models.ts
Normal file
219
frontend/bindings/voidraft/internal/models/models.ts
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import {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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AppConfig 应用配置
|
||||||
|
*/
|
||||||
|
export class AppConfig {
|
||||||
|
/**
|
||||||
|
* 编辑器配置
|
||||||
|
*/
|
||||||
|
"editor": EditorConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 路径配置
|
||||||
|
*/
|
||||||
|
"paths": PathConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置元数据
|
||||||
|
*/
|
||||||
|
"metadata": ConfigMetadata;
|
||||||
|
|
||||||
|
/** Creates a new AppConfig instance. */
|
||||||
|
constructor($$source: Partial<AppConfig> = {}) {
|
||||||
|
if (!("editor" in $$source)) {
|
||||||
|
this["editor"] = (new EditorConfig());
|
||||||
|
}
|
||||||
|
if (!("paths" in $$source)) {
|
||||||
|
this["paths"] = (new PathConfig());
|
||||||
|
}
|
||||||
|
if (!("metadata" in $$source)) {
|
||||||
|
this["metadata"] = (new ConfigMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new AppConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): AppConfig {
|
||||||
|
const $$createField0_0 = $$createType0;
|
||||||
|
const $$createField1_0 = $$createType1;
|
||||||
|
const $$createField2_0 = $$createType2;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("editor" in $$parsedSource) {
|
||||||
|
$$parsedSource["editor"] = $$createField0_0($$parsedSource["editor"]);
|
||||||
|
}
|
||||||
|
if ("paths" in $$parsedSource) {
|
||||||
|
$$parsedSource["paths"] = $$createField1_0($$parsedSource["paths"]);
|
||||||
|
}
|
||||||
|
if ("metadata" in $$parsedSource) {
|
||||||
|
$$parsedSource["metadata"] = $$createField2_0($$parsedSource["metadata"]);
|
||||||
|
}
|
||||||
|
return new AppConfig($$parsedSource as Partial<AppConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConfigMetadata 配置元数据
|
||||||
|
*/
|
||||||
|
export class ConfigMetadata {
|
||||||
|
/**
|
||||||
|
* 配置版本
|
||||||
|
*/
|
||||||
|
"version": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最后更新时间
|
||||||
|
*/
|
||||||
|
"lastUpdated": time$0.Time;
|
||||||
|
|
||||||
|
/** Creates a new ConfigMetadata instance. */
|
||||||
|
constructor($$source: Partial<ConfigMetadata> = {}) {
|
||||||
|
if (!("version" in $$source)) {
|
||||||
|
this["version"] = "";
|
||||||
|
}
|
||||||
|
if (!("lastUpdated" in $$source)) {
|
||||||
|
this["lastUpdated"] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new ConfigMetadata instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): ConfigMetadata {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new ConfigMetadata($$parsedSource as Partial<ConfigMetadata>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EditorConfig 定义编辑器配置
|
||||||
|
*/
|
||||||
|
export class EditorConfig {
|
||||||
|
/**
|
||||||
|
* 字体大小
|
||||||
|
*/
|
||||||
|
"fontSize": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文件保存的编码
|
||||||
|
*/
|
||||||
|
"encoding": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用Tab缩进
|
||||||
|
*/
|
||||||
|
"enableTabIndent": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab大小
|
||||||
|
*/
|
||||||
|
"tabSize": number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tab类型(空格或Tab)
|
||||||
|
*/
|
||||||
|
"tabType": TabType;
|
||||||
|
|
||||||
|
/** Creates a new EditorConfig instance. */
|
||||||
|
constructor($$source: Partial<EditorConfig> = {}) {
|
||||||
|
if (!("fontSize" in $$source)) {
|
||||||
|
this["fontSize"] = 0;
|
||||||
|
}
|
||||||
|
if (!("encoding" in $$source)) {
|
||||||
|
this["encoding"] = "";
|
||||||
|
}
|
||||||
|
if (!("enableTabIndent" in $$source)) {
|
||||||
|
this["enableTabIndent"] = false;
|
||||||
|
}
|
||||||
|
if (!("tabSize" in $$source)) {
|
||||||
|
this["tabSize"] = 0;
|
||||||
|
}
|
||||||
|
if (!("tabType" in $$source)) {
|
||||||
|
this["tabType"] = ("" as TabType);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new EditorConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): EditorConfig {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new EditorConfig($$parsedSource as Partial<EditorConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PathConfig 定义配置文件路径相关配置
|
||||||
|
*/
|
||||||
|
export class PathConfig {
|
||||||
|
/**
|
||||||
|
* 根目录
|
||||||
|
*/
|
||||||
|
"rootDir": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置文件路径
|
||||||
|
*/
|
||||||
|
"configPath": string;
|
||||||
|
|
||||||
|
/** Creates a new PathConfig instance. */
|
||||||
|
constructor($$source: Partial<PathConfig> = {}) {
|
||||||
|
if (!("rootDir" in $$source)) {
|
||||||
|
this["rootDir"] = "";
|
||||||
|
}
|
||||||
|
if (!("configPath" in $$source)) {
|
||||||
|
this["configPath"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new PathConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): PathConfig {
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
return new PathConfig($$parsedSource as Partial<PathConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TabType 定义了制表符类型
|
||||||
|
*/
|
||||||
|
export enum TabType {
|
||||||
|
/**
|
||||||
|
* The Go zero value for the underlying type of the enum.
|
||||||
|
*/
|
||||||
|
$zero = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TabTypeSpaces 使用空格作为制表符
|
||||||
|
*/
|
||||||
|
TabTypeSpaces = "spaces",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TabTypeTab 使用Tab作为制表符
|
||||||
|
*/
|
||||||
|
TabTypeTab = "tab",
|
||||||
|
};
|
||||||
|
|
||||||
|
// Private type creation functions
|
||||||
|
const $$createType0 = EditorConfig.createFrom;
|
||||||
|
const $$createType1 = PathConfig.createFrom;
|
||||||
|
const $$createType2 = ConfigMetadata.createFrom;
|
@@ -0,0 +1,76 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConfigService 提供配置管理功能
|
||||||
|
* @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 models$0 from "../models/models.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetAppConfig 获取应用配置
|
||||||
|
*/
|
||||||
|
export function GetAppConfig(): Promise<models$0.AppConfig | null> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3361428829) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType1($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetEditorConfig 获取编辑器配置
|
||||||
|
*/
|
||||||
|
export function GetEditorConfig(): Promise<models$0.EditorConfig> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3648153351) as any;
|
||||||
|
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||||
|
return $$createType2($result);
|
||||||
|
}) as any;
|
||||||
|
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||||
|
return $typingPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetFullConfigPath 获取完整的配置文件路径
|
||||||
|
*/
|
||||||
|
export function GetFullConfigPath(): Promise<string> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(38527092) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResetToDefault 重置为默认配置
|
||||||
|
*/
|
||||||
|
export function ResetToDefault(): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(4057687351) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SaveAppConfig 保存应用配置
|
||||||
|
*/
|
||||||
|
export function SaveAppConfig(config: models$0.AppConfig | null): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2077587650, config) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UpdateEditorConfig 更新编辑器配置
|
||||||
|
*/
|
||||||
|
export function UpdateEditorConfig(editorConfig: models$0.EditorConfig): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1237949666, editorConfig) 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;
|
51
frontend/bindings/voidraft/internal/services/fileservice.ts
Normal file
51
frontend/bindings/voidraft/internal/services/fileservice.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// 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";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeleteFile 删除文件
|
||||||
|
*/
|
||||||
|
export function DeleteFile(filePath: string): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1771867857, filePath) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EnsureDir 确保目录存在,如不存在则创建
|
||||||
|
*/
|
||||||
|
export function EnsureDir(dirPath: string): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2291976369, dirPath) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileExists 检查文件是否存在
|
||||||
|
*/
|
||||||
|
export function FileExists(filePath: string): Promise<boolean> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(4264173930, filePath) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoadJSON 从文件加载JSON数据
|
||||||
|
*/
|
||||||
|
export function LoadJSON(filePath: string, target: any): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1385779418, filePath, target) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SaveJSON 原子化保存JSON数据到文件
|
||||||
|
*/
|
||||||
|
export function SaveJSON(filePath: string, data: any): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(3646933935, filePath, data) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
9
frontend/bindings/voidraft/internal/services/index.ts
Normal file
9
frontend/bindings/voidraft/internal/services/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||||
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
|
import * as ConfigService from "./configservice.js";
|
||||||
|
import * as FileService from "./fileservice.js";
|
||||||
|
export {
|
||||||
|
ConfigService,
|
||||||
|
FileService
|
||||||
|
};
|
45
frontend/package-lock.json
generated
45
frontend/package-lock.json
generated
@@ -40,6 +40,7 @@
|
|||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@primeuix/themes": "^1.0.3",
|
"@primeuix/themes": "^1.0.3",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@vueuse/core": "^13.1.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"pinia-plugin-persistedstate": "^4.2.0",
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
@@ -2156,6 +2157,12 @@
|
|||||||
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
|
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/web-bluetooth": {
|
||||||
|
"version": "0.0.21",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz",
|
||||||
|
"integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "8.31.0",
|
"version": "8.31.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz",
|
||||||
@@ -2600,6 +2607,44 @@
|
|||||||
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/core": {
|
||||||
|
"version": "13.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-13.1.0.tgz",
|
||||||
|
"integrity": "sha512-PAauvdRXZvTWXtGLg8cPUFjiZEddTqmogdwYpnn60t08AA5a8Q4hZokBnpTOnVNqySlFlTcRYIC8OqreV4hv3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/web-bluetooth": "^0.0.21",
|
||||||
|
"@vueuse/metadata": "13.1.0",
|
||||||
|
"@vueuse/shared": "13.1.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/metadata": {
|
||||||
|
"version": "13.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-13.1.0.tgz",
|
||||||
|
"integrity": "sha512-+TDd7/a78jale5YbHX9KHW3cEDav1lz1JptwDvep2zSG8XjCsVE+9mHIzjTOaPbHUAk5XiE4jXLz51/tS+aKQw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared": {
|
||||||
|
"version": "13.1.0",
|
||||||
|
"resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-13.1.0.tgz",
|
||||||
|
"integrity": "sha512-IVS/qRRjhPTZ6C2/AM3jieqXACGwFZwWTdw5sNTSKk2m/ZpkuuN+ri+WCVUP8TqaKwJYt/KuMwmXspMAw8E6ew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.5.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@wailsio/runtime": {
|
"node_modules/@wailsio/runtime": {
|
||||||
"version": "3.0.0-alpha.66",
|
"version": "3.0.0-alpha.66",
|
||||||
"resolved": "https://registry.npmmirror.com/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz",
|
"resolved": "https://registry.npmmirror.com/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz",
|
||||||
|
@@ -44,6 +44,7 @@
|
|||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@primeuix/themes": "^1.0.3",
|
"@primeuix/themes": "^1.0.3",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
|
"@vueuse/core": "^13.1.0",
|
||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"pinia": "^3.0.2",
|
"pinia": "^3.0.2",
|
||||||
"pinia-plugin-persistedstate": "^4.2.0",
|
"pinia-plugin-persistedstate": "^4.2.0",
|
||||||
|
@@ -1,14 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import {onMounted} from 'vue';
|
||||||
import Editor from '@/editor/Editor.vue';
|
import Editor from '@/editor/Editor.vue';
|
||||||
import Toolbar from '@/components/toolbar/Toolbar.vue';
|
import Toolbar from '@/components/toolbar/Toolbar.vue';
|
||||||
|
import {useConfigStore} from "@/stores/configStore";
|
||||||
|
|
||||||
|
const configStore = useConfigStore();
|
||||||
|
onMounted(async () => {
|
||||||
|
await configStore.loadConfigFromBackend();
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<div class="editor-wrapper">
|
<div class="editor-wrapper">
|
||||||
<Editor />
|
<Editor/>
|
||||||
</div>
|
</div>
|
||||||
<Toolbar />
|
<Toolbar/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -20,7 +27,7 @@ import Toolbar from '@/components/toolbar/Toolbar.vue';
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
.editor-wrapper {
|
.editor-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@@ -38,7 +38,7 @@ const configStore = useConfigStore();
|
|||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="encoding">{{ configStore.config.encoding }}</span>
|
<span class="encoding">{{ configStore.config.encoding }}</span>
|
||||||
<button class="settings-btn" @click="configStore.openSettings">
|
<button class="settings-btn">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none"
|
||||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
@@ -7,10 +7,10 @@ import {useConfigStore} from '@/stores/configStore';
|
|||||||
import {createBasicSetup} from './extensions/basicSetup';
|
import {createBasicSetup} from './extensions/basicSetup';
|
||||||
import {
|
import {
|
||||||
createStatsUpdateExtension,
|
createStatsUpdateExtension,
|
||||||
getTabExtensions,
|
|
||||||
updateTabConfig,
|
|
||||||
createWheelZoomHandler,
|
createWheelZoomHandler,
|
||||||
updateStats
|
getTabExtensions,
|
||||||
|
updateStats,
|
||||||
|
updateTabConfig
|
||||||
} from './extensions';
|
} from './extensions';
|
||||||
|
|
||||||
const editorStore = useEditorStore();
|
const editorStore = useEditorStore();
|
||||||
@@ -19,7 +19,7 @@ const configStore = useConfigStore();
|
|||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
initialDoc: {
|
initialDoc: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '// 在此处编写代码'
|
default: '// 在此处编写文本...'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -31,17 +31,17 @@ const createEditor = () => {
|
|||||||
|
|
||||||
// 获取基本扩展
|
// 获取基本扩展
|
||||||
const basicExtensions = createBasicSetup();
|
const basicExtensions = createBasicSetup();
|
||||||
|
|
||||||
// 获取Tab相关扩展
|
// 获取Tab相关扩展
|
||||||
const tabExtensions = getTabExtensions(
|
const tabExtensions = getTabExtensions(
|
||||||
configStore.config.tabSize,
|
configStore.config.tabSize,
|
||||||
configStore.config.enableTabIndent,
|
configStore.config.enableTabIndent,
|
||||||
configStore.config.tabType
|
configStore.config.tabType
|
||||||
);
|
);
|
||||||
|
|
||||||
// 创建统计信息更新扩展
|
// 创建统计信息更新扩展
|
||||||
const statsExtension = createStatsUpdateExtension(
|
const statsExtension = createStatsUpdateExtension(
|
||||||
editorStore.updateDocumentStats
|
editorStore.updateDocumentStats
|
||||||
);
|
);
|
||||||
|
|
||||||
// 组合所有扩展
|
// 组合所有扩展
|
||||||
@@ -62,31 +62,31 @@ const createEditor = () => {
|
|||||||
state,
|
state,
|
||||||
parent: editorElement.value
|
parent: editorElement.value
|
||||||
});
|
});
|
||||||
|
|
||||||
// 将编辑器实例保存到store
|
// 将编辑器实例保存到store
|
||||||
editorStore.setEditorView(view);
|
editorStore.setEditorView(view);
|
||||||
|
|
||||||
// 应用初始字体大小
|
// 应用初始字体大小
|
||||||
editorStore.applyFontSize();
|
editorStore.applyFontSize();
|
||||||
|
|
||||||
// 立即更新统计信息,不等待用户交互
|
// 立即更新统计信息,不等待用户交互
|
||||||
updateStats(view, editorStore.updateDocumentStats);
|
updateStats(view, editorStore.updateDocumentStats);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建滚轮事件处理器
|
// 创建滚轮事件处理器
|
||||||
const handleWheel = createWheelZoomHandler(
|
const handleWheel = createWheelZoomHandler(
|
||||||
configStore.increaseFontSize,
|
configStore.increaseFontSize,
|
||||||
configStore.decreaseFontSize
|
configStore.decreaseFontSize
|
||||||
);
|
);
|
||||||
|
|
||||||
// 重新配置编辑器(仅在必要时)
|
// 重新配置编辑器(仅在必要时)
|
||||||
const reconfigureTabSettings = () => {
|
const reconfigureTabSettings = () => {
|
||||||
if (!editorStore.editorView) return;
|
if (!editorStore.editorView) return;
|
||||||
updateTabConfig(
|
updateTabConfig(
|
||||||
editorStore.editorView as EditorView,
|
editorStore.editorView as EditorView,
|
||||||
configStore.config.tabSize,
|
configStore.config.tabSize,
|
||||||
configStore.config.enableTabIndent,
|
configStore.config.enableTabIndent,
|
||||||
configStore.config.tabType
|
configStore.config.tabType
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,12 +103,12 @@ watch(() => configStore.config.fontSize, () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 创建编辑器
|
// 创建编辑器
|
||||||
createEditor();
|
createEditor();
|
||||||
|
|
||||||
// 添加滚轮事件监听
|
// 添加滚轮事件监听
|
||||||
if (editorElement.value) {
|
if (editorElement.value) {
|
||||||
editorElement.value.addEventListener('wheel', handleWheel, {passive: false});
|
editorElement.value.addEventListener('wheel', handleWheel, {passive: false});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保统计信息已更新
|
// 确保统计信息已更新
|
||||||
if (editorStore.editorView) {
|
if (editorStore.editorView) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@@ -122,7 +122,7 @@ onBeforeUnmount(() => {
|
|||||||
if (editorElement.value) {
|
if (editorElement.value) {
|
||||||
editorElement.value.removeEventListener('wheel', handleWheel);
|
editorElement.value.removeEventListener('wheel', handleWheel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 销毁编辑器
|
// 销毁编辑器
|
||||||
if (editorStore.editorView) {
|
if (editorStore.editorView) {
|
||||||
editorStore.editorView.destroy();
|
editorStore.editorView.destroy();
|
||||||
|
@@ -2,7 +2,7 @@ import {Compartment, Extension} from '@codemirror/state';
|
|||||||
import {EditorView, keymap} from '@codemirror/view';
|
import {EditorView, keymap} from '@codemirror/view';
|
||||||
import {indentSelection} from '@codemirror/commands';
|
import {indentSelection} from '@codemirror/commands';
|
||||||
import {indentUnit} from '@codemirror/language';
|
import {indentUnit} from '@codemirror/language';
|
||||||
import {TabType} from "@/types/config";
|
import {TabType} from '@/../bindings/voidraft/internal/models/models';
|
||||||
|
|
||||||
// Tab设置相关的compartment
|
// Tab设置相关的compartment
|
||||||
export const tabSizeCompartment = new Compartment();
|
export const tabSizeCompartment = new Compartment();
|
||||||
|
@@ -1,6 +1,12 @@
|
|||||||
import {defineStore} from 'pinia';
|
import {defineStore} from 'pinia';
|
||||||
import {ref} from 'vue';
|
import {ref, watch} from 'vue';
|
||||||
import {EditorConfig} from '@/types/config';
|
import {useDebounceFn} from '@vueuse/core';
|
||||||
|
import {
|
||||||
|
GetEditorConfig,
|
||||||
|
ResetToDefault,
|
||||||
|
UpdateEditorConfig
|
||||||
|
} from '@/../bindings/voidraft/internal/services/configservice';
|
||||||
|
import {EditorConfig, TabType} from '@/../bindings/voidraft/internal/models/models';
|
||||||
|
|
||||||
// 字体大小范围
|
// 字体大小范围
|
||||||
const MIN_FONT_SIZE = 12;
|
const MIN_FONT_SIZE = 12;
|
||||||
@@ -14,13 +20,43 @@ const MAX_TAB_SIZE = 8;
|
|||||||
|
|
||||||
export const useConfigStore = defineStore('config', () => {
|
export const useConfigStore = defineStore('config', () => {
|
||||||
// 配置状态
|
// 配置状态
|
||||||
const config = ref<EditorConfig>({
|
const config = ref<EditorConfig>(new EditorConfig({
|
||||||
fontSize: DEFAULT_FONT_SIZE,
|
fontSize: DEFAULT_FONT_SIZE,
|
||||||
encoding: 'UTF-8',
|
encoding: 'UTF-8',
|
||||||
enableTabIndent: true,
|
enableTabIndent: true,
|
||||||
tabSize: DEFAULT_TAB_SIZE,
|
tabSize: DEFAULT_TAB_SIZE,
|
||||||
tabType: 'spaces'
|
tabType: TabType.TabTypeSpaces
|
||||||
});
|
}));
|
||||||
|
|
||||||
|
// 配置是否已从后端加载
|
||||||
|
const configLoaded = ref(false);
|
||||||
|
|
||||||
|
// 从后端加载配置
|
||||||
|
async function loadConfigFromBackend() {
|
||||||
|
try {
|
||||||
|
const editorConfig = await GetEditorConfig();
|
||||||
|
config.value = editorConfig;
|
||||||
|
configLoaded.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load configuration:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用防抖保存配置到后端
|
||||||
|
const saveConfigToBackend = useDebounceFn(async () => {
|
||||||
|
try {
|
||||||
|
await UpdateEditorConfig(config.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to save configuration:', error);
|
||||||
|
}
|
||||||
|
}, 500); // 500ms防抖
|
||||||
|
|
||||||
|
// 监听配置变化,自动保存到后端
|
||||||
|
watch(() => config.value, async () => {
|
||||||
|
if (configLoaded.value) {
|
||||||
|
await saveConfigToBackend();
|
||||||
|
}
|
||||||
|
}, {deep: true});
|
||||||
|
|
||||||
// 字体缩放
|
// 字体缩放
|
||||||
function increaseFontSize() {
|
function increaseFontSize() {
|
||||||
@@ -67,18 +103,20 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
|
|
||||||
// 切换Tab类型(空格或制表符)
|
// 切换Tab类型(空格或制表符)
|
||||||
function toggleTabType() {
|
function toggleTabType() {
|
||||||
config.value.tabType = config.value.tabType === 'spaces' ? 'tab' : 'spaces';
|
config.value.tabType = config.value.tabType === TabType.TabTypeSpaces
|
||||||
|
? TabType.TabTypeTab
|
||||||
|
: TabType.TabTypeSpaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置按钮操作
|
// 重置为默认配置
|
||||||
function openSettings() {
|
async function resetToDefaults() {
|
||||||
console.log('打开设置面板');
|
await ResetToDefault();
|
||||||
// 此处可以实现设置面板的逻辑
|
await loadConfigFromBackend();
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
// 状态
|
// 状态
|
||||||
config,
|
config,
|
||||||
|
configLoaded,
|
||||||
|
|
||||||
// 常量
|
// 常量
|
||||||
MIN_FONT_SIZE,
|
MIN_FONT_SIZE,
|
||||||
@@ -88,19 +126,16 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
MAX_TAB_SIZE,
|
MAX_TAB_SIZE,
|
||||||
|
|
||||||
// 方法
|
// 方法
|
||||||
|
loadConfigFromBackend,
|
||||||
|
saveConfigToBackend,
|
||||||
setEncoding,
|
setEncoding,
|
||||||
openSettings,
|
|
||||||
increaseFontSize,
|
increaseFontSize,
|
||||||
decreaseFontSize,
|
decreaseFontSize,
|
||||||
resetFontSize,
|
resetFontSize,
|
||||||
toggleTabIndent,
|
toggleTabIndent,
|
||||||
increaseTabSize,
|
increaseTabSize,
|
||||||
decreaseTabSize,
|
decreaseTabSize,
|
||||||
toggleTabType
|
toggleTabType,
|
||||||
|
resetToDefaults
|
||||||
};
|
};
|
||||||
}, {
|
|
||||||
persist: {
|
|
||||||
key: 'editor-config',
|
|
||||||
storage: localStorage
|
|
||||||
}
|
|
||||||
});
|
});
|
11
frontend/src/types/config.d.ts
vendored
11
frontend/src/types/config.d.ts
vendored
@@ -1,11 +0,0 @@
|
|||||||
// Tab类型
|
|
||||||
export type TabType = 'spaces' | 'tab';
|
|
||||||
|
|
||||||
// 编辑器配置接口
|
|
||||||
export interface EditorConfig {
|
|
||||||
fontSize: number;
|
|
||||||
encoding: string;
|
|
||||||
enableTabIndent: boolean;
|
|
||||||
tabSize: number;
|
|
||||||
tabType: TabType;
|
|
||||||
}
|
|
64
internal/models/config.go
Normal file
64
internal/models/config.go
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TabType 定义了制表符类型
|
||||||
|
type TabType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// TabTypeSpaces 使用空格作为制表符
|
||||||
|
TabTypeSpaces TabType = "spaces"
|
||||||
|
// TabTypeTab 使用Tab作为制表符
|
||||||
|
TabTypeTab TabType = "tab"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathConfig 定义配置文件路径相关配置
|
||||||
|
type PathConfig struct {
|
||||||
|
RootDir string `json:"rootDir"` // 根目录
|
||||||
|
ConfigPath string `json:"configPath"` // 配置文件路径
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppConfig 应用配置
|
||||||
|
type AppConfig struct {
|
||||||
|
Editor EditorConfig `json:"editor"` // 编辑器配置
|
||||||
|
Paths PathConfig `json:"paths"` // 路径配置
|
||||||
|
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigMetadata 配置元数据
|
||||||
|
type ConfigMetadata struct {
|
||||||
|
Version string `json:"version"` // 配置版本
|
||||||
|
LastUpdated time.Time `json:"lastUpdated"` // 最后更新时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultAppConfig 创建默认应用配置
|
||||||
|
func NewDefaultAppConfig() *AppConfig {
|
||||||
|
return &AppConfig{
|
||||||
|
Editor: EditorConfig{
|
||||||
|
FontSize: 13,
|
||||||
|
Encoding: "UTF-8",
|
||||||
|
EnableTabIndent: true,
|
||||||
|
TabSize: 4,
|
||||||
|
TabType: TabTypeSpaces,
|
||||||
|
},
|
||||||
|
Paths: PathConfig{
|
||||||
|
RootDir: ".voidraft",
|
||||||
|
ConfigPath: "config/config.json",
|
||||||
|
},
|
||||||
|
Metadata: ConfigMetadata{
|
||||||
|
Version: "1.0.0",
|
||||||
|
LastUpdated: time.Now(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
298
internal/services/config_service.go
Normal file
298
internal/services/config_service.go
Normal file
@@ -0,0 +1,298 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"voidraft/internal/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigService 提供配置管理功能
|
||||||
|
type ConfigService struct {
|
||||||
|
fileService *FileService
|
||||||
|
configPath string
|
||||||
|
rootDir string
|
||||||
|
homePath string
|
||||||
|
mutex sync.RWMutex
|
||||||
|
config *models.AppConfig // 缓存最新配置
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfigService 创建新的配置服务实例
|
||||||
|
func NewConfigService(fileService *FileService) *ConfigService {
|
||||||
|
// 获取用户主目录
|
||||||
|
homePath, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to get user home directory: %v", 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, // 初始化缓存配置
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化配置目录和文件(非锁定方式)
|
||||||
|
service.initializeConfig()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保配置文件所在目录存在
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查配置文件是否存在
|
||||||
|
configFilePath := cs.GetFullConfigPath()
|
||||||
|
log.Printf("Config file path: %s", configFilePath)
|
||||||
|
|
||||||
|
if !cs.fileService.FileExists(configFilePath) {
|
||||||
|
log.Printf("Config file not found, creating default config")
|
||||||
|
// 创建默认配置文件
|
||||||
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
|
if err := cs.saveAppConfigInitial(defaultConfig); err != nil {
|
||||||
|
log.Printf("Failed to save default config: %v", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 加载现有配置
|
||||||
|
log.Printf("Loading existing config file")
|
||||||
|
existingConfig, err := cs.loadAppConfigInitial()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to load existing config, using default: %v", err)
|
||||||
|
} else {
|
||||||
|
cs.config = existingConfig
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// saveAppConfigInitial 初始化时保存配置,不使用互斥锁
|
||||||
|
func (cs *ConfigService) saveAppConfigInitial(config *models.AppConfig) error {
|
||||||
|
// 更新配置元数据
|
||||||
|
config.Metadata.LastUpdated = time.Now()
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
log.Printf("saveAppConfigInitial: Saving to %s", configPath)
|
||||||
|
|
||||||
|
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
|
||||||
|
return fmt.Errorf("failed to save initial config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = config
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAppConfigInitial 初始化时加载配置,不使用互斥锁
|
||||||
|
func (cs *ConfigService) loadAppConfigInitial() (*models.AppConfig, error) {
|
||||||
|
config := &models.AppConfig{}
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
|
||||||
|
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load initial config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initConfigDir 确保目录存在,如不存在则创建
|
||||||
|
func (cs *ConfigService) initConfigDir() error {
|
||||||
|
configDir := filepath.Join(cs.homePath, cs.rootDir)
|
||||||
|
return cs.fileService.EnsureDir(configDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFullConfigPath 获取完整的配置文件路径
|
||||||
|
func (cs *ConfigService) GetFullConfigPath() string {
|
||||||
|
return filepath.Join(cs.homePath, cs.rootDir, cs.configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAppConfig 获取应用配置
|
||||||
|
func (cs *ConfigService) GetAppConfig() (*models.AppConfig, error) {
|
||||||
|
cs.mutex.RLock()
|
||||||
|
defer cs.mutex.RUnlock()
|
||||||
|
|
||||||
|
// 返回内存中的配置副本
|
||||||
|
if cs.config != nil {
|
||||||
|
return cs.config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从文件加载
|
||||||
|
config := &models.AppConfig{}
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
|
||||||
|
log.Printf("GetAppConfig: Loading from %s", configPath)
|
||||||
|
|
||||||
|
// 如果配置文件存在,则加载
|
||||||
|
if cs.fileService.FileExists(configPath) {
|
||||||
|
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
|
||||||
|
log.Printf("GetAppConfig: Failed to load config: %v", err)
|
||||||
|
return nil, fmt.Errorf("failed to load config file: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("GetAppConfig: Successfully loaded config")
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = config
|
||||||
|
} else {
|
||||||
|
// 文件不存在,使用默认配置
|
||||||
|
log.Printf("GetAppConfig: Config file not found, using default")
|
||||||
|
config = models.NewDefaultAppConfig()
|
||||||
|
|
||||||
|
// 保存默认配置到文件
|
||||||
|
if err := cs.SaveAppConfig(config); err != nil {
|
||||||
|
log.Printf("GetAppConfig: Failed to save default config: %v", err)
|
||||||
|
return nil, fmt.Errorf("failed to save default config: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAppConfig 保存应用配置
|
||||||
|
func (cs *ConfigService) SaveAppConfig(config *models.AppConfig) error {
|
||||||
|
cs.mutex.Lock()
|
||||||
|
defer cs.mutex.Unlock()
|
||||||
|
|
||||||
|
// 更新配置元数据
|
||||||
|
config.Metadata.LastUpdated = time.Now()
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
log.Printf("SaveAppConfig: Saving to %s", configPath)
|
||||||
|
|
||||||
|
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
|
||||||
|
log.Printf("SaveAppConfig: Failed to save config: %v", err)
|
||||||
|
return fmt.Errorf("failed to save config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = config
|
||||||
|
log.Printf("SaveAppConfig: Successfully saved config")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateEditorConfig 更新编辑器配置
|
||||||
|
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
|
||||||
|
cs.mutex.Lock()
|
||||||
|
defer cs.mutex.Unlock()
|
||||||
|
|
||||||
|
// 如果内存中已有配置,直接更新
|
||||||
|
if cs.config != nil {
|
||||||
|
log.Printf("UpdateEditorConfig: Updating in-memory editor config: %+v", editorConfig)
|
||||||
|
cs.config.Editor = editorConfig
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
if err := cs.fileService.SaveJSON(configPath, cs.config); err != nil {
|
||||||
|
log.Printf("UpdateEditorConfig: Failed to save config: %v", err)
|
||||||
|
return fmt.Errorf("failed to save config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("UpdateEditorConfig: Successfully saved updated config")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有内存中的配置,需要先加载
|
||||||
|
config, err := cs.loadAppConfigInitial()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("UpdateEditorConfig: Failed to load config: %v", err)
|
||||||
|
// 使用默认配置
|
||||||
|
config = models.NewDefaultAppConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新编辑器配置
|
||||||
|
config.Editor = editorConfig
|
||||||
|
|
||||||
|
// 更新配置元数据
|
||||||
|
config.Metadata.LastUpdated = time.Now()
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
|
||||||
|
log.Printf("UpdateEditorConfig: Failed to save config: %v", err)
|
||||||
|
return fmt.Errorf("failed to save config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = config
|
||||||
|
log.Printf("UpdateEditorConfig: Successfully saved config with updated editor settings")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEditorConfig 获取编辑器配置
|
||||||
|
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
|
||||||
|
cs.mutex.RLock()
|
||||||
|
defer cs.mutex.RUnlock()
|
||||||
|
|
||||||
|
// 如果内存中已有配置,直接返回
|
||||||
|
if cs.config != nil {
|
||||||
|
return cs.config.Editor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 否则从文件加载
|
||||||
|
config, err := cs.loadAppConfigInitial()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("GetEditorConfig: Failed to load config: %v", err)
|
||||||
|
// 使用默认配置
|
||||||
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
|
cs.config = defaultConfig
|
||||||
|
return defaultConfig.Editor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = config
|
||||||
|
log.Printf("GetEditorConfig: Retrieved editor config: %+v", config.Editor)
|
||||||
|
return config.Editor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetToDefault 重置为默认配置
|
||||||
|
func (cs *ConfigService) ResetToDefault() error {
|
||||||
|
cs.mutex.Lock()
|
||||||
|
defer cs.mutex.Unlock()
|
||||||
|
|
||||||
|
// 创建默认配置
|
||||||
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
|
|
||||||
|
log.Printf("ResetToDefault: Resetting to default config")
|
||||||
|
|
||||||
|
// 保存到文件
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
if err := cs.fileService.SaveJSON(configPath, defaultConfig); err != nil {
|
||||||
|
log.Printf("ResetToDefault: Failed to save default config: %v", err)
|
||||||
|
return fmt.Errorf("failed to save default config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = defaultConfig
|
||||||
|
log.Printf("ResetToDefault: Successfully reset to default config")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
146
internal/services/file_service.go
Normal file
146
internal/services/file_service.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FileService 提供原子化文件操作
|
||||||
|
type FileService struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFileService 创建新的文件服务实例
|
||||||
|
func NewFileService() *FileService {
|
||||||
|
return &FileService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnsureDir 确保目录存在,如不存在则创建
|
||||||
|
func (fs *FileService) EnsureDir(dirPath string) error {
|
||||||
|
fs.mutex.Lock()
|
||||||
|
defer fs.mutex.Unlock()
|
||||||
|
|
||||||
|
return fs.ensureDirNoLock(dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureDirNoLock 无锁版本的EnsureDir,仅供内部使用
|
||||||
|
func (fs *FileService) ensureDirNoLock(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveJSON 原子化保存JSON数据到文件
|
||||||
|
func (fs *FileService) SaveJSON(filePath string, data interface{}) error {
|
||||||
|
fs.mutex.Lock()
|
||||||
|
defer fs.mutex.Unlock()
|
||||||
|
|
||||||
|
log.Printf("SaveJSON: Saving to file: %s", filePath)
|
||||||
|
|
||||||
|
// 确保目录存在 - 使用无锁版本以避免死锁
|
||||||
|
dir := filepath.Dir(filePath)
|
||||||
|
if err := fs.ensureDirNoLock(dir); err != nil {
|
||||||
|
log.Printf("SaveJSON: 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("SaveJSON: Failed to encode JSON: %v", err)
|
||||||
|
return fmt.Errorf("failed to encode JSON: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 先写入临时文件
|
||||||
|
tempFile := filePath + ".tmp"
|
||||||
|
log.Printf("SaveJSON: Writing to temporary file: %s", tempFile)
|
||||||
|
if err := os.WriteFile(tempFile, jsonData, 0644); err != nil {
|
||||||
|
log.Printf("SaveJSON: Failed to write temporary file: %v", err)
|
||||||
|
return fmt.Errorf("failed to write temporary file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 原子替换原文件
|
||||||
|
log.Printf("SaveJSON: Replacing original file with temporary file")
|
||||||
|
if err := os.Rename(tempFile, filePath); err != nil {
|
||||||
|
os.Remove(tempFile) // 清理临时文件
|
||||||
|
log.Printf("SaveJSON: Failed to replace file: %v", err)
|
||||||
|
return fmt.Errorf("failed to replace file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("SaveJSON: File saved successfully: %s", filePath)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadJSON 从文件加载JSON数据
|
||||||
|
func (fs *FileService) LoadJSON(filePath string, target interface{}) error {
|
||||||
|
fs.mutex.Lock()
|
||||||
|
defer fs.mutex.Unlock()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
fs.mutex.Lock()
|
||||||
|
defer fs.mutex.Unlock()
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
43
internal/services/service_manager.go
Normal file
43
internal/services/service_manager.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ServiceManager 服务管理器,负责协调各个服务
|
||||||
|
type ServiceManager struct {
|
||||||
|
fileService *FileService
|
||||||
|
configService *ConfigService
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServiceManager 创建新的服务管理器实例
|
||||||
|
func NewServiceManager() *ServiceManager {
|
||||||
|
// 初始化文件服务
|
||||||
|
fileService := NewFileService()
|
||||||
|
|
||||||
|
// 初始化配置服务
|
||||||
|
configService := NewConfigService(fileService)
|
||||||
|
|
||||||
|
return &ServiceManager{
|
||||||
|
fileService: fileService,
|
||||||
|
configService: configService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
10
main.go
10
main.go
@@ -5,6 +5,7 @@ import (
|
|||||||
_ "embed"
|
_ "embed"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
"voidraft/internal/services"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
)
|
)
|
||||||
@@ -21,16 +22,17 @@ var assets embed.FS
|
|||||||
// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and
|
// and starts a goroutine that emits a time-based event every second. It subsequently runs the application and
|
||||||
// logs any error that might occur.
|
// logs any error that might occur.
|
||||||
func main() {
|
func main() {
|
||||||
|
serviceManager := services.NewServiceManager()
|
||||||
// Create a new Wails application by providing the necessary options.
|
// Create a new Wails application by providing the necessary options.
|
||||||
// Variables 'Name' and 'Description' are for application metadata.
|
// Variables 'Name' and 'Description' are for application metadata.
|
||||||
// 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
|
// 'Assets' configures the asset server with the 'FS' variable pointing to the frontend files.
|
||||||
// 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances.
|
// 'Bind' is a list of Go struct instances. The frontend has access to the methods of these instances.
|
||||||
// 'Mac' options tailor the application when running an macOS.
|
// 'Mac' options tailor the application when running an macOS.
|
||||||
|
log.Println("Creating Wails application...")
|
||||||
app := application.New(application.Options{
|
app := application.New(application.Options{
|
||||||
Name: "voidraft",
|
Name: "voidraft",
|
||||||
Description: "voidraft",
|
Description: "voidraft",
|
||||||
Services: []application.Service{},
|
Services: serviceManager.GetServices(),
|
||||||
Assets: application.AssetOptions{
|
Assets: application.AssetOptions{
|
||||||
Handler: application.AssetFileServerFS(assets),
|
Handler: application.AssetFileServerFS(assets),
|
||||||
},
|
},
|
||||||
@@ -44,6 +46,7 @@ func main() {
|
|||||||
// 'Mac' options tailor the window when running on macOS.
|
// 'Mac' options tailor the window when running on macOS.
|
||||||
// 'BackgroundColour' is the background colour of the window.
|
// 'BackgroundColour' is the background colour of the window.
|
||||||
// 'URL' is the URL that will be loaded into the webview.
|
// 'URL' is the URL that will be loaded into the webview.
|
||||||
|
log.Println("Creating main window...")
|
||||||
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||||
Title: "voidraft",
|
Title: "voidraft",
|
||||||
Width: 700,
|
Width: 700,
|
||||||
@@ -68,10 +71,13 @@ func main() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Run the application. This blocks until the application has been exited.
|
// Run the application. This blocks until the application has been exited.
|
||||||
|
log.Println("Starting application event loop...")
|
||||||
err := app.Run()
|
err := app.Run()
|
||||||
|
|
||||||
// If an error occurred while running the application, log it and exit.
|
// If an error occurred while running the application, log it and exit.
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Println("Application exiting...")
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user