Add configuration information file storage

This commit is contained in:
2025-04-27 18:04:08 +08:00
parent 946075f25d
commit 3ab209f899
20 changed files with 1106 additions and 58 deletions

View 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";

View 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;

View 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";

View 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;

View File

@@ -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;

View 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;
}

View 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
};

View File

@@ -40,6 +40,7 @@
"@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",
@@ -2156,6 +2157,12 @@
"integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==",
"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": {
"version": "8.31.0",
"resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.0.tgz",
@@ -2600,6 +2607,44 @@
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
"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": {
"version": "3.0.0-alpha.66",
"resolved": "https://registry.npmmirror.com/@wailsio/runtime/-/runtime-3.0.0-alpha.66.tgz",

View File

@@ -44,6 +44,7 @@
"@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",

View File

@@ -1,14 +1,21 @@
<script setup lang="ts">
import {onMounted} from 'vue';
import Editor from '@/editor/Editor.vue';
import Toolbar from '@/components/toolbar/Toolbar.vue';
import {useConfigStore} from "@/stores/configStore";
const configStore = useConfigStore();
onMounted(async () => {
await configStore.loadConfigFromBackend();
})
</script>
<template>
<div class="app-container">
<div class="editor-wrapper">
<Editor />
<Editor/>
</div>
<Toolbar />
<Toolbar/>
</div>
</template>
@@ -20,7 +27,7 @@ import Toolbar from '@/components/toolbar/Toolbar.vue';
padding: 0;
display: flex;
flex-direction: column;
.editor-wrapper {
flex: 1;
overflow: hidden;

View File

@@ -38,7 +38,7 @@ const configStore = useConfigStore();
</span>
</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"
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="3"></circle>

View File

@@ -7,10 +7,10 @@ import {useConfigStore} from '@/stores/configStore';
import {createBasicSetup} from './extensions/basicSetup';
import {
createStatsUpdateExtension,
getTabExtensions,
updateTabConfig,
createWheelZoomHandler,
updateStats
getTabExtensions,
updateStats,
updateTabConfig
} from './extensions';
const editorStore = useEditorStore();
@@ -19,7 +19,7 @@ const configStore = useConfigStore();
const props = defineProps({
initialDoc: {
type: String,
default: '// 在此处编写代码'
default: '// 在此处编写文本...'
}
});
@@ -31,17 +31,17 @@ const createEditor = () => {
// 获取基本扩展
const basicExtensions = createBasicSetup();
// 获取Tab相关扩展
const tabExtensions = getTabExtensions(
configStore.config.tabSize,
configStore.config.enableTabIndent,
configStore.config.tabType
configStore.config.tabSize,
configStore.config.enableTabIndent,
configStore.config.tabType
);
// 创建统计信息更新扩展
const statsExtension = createStatsUpdateExtension(
editorStore.updateDocumentStats
editorStore.updateDocumentStats
);
// 组合所有扩展
@@ -62,31 +62,31 @@ const createEditor = () => {
state,
parent: editorElement.value
});
// 将编辑器实例保存到store
editorStore.setEditorView(view);
// 应用初始字体大小
editorStore.applyFontSize();
// 立即更新统计信息,不等待用户交互
updateStats(view, editorStore.updateDocumentStats);
};
// 创建滚轮事件处理器
const handleWheel = createWheelZoomHandler(
configStore.increaseFontSize,
configStore.decreaseFontSize
configStore.increaseFontSize,
configStore.decreaseFontSize
);
// 重新配置编辑器(仅在必要时)
const reconfigureTabSettings = () => {
if (!editorStore.editorView) return;
updateTabConfig(
editorStore.editorView as EditorView,
configStore.config.tabSize,
configStore.config.enableTabIndent,
configStore.config.tabType
editorStore.editorView as EditorView,
configStore.config.tabSize,
configStore.config.enableTabIndent,
configStore.config.tabType
);
};
@@ -103,12 +103,12 @@ watch(() => configStore.config.fontSize, () => {
onMounted(() => {
// 创建编辑器
createEditor();
// 添加滚轮事件监听
if (editorElement.value) {
editorElement.value.addEventListener('wheel', handleWheel, {passive: false});
}
// 确保统计信息已更新
if (editorStore.editorView) {
setTimeout(() => {
@@ -122,7 +122,7 @@ onBeforeUnmount(() => {
if (editorElement.value) {
editorElement.value.removeEventListener('wheel', handleWheel);
}
// 销毁编辑器
if (editorStore.editorView) {
editorStore.editorView.destroy();

View File

@@ -2,7 +2,7 @@ import {Compartment, Extension} from '@codemirror/state';
import {EditorView, keymap} from '@codemirror/view';
import {indentSelection} from '@codemirror/commands';
import {indentUnit} from '@codemirror/language';
import {TabType} from "@/types/config";
import {TabType} from '@/../bindings/voidraft/internal/models/models';
// Tab设置相关的compartment
export const tabSizeCompartment = new Compartment();

View File

@@ -1,6 +1,12 @@
import {defineStore} from 'pinia';
import {ref} from 'vue';
import {EditorConfig} from '@/types/config';
import {ref, watch} from 'vue';
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;
@@ -14,13 +20,43 @@ const MAX_TAB_SIZE = 8;
export const useConfigStore = defineStore('config', () => {
// 配置状态
const config = ref<EditorConfig>({
const config = ref<EditorConfig>(new EditorConfig({
fontSize: DEFAULT_FONT_SIZE,
encoding: 'UTF-8',
enableTabIndent: true,
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() {
@@ -67,18 +103,20 @@ export const useConfigStore = defineStore('config', () => {
// 切换Tab类型空格或制表符
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() {
console.log('打开设置面板');
// 此处可以实现设置面板的逻辑
// 重置为默认配置
async function resetToDefaults() {
await ResetToDefault();
await loadConfigFromBackend();
}
return {
// 状态
config,
configLoaded,
// 常量
MIN_FONT_SIZE,
@@ -88,19 +126,16 @@ export const useConfigStore = defineStore('config', () => {
MAX_TAB_SIZE,
// 方法
loadConfigFromBackend,
saveConfigToBackend,
setEncoding,
openSettings,
increaseFontSize,
decreaseFontSize,
resetFontSize,
toggleTabIndent,
increaseTabSize,
decreaseTabSize,
toggleTabType
toggleTabType,
resetToDefaults
};
}, {
persist: {
key: 'editor-config',
storage: localStorage
}
});

View File

@@ -1,11 +0,0 @@
// Tab类型
export type TabType = 'spaces' | 'tab';
// 编辑器配置接口
export interface EditorConfig {
fontSize: number;
encoding: string;
enableTabIndent: boolean;
tabSize: number;
tabType: TabType;
}