♻️ Refactor code
This commit is contained in:
1 parent
44f7baad10
commit
a516b8973e
53 files changed
+1487
-1068
No files matched your search
@@ -3,9 +3,11 @@
|
||||
|
||||
import * as ConfigService from "./configservice.js";
|
||||
import * as DocumentService from "./documentservice.js";
|
||||
import * as SystemService from "./systemservice.js";
|
||||
export {
|
||||
ConfigService,
|
||||
DocumentService
|
||||
DocumentService,
|
||||
SystemService
|
||||
};
|
||||
|
||||
export * from "./models.js";
|
||||
@@ -136,6 +136,73 @@ export enum EditType {
|
||||
EditEqual = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* MemoryStats 内存统计信息
|
||||
*/
|
||||
export class MemoryStats {
|
||||
/**
|
||||
* 当前堆内存使用量(字节)
|
||||
*/
|
||||
"heapInUse": number;
|
||||
|
||||
/**
|
||||
* 堆内存分配总量(字节)
|
||||
*/
|
||||
"heapAlloc": number;
|
||||
|
||||
/**
|
||||
* 系统内存使用量(字节)
|
||||
*/
|
||||
"sys": number;
|
||||
|
||||
/**
|
||||
* GC 次数
|
||||
*/
|
||||
"numGC": number;
|
||||
|
||||
/**
|
||||
* GC 暂停时间(纳秒)
|
||||
*/
|
||||
"pauseTotalNs": number;
|
||||
|
||||
/**
|
||||
* Goroutine 数量
|
||||
*/
|
||||
"numGoroutine": number;
|
||||
|
||||
/** Creates a new MemoryStats instance. */
|
||||
constructor($$source: Partial<MemoryStats> = {}) {
|
||||
if (!("heapInUse" in $$source)) {
|
||||
this["heapInUse"] = 0;
|
||||
}
|
||||
if (!("heapAlloc" in $$source)) {
|
||||
this["heapAlloc"] = 0;
|
||||
}
|
||||
if (!("sys" in $$source)) {
|
||||
this["sys"] = 0;
|
||||
}
|
||||
if (!("numGC" in $$source)) {
|
||||
this["numGC"] = 0;
|
||||
}
|
||||
if (!("pauseTotalNs" in $$source)) {
|
||||
this["pauseTotalNs"] = 0;
|
||||
}
|
||||
if (!("numGoroutine" in $$source)) {
|
||||
this["numGoroutine"] = 0;
|
||||
}
|
||||
|
||||
Object.assign(this, $$source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new MemoryStats instance from a string or object.
|
||||
*/
|
||||
static createFrom($$source: any = {}): MemoryStats {
|
||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||
return new MemoryStats($$parsedSource as Partial<MemoryStats>);
|
||||
}
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = Edit.createFrom;
|
||||
const $$createType1 = $Create.Array($$createType0);
|
||||
@@ -0,0 +1,46 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
/**
|
||||
* SystemService 系统监控服务
|
||||
* @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 from "./models.js";
|
||||
|
||||
/**
|
||||
* FormatBytes 格式化字节数为人类可读的格式
|
||||
*/
|
||||
export function FormatBytes(bytes: number): Promise<string> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1368998019, bytes) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* GetMemoryStats 获取当前内存统计信息
|
||||
*/
|
||||
export function GetMemoryStats(): Promise<$models.MemoryStats> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(1678201009) as any;
|
||||
let $typingPromise = $resultPromise.then(($result: any) => {
|
||||
return $$createType0($result);
|
||||
}) as any;
|
||||
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
|
||||
return $typingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* TriggerGC 手动触发垃圾回收
|
||||
*/
|
||||
export function TriggerGC(): Promise<void> & { cancel(): void } {
|
||||
let $resultPromise = $Call.ByID(741882899) as any;
|
||||
return $resultPromise;
|
||||
}
|
||||
|
||||
// Private type creation functions
|
||||
const $$createType0 = $models.MemoryStats.createFrom;
|
||||
Vendored
+1
@@ -8,6 +8,7 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
MemoryMonitor: typeof import('./src/components/monitor/MemoryMonitor.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
Toolbar: typeof import('./src/components/toolbar/Toolbar.vue')['default']
|
||||
|
||||
Generated
+292
-425
File diff suppressed because it is too large.
Load diff
+13
-12
@@ -29,7 +29,7 @@
|
||||
"@codemirror/lang-python": "^6.2.1",
|
||||
"@codemirror/lang-rust": "^6.0.1",
|
||||
"@codemirror/lang-sass": "^6.0.2",
|
||||
"@codemirror/lang-sql": "^6.8.0",
|
||||
"@codemirror/lang-sql": "^6.9.0",
|
||||
"@codemirror/lang-vue": "^0.1.3",
|
||||
"@codemirror/lang-wast": "^6.0.2",
|
||||
"@codemirror/lang-xml": "^6.1.0",
|
||||
@@ -40,29 +40,30 @@
|
||||
"@codemirror/lint": "^6.8.5",
|
||||
"@codemirror/search": "^6.5.11",
|
||||
"@codemirror/state": "^6.5.2",
|
||||
"@codemirror/view": "^6.36.8",
|
||||
"@codemirror/view": "^6.37.1",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vueuse/core": "^13.2.0",
|
||||
"@vueuse/core": "^13.3.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"pinia": "^3.0.2",
|
||||
"sass": "^1.89.0",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"sass": "^1.89.1",
|
||||
"uuid": "^11.1.0",
|
||||
"vue": "^3.5.14",
|
||||
"vue-i18n": "^11.1.3",
|
||||
"vue": "^3.5.16",
|
||||
"vue-i18n": "^11.1.5",
|
||||
"vue-router": "^4.5.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.27.0",
|
||||
"@types/node": "^22.15.18",
|
||||
"@eslint/js": "^9.28.0",
|
||||
"@types/node": "^22.15.29",
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"@wailsio/runtime": "latest",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint": "^9.28.0",
|
||||
"eslint-plugin-vue": "^10.1.0",
|
||||
"globals": "^16.1.0",
|
||||
"globals": "^16.2.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"unplugin-vue-components": "^28.5.0",
|
||||
"typescript-eslint": "^8.33.0",
|
||||
"unplugin-vue-components": "^28.7.0",
|
||||
"vite": "^6.3.5",
|
||||
"vue-eslint-parser": "^10.1.3",
|
||||
"vue-tsc": "^2.2.10"
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
|
||||
const configStore = useConfigStore();
|
||||
|
||||
|
||||
// 应用启动时加载配置
|
||||
onMounted(async () => {
|
||||
await configStore.initializeLanguage();
|
||||
await configStore.initConfig();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
|
||||
import { SystemService } from '@/../bindings/voidraft/internal/services';
|
||||
import type { MemoryStats } from '@/../bindings/voidraft/internal/services';
|
||||
|
||||
const memoryStats = ref<MemoryStats | null>(null);
|
||||
const formattedMemory = ref('');
|
||||
const isLoading = ref(true);
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null);
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
// 存储历史数据点 (最近60个数据点,每3秒一个点,总共3分钟历史)
|
||||
const historyData = ref<number[]>([]);
|
||||
const maxDataPoints = 60;
|
||||
|
||||
// 获取内存统计信息
|
||||
const fetchMemoryStats = async () => {
|
||||
try {
|
||||
const stats = await SystemService.GetMemoryStats();
|
||||
memoryStats.value = stats;
|
||||
|
||||
// 格式化内存显示 - 主要显示堆内存使用量
|
||||
const heapMB = (stats.heapInUse / 1024 / 1024);
|
||||
if (heapMB < 1) {
|
||||
formattedMemory.value = `${(heapMB * 1024).toFixed(0)}K`;
|
||||
} else if (heapMB < 100) {
|
||||
formattedMemory.value = `${heapMB.toFixed(1)}M`;
|
||||
} else {
|
||||
formattedMemory.value = `${heapMB.toFixed(0)}M`;
|
||||
}
|
||||
|
||||
// 添加新数据点到历史记录
|
||||
const memoryUsagePercent = Math.min((stats.heapInUse / (100 * 1024 * 1024)) * 100, 100);
|
||||
historyData.value.push(memoryUsagePercent);
|
||||
|
||||
// 保持最大数据点数量
|
||||
if (historyData.value.length > maxDataPoints) {
|
||||
historyData.value.shift();
|
||||
}
|
||||
|
||||
// 更新图表
|
||||
drawChart();
|
||||
|
||||
isLoading.value = false;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch memory stats:', error);
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 绘制实时曲线图
|
||||
const drawChart = () => {
|
||||
if (!canvasRef.value || historyData.value.length === 0) return;
|
||||
|
||||
const canvas = canvasRef.value;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
// 设置canvas尺寸
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * window.devicePixelRatio;
|
||||
canvas.height = rect.height * window.devicePixelRatio;
|
||||
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
||||
|
||||
const width = rect.width;
|
||||
const height = rect.height;
|
||||
|
||||
// 清除画布
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
|
||||
// 绘制背景网格 - 朦胧的网格,从上到下逐渐清晰
|
||||
for (let i = 0; i <= 6; i++) {
|
||||
const y = (height / 6) * i;
|
||||
const opacity = 0.01 + (i / 6) * 0.03; // 从上到下逐渐清晰
|
||||
ctx.strokeStyle = `rgba(255, 255, 255, ${opacity})`;
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, y);
|
||||
ctx.lineTo(width, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
// 垂直网格线
|
||||
for (let i = 0; i <= 8; i++) {
|
||||
const x = (width / 8) * i;
|
||||
ctx.strokeStyle = 'rgba(255, 255, 255, 0.02)';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x, 0);
|
||||
ctx.lineTo(x, height);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
if (historyData.value.length < 2) return;
|
||||
|
||||
// 计算数据点位置
|
||||
const dataLength = historyData.value.length;
|
||||
const stepX = width / (maxDataPoints - 1);
|
||||
const startX = width - (dataLength - 1) * stepX;
|
||||
|
||||
// 绘制填充区域 - 从上朦胧到下清晰的渐变
|
||||
const gradient = ctx.createLinearGradient(0, 0, 0, height);
|
||||
gradient.addColorStop(0, 'rgba(74, 158, 255, 0.1)'); // 顶部很淡
|
||||
gradient.addColorStop(0.3, 'rgba(74, 158, 255, 0.15)');
|
||||
gradient.addColorStop(0.7, 'rgba(74, 158, 255, 0.25)');
|
||||
gradient.addColorStop(1, 'rgba(74, 158, 255, 0.4)'); // 底部较浓
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(startX, height);
|
||||
|
||||
// 移动到第一个数据点
|
||||
const firstY = height - (historyData.value[0] / 100) * height;
|
||||
ctx.lineTo(startX, firstY);
|
||||
|
||||
// 使用二次贝塞尔曲线平滑曲线
|
||||
for (let i = 1; i < dataLength; i++) {
|
||||
const x = startX + i * stepX;
|
||||
const y = height - (historyData.value[i] / 100) * height;
|
||||
|
||||
if (i < dataLength - 1) {
|
||||
const nextX = startX + (i + 1) * stepX;
|
||||
const nextY = height - (historyData.value[i + 1] / 100) * height;
|
||||
const controlX = x + stepX / 2;
|
||||
const controlY = y;
|
||||
ctx.quadraticCurveTo(controlX, controlY, (x + nextX) / 2, (y + nextY) / 2);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// 完成填充路径
|
||||
const lastX = startX + (dataLength - 1) * stepX;
|
||||
ctx.lineTo(lastX, height);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fill();
|
||||
|
||||
// 绘制主曲线 - 从上到下逐渐清晰
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
|
||||
// 分段绘制曲线,每段有不同的透明度
|
||||
const segments = 10;
|
||||
for (let seg = 0; seg < segments; seg++) {
|
||||
const segmentStart = seg / segments;
|
||||
const segmentEnd = (seg + 1) / segments;
|
||||
const opacity = 0.3 + (seg / segments) * 0.7; // 从上0.3到下1.0
|
||||
|
||||
ctx.strokeStyle = `rgba(74, 158, 255, ${opacity})`;
|
||||
ctx.lineWidth = 1.5 + (seg / segments) * 0.8; // 线条也从细到粗
|
||||
|
||||
ctx.beginPath();
|
||||
let segmentStarted = false;
|
||||
|
||||
for (let i = 0; i < dataLength; i++) {
|
||||
const x = startX + i * stepX;
|
||||
const y = height - (historyData.value[i] / 100) * height;
|
||||
const yPercent = 1 - (y / height);
|
||||
|
||||
if (yPercent >= segmentStart && yPercent <= segmentEnd) {
|
||||
if (!segmentStarted) {
|
||||
ctx.moveTo(x, y);
|
||||
segmentStarted = true;
|
||||
} else {
|
||||
if (i < dataLength - 1) {
|
||||
const nextX = startX + (i + 1) * stepX;
|
||||
const nextY = height - (historyData.value[i + 1] / 100) * height;
|
||||
const controlX = x + stepX / 2;
|
||||
const controlY = y;
|
||||
ctx.quadraticCurveTo(controlX, controlY, (x + nextX) / 2, (y + nextY) / 2);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (segmentStarted) {
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
// 绘制当前值的高亮点 - 根据位置调整透明度
|
||||
const lastY = height - (historyData.value[dataLength - 1] / 100) * height;
|
||||
const pointOpacity = 0.4 + (1 - lastY / height) * 0.6;
|
||||
|
||||
// 外圈
|
||||
ctx.fillStyle = `rgba(74, 158, 255, ${pointOpacity * 0.3})`;
|
||||
ctx.beginPath();
|
||||
ctx.arc(lastX, lastY, 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
// 内圈
|
||||
ctx.fillStyle = `rgba(74, 158, 255, ${pointOpacity})`;
|
||||
ctx.beginPath();
|
||||
ctx.arc(lastX, lastY, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
// 手动触发GC
|
||||
const triggerGC = async () => {
|
||||
try {
|
||||
await SystemService.TriggerGC();
|
||||
// 延迟一下再获取新的统计信息
|
||||
setTimeout(fetchMemoryStats, 100);
|
||||
} catch (error) {
|
||||
console.error('Failed to trigger GC:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 处理窗口大小变化
|
||||
const handleResize = () => {
|
||||
if (historyData.value.length > 0) {
|
||||
nextTick(() => drawChart());
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
fetchMemoryStats();
|
||||
// 每3秒更新一次内存信息
|
||||
intervalId = setInterval(fetchMemoryStats, 3000);
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', handleResize);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
window.removeEventListener('resize', handleResize);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="memory-monitor" @click="triggerGC" :title="`内存: ${formattedMemory} | 点击清理内存`">
|
||||
<div class="monitor-info">
|
||||
<div class="memory-label">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/>
|
||||
</svg>
|
||||
<span>内存</span>
|
||||
</div>
|
||||
<div class="memory-value" v-if="!isLoading">{{ formattedMemory }}</div>
|
||||
<div class="memory-loading" v-else>--</div>
|
||||
</div>
|
||||
<div class="chart-area">
|
||||
<canvas
|
||||
ref="canvasRef"
|
||||
class="memory-chart"
|
||||
:class="{ 'loading': isLoading }"
|
||||
></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.memory-monitor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
.monitor-info {
|
||||
.memory-label {
|
||||
color: #4a9eff;
|
||||
}
|
||||
|
||||
.memory-value {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-area .memory-chart {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.monitor-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.memory-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: #a0a0a0;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
svg {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
span {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.memory-value, .memory-loading {
|
||||
color: #e0e0e0;
|
||||
font-family: 'JetBrains Mono', 'Courier New', monospace;
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.memory-loading {
|
||||
opacity: 0.5;
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.chart-area {
|
||||
height: 48px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
border-radius: 3px;
|
||||
|
||||
.memory-chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.2s ease;
|
||||
|
||||
&.loading {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,32 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import {useEditorStore} from '@/stores/editorStore';
|
||||
import {useConfigStore} from '@/stores/configStore';
|
||||
import {useConfigStore, SUPPORTED_LOCALES, type SupportedLocaleType} from '@/stores/configStore';
|
||||
import {useLogStore} from '@/stores/logStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import {SUPPORTED_LOCALES, setLocale, SupportedLocaleType} from '@/i18n';
|
||||
import {LanguageType} from '@/../bindings/voidraft/internal/models/models';
|
||||
import { ConfigUtils } from '@/utils/configUtils';
|
||||
import * as runtime from '@wailsio/runtime';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const editorStore = useEditorStore();
|
||||
const configStore = useConfigStore();
|
||||
const logStore = useLogStore();
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
const router = useRouter();
|
||||
// 语言下拉菜单
|
||||
const showLanguageMenu = ref(false);
|
||||
|
||||
// 切换语言
|
||||
const changeLanguage = async (localeCode: SupportedLocaleType) => {
|
||||
// 使用工具类转换语言类型
|
||||
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
|
||||
|
||||
try {
|
||||
// 设置后端语言配置
|
||||
await configStore.setLanguage(backendLanguage);
|
||||
// 设置前端语言
|
||||
setLocale(localeCode);
|
||||
// 使用 configStore 的语言设置方法
|
||||
await configStore.setLocale(localeCode);
|
||||
} catch (error) {
|
||||
console.error('Failed to change language:', error);
|
||||
}
|
||||
@@ -44,25 +38,16 @@ const toggleAlwaysOnTop = async () => {
|
||||
try {
|
||||
await configStore.toggleAlwaysOnTop();
|
||||
// 使用Window.SetAlwaysOnTop方法设置窗口置顶状态
|
||||
runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
|
||||
await runtime.Window.SetAlwaysOnTop(configStore.config.alwaysOnTop);
|
||||
} catch (error) {
|
||||
console.error('Failed to set window always on top:', error);
|
||||
logStore.error(t('config.alwaysOnTopFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
// 打开设置窗口
|
||||
const openSettingsWindow = () => {
|
||||
try {
|
||||
// 直接操作窗口对象
|
||||
runtime.Events.Emit({
|
||||
name: "show_settings_window",
|
||||
data: {},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to open settings window:', error);
|
||||
logStore.error('Failed to open settings window');
|
||||
}
|
||||
// 打开设置页面
|
||||
const openSettings = () => {
|
||||
router.push('/settings');
|
||||
};
|
||||
|
||||
// 初始化配置
|
||||
@@ -75,17 +60,11 @@ onMounted(async () => {
|
||||
// 设置窗口置顶状态
|
||||
if (configStore.config.alwaysOnTop) {
|
||||
try {
|
||||
runtime.Window.SetAlwaysOnTop(true);
|
||||
await runtime.Window.SetAlwaysOnTop(true);
|
||||
} catch (error) {
|
||||
console.error('Failed to set window always on top:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 同步前端语言设置
|
||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(configStore.config.language);
|
||||
if (locale.value !== frontendLocale) {
|
||||
setLocale(frontendLocale);
|
||||
}
|
||||
});
|
||||
|
||||
// 监听配置加载完成
|
||||
@@ -130,9 +109,9 @@ watch(() => configStore.configLoaded, (isLoaded) => {
|
||||
{{ t('toolbar.tabType.' + (configStore.config.tabType === 'spaces' ? 'spaces' : 'tab')) }}
|
||||
</span>
|
||||
<span class="tab-size" title="Tab大小" v-if="configStore.config.tabType === 'spaces'">
|
||||
<button class="tab-btn" @click="() => configStore.decreaseTabSize()" :disabled="configStore.config.tabSize <= configStore.MIN_TAB_SIZE">-</button>
|
||||
<button class="tab-btn" @click="() => configStore.decreaseTabSize()" :disabled="configStore.config.tabSize <= configStore.tabSize.min">-</button>
|
||||
<span>{{ configStore.config.tabSize }}</span>
|
||||
<button class="tab-btn" @click="() => configStore.increaseTabSize()" :disabled="configStore.config.tabSize >= configStore.MAX_TAB_SIZE">+</button>
|
||||
<button class="tab-btn" @click="() => configStore.increaseTabSize()" :disabled="configStore.config.tabSize >= configStore.tabSize.max">+</button>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
@@ -167,7 +146,7 @@ watch(() => configStore.configLoaded, (isLoaded) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="settings-btn" :title="t('toolbar.settings')" @click="openSettingsWindow">
|
||||
<button class="settings-btn" :title="t('toolbar.settings')" @click="openSettings">
|
||||
<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>
|
||||
|
||||
@@ -1,219 +0,0 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { Extension, Compartment } from '@codemirror/state';
|
||||
|
||||
// 字体配置接口
|
||||
export interface FontConfig {
|
||||
fontFamily: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}
|
||||
|
||||
// 创建字体配置compartment
|
||||
export const fontCompartment = new Compartment();
|
||||
|
||||
// 默认鸿蒙字体配置
|
||||
export const HARMONYOS_FONT_CONFIG: FontConfig = {
|
||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
};
|
||||
|
||||
// 从后端配置创建字体配置
|
||||
export function createFontConfigFromBackend(backendConfig: {
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}): FontConfig {
|
||||
return {
|
||||
fontFamily: backendConfig.fontFamily || HARMONYOS_FONT_CONFIG.fontFamily,
|
||||
fontSize: backendConfig.fontSize || HARMONYOS_FONT_CONFIG.fontSize,
|
||||
lineHeight: backendConfig.lineHeight || HARMONYOS_FONT_CONFIG.lineHeight,
|
||||
fontWeight: backendConfig.fontWeight || HARMONYOS_FONT_CONFIG.fontWeight,
|
||||
};
|
||||
}
|
||||
|
||||
// 创建字体样式扩展
|
||||
export function createFontExtension(config: Partial<FontConfig> = {}): Extension {
|
||||
const fontConfig = { ...HARMONYOS_FONT_CONFIG, ...config };
|
||||
|
||||
const styles: Record<string, any> = {
|
||||
'&': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||
},
|
||||
'.cm-content': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||
},
|
||||
'.cm-editor': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-scroller': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-gutters': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
},
|
||||
'.cm-lineNumbers': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${Math.max(10, fontConfig.fontSize - 1)}px` }),
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${Math.max(12, fontConfig.fontSize - 1)}px` }),
|
||||
},
|
||||
'.cm-completionLabel': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-completionDetail': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
}
|
||||
};
|
||||
|
||||
return EditorView.theme(styles);
|
||||
}
|
||||
|
||||
// 创建响应式字体大小扩展
|
||||
export function createResponsiveFontExtension(baseFontSize: number = 14): Extension {
|
||||
return fontCompartment.of(createFontExtension({
|
||||
fontSize: baseFontSize,
|
||||
lineHeight: 1.5
|
||||
}));
|
||||
}
|
||||
|
||||
// 从后端配置创建字体扩展
|
||||
export function createFontExtensionFromBackend(backendConfig: {
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}): Extension {
|
||||
const fontConfig = createFontConfigFromBackend(backendConfig);
|
||||
return fontCompartment.of(createFontExtension(fontConfig));
|
||||
}
|
||||
|
||||
// 动态更新字体配置
|
||||
export function updateFontConfig(view: EditorView, config: Partial<FontConfig>): void {
|
||||
const newFontExtension = createFontExtension(config);
|
||||
|
||||
// 使用compartment重新配置字体扩展
|
||||
view.dispatch({
|
||||
effects: fontCompartment.reconfigure(newFontExtension)
|
||||
});
|
||||
}
|
||||
|
||||
// 预设字体配置
|
||||
export const FONT_PRESETS = {
|
||||
// 鸿蒙字体系列
|
||||
harmonyos: {
|
||||
name: '鸿蒙字体',
|
||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
harmonyosCondensed: {
|
||||
name: '鸿蒙紧凑字体',
|
||||
fontFamily: '"HarmonyOS Sans Condensed", "HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.4,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
|
||||
// 编程专用字体
|
||||
jetbrainsMono: {
|
||||
name: 'JetBrains Mono',
|
||||
fontFamily: '"JetBrains Mono", "Fira Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
firaCode: {
|
||||
name: 'Fira Code',
|
||||
fontFamily: '"Fira Code", "JetBrains Mono", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
sourceCodePro: {
|
||||
name: 'Source Code Pro',
|
||||
fontFamily: '"Source Code Pro", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
|
||||
// 系统字体
|
||||
systemMono: {
|
||||
name: '系统等宽字体',
|
||||
fontFamily: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
cascadiaCode: {
|
||||
name: 'Cascadia Code',
|
||||
fontFamily: '"Cascadia Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
|
||||
// 中文友好字体
|
||||
microsoftYaHei: {
|
||||
name: '微软雅黑',
|
||||
fontFamily: '"Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
pingFang: {
|
||||
name: '苹方字体',
|
||||
fontFamily: '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
|
||||
// 经典字体
|
||||
arial: {
|
||||
name: 'Arial',
|
||||
fontFamily: 'Arial, "Helvetica Neue", Helvetica, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
},
|
||||
helvetica: {
|
||||
name: 'Helvetica',
|
||||
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
}
|
||||
} as const;
|
||||
|
||||
// 字体预设类型
|
||||
export type FontPresetKey = keyof typeof FONT_PRESETS;
|
||||
|
||||
// 获取所有字体预设选项
|
||||
export function getFontPresetOptions() {
|
||||
return Object.entries(FONT_PRESETS).map(([key, preset]) => ({
|
||||
value: key as FontPresetKey,
|
||||
label: preset.name,
|
||||
fontFamily: preset.fontFamily
|
||||
}));
|
||||
}
|
||||
|
||||
// 根据预设创建字体扩展
|
||||
export function createPresetFontExtension(preset: keyof typeof FONT_PRESETS, overrides: Partial<FontConfig> = {}): Extension {
|
||||
const config = { ...FONT_PRESETS[preset], ...overrides };
|
||||
return createFontExtension(config);
|
||||
}
|
||||
@@ -1,81 +1,17 @@
|
||||
import {createI18n} from 'vue-i18n';
|
||||
import messages from './locales';
|
||||
import { ConfigService } from '@/../bindings/voidraft/internal/services';
|
||||
import { LanguageType } from '@/../bindings/voidraft/internal/models';
|
||||
import { ConfigUtils } from '@/utils/configUtils';
|
||||
|
||||
// 定义支持的语言类型
|
||||
export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
||||
|
||||
// 支持的语言列表
|
||||
export const SUPPORTED_LOCALES = [
|
||||
{
|
||||
code: 'zh-CN' as SupportedLocaleType,
|
||||
name: '简体中文'
|
||||
},
|
||||
{
|
||||
code: 'en-US' as SupportedLocaleType,
|
||||
name: 'English'
|
||||
}
|
||||
];
|
||||
|
||||
// 获取浏览器的默认语言
|
||||
const getBrowserLanguage = (): SupportedLocaleType => {
|
||||
const browserLang = navigator.language;
|
||||
const langCode = browserLang.split('-')[0];
|
||||
|
||||
// 检查是否支持此语言
|
||||
const supportedLang = SUPPORTED_LOCALES.find(locale =>
|
||||
locale.code.startsWith(langCode) || locale.code.split('-')[0] === langCode
|
||||
);
|
||||
|
||||
return supportedLang?.code || 'zh-CN';
|
||||
};
|
||||
|
||||
// 创建i18n实例
|
||||
const i18n = createI18n({
|
||||
compositionOnly: false,
|
||||
globalInjection: true,
|
||||
silentTranslationWarn: true,
|
||||
locale: 'zh-CN',
|
||||
fallbackLocale: 'zh-CN' as SupportedLocaleType,
|
||||
fallbackLocale: 'zh-CN',
|
||||
silentFallbackWarn: true,
|
||||
missingWarn: true,
|
||||
fallbackWarn: false,
|
||||
messages
|
||||
});
|
||||
|
||||
// 从后端获取语言设置
|
||||
const initializeLanguage = async () => {
|
||||
try {
|
||||
// 使用新的配置服务方法获取语言设置
|
||||
const language = await ConfigService.Get('editor.language') as LanguageType;
|
||||
if (language) {
|
||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
||||
i18n.global.locale = frontendLocale as any;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get language from backend:', error);
|
||||
// 如果获取失败,使用浏览器语言作为后备
|
||||
const browserLang = getBrowserLanguage();
|
||||
i18n.global.locale = browserLang as any;
|
||||
}
|
||||
};
|
||||
|
||||
// 立即初始化语言
|
||||
initializeLanguage();
|
||||
|
||||
// 切换语言的方法
|
||||
export const setLocale = async (locale: SupportedLocaleType) => {
|
||||
if (SUPPORTED_LOCALES.some(l => l.code === locale)) {
|
||||
try {
|
||||
// 转换为后端语言类型
|
||||
const backendLanguage = ConfigUtils.frontendLanguageToBackend(locale);
|
||||
|
||||
// 使用新的配置服务方法设置语言
|
||||
await ConfigService.Set('editor.language', backendLanguage);
|
||||
|
||||
// 更新前端语言
|
||||
i18n.global.locale = locale;
|
||||
} catch (error) {
|
||||
console.error('Failed to set language:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default i18n;
|
||||
@@ -48,6 +48,8 @@ export default {
|
||||
},
|
||||
settings: {
|
||||
title: 'Settings',
|
||||
backToEditor: 'Back to Editor',
|
||||
systemInfo: 'System Info',
|
||||
general: 'General',
|
||||
editing: 'Editor',
|
||||
appearance: 'Appearance',
|
||||
@@ -56,6 +58,10 @@ export default {
|
||||
comingSoon: 'Coming Soon...',
|
||||
save: 'Save',
|
||||
reset: 'Reset',
|
||||
dangerZone: 'Danger Zone',
|
||||
resetAllSettings: 'Reset All Settings',
|
||||
resetDescription: 'This will restore all settings to their default values. This action cannot be undone.',
|
||||
confirmReset: 'Are you sure you want to reset all settings? This action cannot be undone.',
|
||||
globalHotkey: 'Global Keyboard Shortcuts',
|
||||
enableGlobalHotkey: 'Enable Global Hotkeys',
|
||||
window: 'Window/Application',
|
||||
@@ -67,8 +73,8 @@ export default {
|
||||
fontSize: 'Font Size',
|
||||
fontSizeDescription: 'Editor font size',
|
||||
fontSettings: 'Font Settings',
|
||||
fontPreset: 'Font Preset',
|
||||
fontPresetDescription: 'Choose a predefined font combination',
|
||||
fontFamily: 'Font Family',
|
||||
fontFamilyDescription: 'Choose editor font family',
|
||||
fontWeight: 'Font Weight',
|
||||
fontWeightDescription: 'Set the thickness of the font',
|
||||
lineHeight: 'Line Height',
|
||||
|
||||
@@ -48,6 +48,8 @@ export default {
|
||||
},
|
||||
settings: {
|
||||
title: '设置',
|
||||
backToEditor: '返回编辑器',
|
||||
systemInfo: '系统信息',
|
||||
general: '常规',
|
||||
editing: '编辑器',
|
||||
appearance: '外观',
|
||||
@@ -56,6 +58,10 @@ export default {
|
||||
comingSoon: '即将推出...',
|
||||
save: '保存',
|
||||
reset: '重置',
|
||||
dangerZone: '危险操作',
|
||||
resetAllSettings: '重置所有设置',
|
||||
resetDescription: '这将恢复所有设置为默认值,此操作无法撤销',
|
||||
confirmReset: '确定要重置所有设置吗?此操作无法撤销。',
|
||||
globalHotkey: '全局键盘快捷键',
|
||||
enableGlobalHotkey: '启用全局热键',
|
||||
window: '窗口/应用程序',
|
||||
@@ -67,8 +73,8 @@ export default {
|
||||
fontSize: '字体大小',
|
||||
fontSizeDescription: '编辑器字体大小',
|
||||
fontSettings: '字体设置',
|
||||
fontPreset: '字体预设',
|
||||
fontPresetDescription: '选择预设的字体组合',
|
||||
fontFamily: '字体',
|
||||
fontFamilyDescription: '选择编辑器字体',
|
||||
fontWeight: '字体粗细',
|
||||
fontWeightDescription: '设置字体的粗细程度',
|
||||
lineHeight: '行高',
|
||||
|
||||
@@ -4,9 +4,10 @@ import '@/assets/styles/index.css';
|
||||
import {createPinia} from 'pinia';
|
||||
import i18n from './i18n';
|
||||
import router from './router';
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
const app = createApp(App);
|
||||
app.use(pinia)
|
||||
app.use(i18n);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import {createRouter, createWebHashHistory, createWebHistory, RouteRecordRaw} from 'vue-router';
|
||||
import Editor from '@/editor/Editor.vue';
|
||||
import Settings from '@/settings/Settings.vue';
|
||||
import GeneralPage from '@/settings/pages/GeneralPage.vue';
|
||||
import EditingPage from '@/settings/pages/EditingPage.vue';
|
||||
import AppearancePage from '@/settings/pages/AppearancePage.vue';
|
||||
import KeyBindingsPage from '@/settings/pages/KeyBindingsPage.vue';
|
||||
import UpdatesPage from '@/settings/pages/UpdatesPage.vue';
|
||||
import Editor from '@/views/editor/Editor.vue';
|
||||
import Settings from '@/views/settings/Settings.vue';
|
||||
import GeneralPage from '@/views/settings/pages/GeneralPage.vue';
|
||||
import EditingPage from '@/views/settings/pages/EditingPage.vue';
|
||||
import AppearancePage from '@/views/settings/pages/AppearancePage.vue';
|
||||
import KeyBindingsPage from '@/views/settings/pages/KeyBindingsPage.vue';
|
||||
import UpdatesPage from '@/views/settings/pages/UpdatesPage.vue';
|
||||
|
||||
const routes: RouteRecordRaw[] = [
|
||||
{
|
||||
|
||||
+214
-169
@@ -1,5 +1,5 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {ref, computed} from 'vue';
|
||||
import {computed, reactive} from 'vue';
|
||||
import {
|
||||
ConfigService
|
||||
} from '@/../bindings/voidraft/internal/services';
|
||||
@@ -7,10 +7,34 @@ import {EditorConfig, TabType, LanguageType} from '@/../bindings/voidraft/intern
|
||||
import {useLogStore} from './logStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ConfigUtils } from '@/utils/configUtils';
|
||||
import { FONT_PRESETS, getFontPresetOptions, type FontPresetKey } from '@/editor/extensions/fontExtension';
|
||||
|
||||
// 配置键映射 - 前端字段到后端配置键的映射
|
||||
const CONFIG_KEY_MAP = {
|
||||
// 国际化相关导入
|
||||
export type SupportedLocaleType = 'zh-CN' | 'en-US';
|
||||
|
||||
// 支持的语言列表
|
||||
export const SUPPORTED_LOCALES = [
|
||||
{
|
||||
code: 'zh-CN' as SupportedLocaleType,
|
||||
name: '简体中文'
|
||||
},
|
||||
{
|
||||
code: 'en-US' as SupportedLocaleType,
|
||||
name: 'English'
|
||||
}
|
||||
];
|
||||
|
||||
// 配置键映射和限制的类型定义
|
||||
type ConfigKeyMap = {
|
||||
readonly [K in keyof EditorConfig]: string;
|
||||
};
|
||||
|
||||
type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
||||
type StringConfigKey = 'fontFamily' | 'fontWeight';
|
||||
type BooleanConfigKey = 'enableTabIndent' | 'alwaysOnTop';
|
||||
type EnumConfigKey = 'tabType' | 'language';
|
||||
|
||||
// 配置键映射
|
||||
const CONFIG_KEY_MAP: ConfigKeyMap = {
|
||||
fontSize: 'editor.font_size',
|
||||
fontFamily: 'editor.font_family',
|
||||
fontWeight: 'editor.font_weight',
|
||||
@@ -26,236 +50,257 @@ const CONFIG_KEY_MAP = {
|
||||
const CONFIG_LIMITS = {
|
||||
fontSize: { min: 12, max: 28, default: 13 },
|
||||
tabSize: { min: 2, max: 8, default: 4 },
|
||||
lineHeight: { min: 1.0, max: 3.0, default: 1.5 },
|
||||
tabType: { values: [TabType.TabTypeSpaces, TabType.TabTypeTab], default: TabType.TabTypeSpaces }
|
||||
} as const;
|
||||
|
||||
export const useConfigStore = defineStore('config', () => {
|
||||
// 获取日志store
|
||||
const logStore = useLogStore();
|
||||
const { t } = useI18n();
|
||||
// 常用字体选项
|
||||
export const FONT_OPTIONS = [
|
||||
{ label: '鸿蒙字体', value: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif' },
|
||||
{ label: '微软雅黑', value: '"Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif' },
|
||||
{ label: '苹方字体', value: '"PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif' },
|
||||
{ label: 'JetBrains Mono', value: '"JetBrains Mono", "Fira Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||
{ label: 'Fira Code', value: '"Fira Code", "JetBrains Mono", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||
{ label: 'Source Code Pro', value: '"Source Code Pro", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||
{ label: 'Cascadia Code', value: '"Cascadia Code", "SF Mono", Monaco, Consolas, "Ubuntu Mono", monospace' },
|
||||
{ label: '系统等宽字体', value: '"SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, "Courier New", monospace' }
|
||||
] as const;
|
||||
|
||||
// 配置状态
|
||||
const config = ref<EditorConfig>(new EditorConfig({
|
||||
// 获取浏览器的默认语言
|
||||
const getBrowserLanguage = (): SupportedLocaleType => {
|
||||
const browserLang = navigator.language;
|
||||
const langCode = browserLang.split('-')[0];
|
||||
|
||||
// 检查是否支持此语言
|
||||
const supportedLang = SUPPORTED_LOCALES.find(locale =>
|
||||
locale.code.startsWith(langCode) || locale.code.split('-')[0] === langCode
|
||||
);
|
||||
|
||||
return supportedLang?.code || 'zh-CN';
|
||||
};
|
||||
|
||||
// 默认配置
|
||||
const DEFAULT_CONFIG: EditorConfig = {
|
||||
fontSize: CONFIG_LIMITS.fontSize.default,
|
||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||
fontFamily: FONT_OPTIONS[0].value,
|
||||
fontWeight: 'normal',
|
||||
lineHeight: 1.5,
|
||||
lineHeight: CONFIG_LIMITS.lineHeight.default,
|
||||
enableTabIndent: true,
|
||||
tabSize: CONFIG_LIMITS.tabSize.default,
|
||||
tabType: CONFIG_LIMITS.tabType.default,
|
||||
language: LanguageType.LangZhCN,
|
||||
alwaysOnTop: false
|
||||
}));
|
||||
};
|
||||
|
||||
// 加载状态
|
||||
const isLoading = ref(false);
|
||||
const configLoaded = ref(false);
|
||||
export const useConfigStore = defineStore('config', () => {
|
||||
const logStore = useLogStore();
|
||||
const { t, locale } = useI18n();
|
||||
|
||||
// 计算属性
|
||||
const MIN_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.min);
|
||||
const MAX_FONT_SIZE = computed(() => CONFIG_LIMITS.fontSize.max);
|
||||
const MIN_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.min);
|
||||
const MAX_TAB_SIZE = computed(() => CONFIG_LIMITS.tabSize.max);
|
||||
// 响应式状态
|
||||
const state = reactive({
|
||||
config: { ...DEFAULT_CONFIG } as EditorConfig,
|
||||
isLoading: false,
|
||||
configLoaded: false
|
||||
});
|
||||
|
||||
// 从后端加载配置
|
||||
async function loadConfig(): Promise<void> {
|
||||
if (isLoading.value) return;
|
||||
// 计算属性 - 使用工厂函数简化
|
||||
const createLimitComputed = (key: NumberConfigKey) => computed(() => CONFIG_LIMITS[key]);
|
||||
const limits = {
|
||||
fontSize: createLimitComputed('fontSize'),
|
||||
tabSize: createLimitComputed('tabSize'),
|
||||
lineHeight: createLimitComputed('lineHeight')
|
||||
};
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const appConfig = await ConfigService.GetConfig();
|
||||
// 错误处理装饰器
|
||||
const withErrorHandling = <T extends any[], R>(
|
||||
fn: (...args: T) => Promise<R>,
|
||||
errorMsg: string,
|
||||
successMsg?: string
|
||||
) => async (...args: T): Promise<R | null> => {
|
||||
const result = await fn(...args).catch(error => {
|
||||
console.error(errorMsg, error);
|
||||
logStore.error(t(errorMsg));
|
||||
return null;
|
||||
});
|
||||
|
||||
if (appConfig?.editor) {
|
||||
Object.assign(config.value, appConfig.editor);
|
||||
if (result !== null && successMsg) {
|
||||
logStore.info(t(successMsg));
|
||||
}
|
||||
|
||||
configLoaded.value = true;
|
||||
logStore.info(t('config.loadSuccess'));
|
||||
} catch (error) {
|
||||
console.error('Failed to load configuration:', error);
|
||||
logStore.error(t('config.loadFailed'));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// 通用配置更新方法
|
||||
const updateConfig = withErrorHandling(
|
||||
async <K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> => {
|
||||
// 确保配置已加载
|
||||
if (!state.configLoaded && !state.isLoading) {
|
||||
await initConfig();
|
||||
}
|
||||
|
||||
// 更新配置项的通用方法 - 直接调用后端Set方法
|
||||
async function updateConfig<K extends keyof EditorConfig>(key: K, value: EditorConfig[K]): Promise<void> {
|
||||
if (!configLoaded.value) return;
|
||||
|
||||
try {
|
||||
const backendKey = CONFIG_KEY_MAP[key as keyof typeof CONFIG_KEY_MAP];
|
||||
const backendKey = CONFIG_KEY_MAP[key];
|
||||
if (!backendKey) {
|
||||
throw new Error(`No backend key mapping found for ${String(key)}`);
|
||||
throw new Error(`No backend key mapping found for ${key.toString()}`);
|
||||
}
|
||||
|
||||
await ConfigService.Set(backendKey, value);
|
||||
state.config[key] = value;
|
||||
},
|
||||
'config.saveFailed',
|
||||
'config.saveSuccess'
|
||||
);
|
||||
|
||||
// 更新本地状态
|
||||
config.value[key] = value;
|
||||
// 加载配置
|
||||
const initConfig = withErrorHandling(
|
||||
async (): Promise<void> => {
|
||||
if (state.isLoading) return;
|
||||
|
||||
logStore.info(t('config.saveSuccess'));
|
||||
} catch (error) {
|
||||
console.error(`Failed to update config ${String(key)}:`, error);
|
||||
logStore.error(t('config.saveFailed'));
|
||||
throw error;
|
||||
}
|
||||
state.isLoading = true;
|
||||
const appConfig = await ConfigService.GetConfig();
|
||||
|
||||
if (appConfig?.editor) {
|
||||
Object.assign(state.config, appConfig.editor);
|
||||
}
|
||||
|
||||
// 字体大小操作
|
||||
async function adjustFontSize(delta: number): Promise<void> {
|
||||
const newSize = ConfigUtils.clamp(config.value.fontSize + delta, CONFIG_LIMITS.fontSize.min, CONFIG_LIMITS.fontSize.max);
|
||||
await updateConfig('fontSize', newSize);
|
||||
}
|
||||
state.configLoaded = true;
|
||||
state.isLoading = false;
|
||||
},
|
||||
'config.loadFailed',
|
||||
'config.loadSuccess'
|
||||
);
|
||||
|
||||
// Tab大小操作
|
||||
async function adjustTabSize(delta: number): Promise<void> {
|
||||
const newSize = ConfigUtils.clamp(config.value.tabSize + delta, CONFIG_LIMITS.tabSize.min, CONFIG_LIMITS.tabSize.max);
|
||||
await updateConfig('tabSize', newSize);
|
||||
}
|
||||
// 数值调整工厂函数
|
||||
const createNumberAdjuster = (key: NumberConfigKey) => {
|
||||
const limit = CONFIG_LIMITS[key];
|
||||
return {
|
||||
increase: () => updateConfig(key, ConfigUtils.clamp(state.config[key] + 1, limit.min, limit.max)),
|
||||
decrease: () => updateConfig(key, ConfigUtils.clamp(state.config[key] - 1, limit.min, limit.max)),
|
||||
set: (value: number) => updateConfig(key, ConfigUtils.clamp(value, limit.min, limit.max)),
|
||||
reset: () => updateConfig(key, limit.default)
|
||||
};
|
||||
};
|
||||
|
||||
// 切换操作
|
||||
async function toggleTabIndent(): Promise<void> {
|
||||
await updateConfig('enableTabIndent', !config.value.enableTabIndent);
|
||||
}
|
||||
// 布尔值切换工厂函数
|
||||
const createToggler = (key: BooleanConfigKey) =>
|
||||
() => updateConfig(key, !state.config[key]);
|
||||
|
||||
async function toggleTabType(): Promise<void> {
|
||||
const newTabType = config.value.tabType === TabType.TabTypeSpaces ? TabType.TabTypeTab : TabType.TabTypeSpaces;
|
||||
await updateConfig('tabType', newTabType);
|
||||
}
|
||||
|
||||
async function toggleAlwaysOnTop(): Promise<void> {
|
||||
await updateConfig('alwaysOnTop', !config.value.alwaysOnTop);
|
||||
}
|
||||
|
||||
// 语言设置
|
||||
async function setLanguage(language: LanguageType): Promise<void> {
|
||||
try {
|
||||
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
|
||||
config.value.language = language;
|
||||
logStore.info(t('config.languageChanged'));
|
||||
} catch (error) {
|
||||
console.error('Failed to set language:', error);
|
||||
logStore.error(t('config.languageChangeFailed'));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
// 枚举值切换工厂函数
|
||||
const createEnumToggler = <T extends TabType>(key: 'tabType', values: readonly T[]) =>
|
||||
() => {
|
||||
const currentIndex = values.indexOf(state.config[key] as T);
|
||||
const nextIndex = (currentIndex + 1) % values.length;
|
||||
return updateConfig(key, values[nextIndex]);
|
||||
};
|
||||
|
||||
// 重置配置
|
||||
async function resetConfig(): Promise<void> {
|
||||
if (isLoading.value) return;
|
||||
const resetConfig = withErrorHandling(
|
||||
async (): Promise<void> => {
|
||||
if (state.isLoading) return;
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
state.isLoading = true;
|
||||
await ConfigService.ResetConfig();
|
||||
await loadConfig();
|
||||
logStore.info(t('config.resetSuccess'));
|
||||
} catch (error) {
|
||||
console.error('Failed to reset configuration:', error);
|
||||
logStore.error(t('config.resetFailed'));
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
await initConfig();
|
||||
state.isLoading = false;
|
||||
},
|
||||
'config.resetFailed',
|
||||
'config.resetSuccess'
|
||||
);
|
||||
|
||||
// 设置字体大小
|
||||
async function setFontSize(size: number): Promise<void> {
|
||||
await updateConfig('fontSize', size);
|
||||
}
|
||||
// 语言设置方法
|
||||
const setLanguage = withErrorHandling(
|
||||
async (language: LanguageType): Promise<void> => {
|
||||
await ConfigService.Set(CONFIG_KEY_MAP.language, language);
|
||||
state.config.language = language;
|
||||
|
||||
// 设置Tab大小
|
||||
async function setTabSize(size: number): Promise<void> {
|
||||
await updateConfig('tabSize', size);
|
||||
}
|
||||
// 同步更新前端语言
|
||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(language);
|
||||
locale.value = frontendLocale as any;
|
||||
},
|
||||
'config.languageChangeFailed',
|
||||
'config.languageChanged'
|
||||
);
|
||||
|
||||
// 字体预设相关方法
|
||||
async function setFontPreset(presetKey: FontPresetKey): Promise<void> {
|
||||
const preset = FONT_PRESETS[presetKey];
|
||||
if (!preset) {
|
||||
throw new Error(`Unknown font preset: ${presetKey}`);
|
||||
}
|
||||
// 通过前端语言代码设置语言
|
||||
const setLocale = async (localeCode: SupportedLocaleType): Promise<void> => {
|
||||
const backendLanguage = ConfigUtils.frontendLanguageToBackend(localeCode);
|
||||
await setLanguage(backendLanguage);
|
||||
};
|
||||
|
||||
// 初始化语言设置
|
||||
const initializeLanguage = async (): Promise<void> => {
|
||||
try {
|
||||
// 批量更新字体相关配置
|
||||
await updateConfig('fontFamily', preset.fontFamily);
|
||||
await updateConfig('fontWeight', preset.fontWeight);
|
||||
await updateConfig('lineHeight', preset.lineHeight);
|
||||
// 可选择是否同时更新字体大小
|
||||
// await updateConfig('fontSize', preset.fontSize);
|
||||
// 如果配置未加载,先加载配置
|
||||
if (!state.configLoaded) {
|
||||
await initConfig();
|
||||
}
|
||||
|
||||
logStore.info(`字体预设已切换为: ${preset.name}`);
|
||||
// 同步前端语言设置
|
||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(state.config.language);
|
||||
locale.value = frontendLocale as any;
|
||||
} catch (error) {
|
||||
console.error('Failed to set font preset:', error);
|
||||
logStore.error('字体预设设置失败');
|
||||
throw error;
|
||||
}
|
||||
const browserLang = getBrowserLanguage();
|
||||
locale.value = browserLang as any;
|
||||
}
|
||||
};
|
||||
|
||||
// 获取当前字体预设(如果匹配的话)
|
||||
function getCurrentFontPreset(): FontPresetKey | null {
|
||||
const currentFamily = config.value.fontFamily;
|
||||
for (const [key, preset] of Object.entries(FONT_PRESETS)) {
|
||||
if (preset.fontFamily === currentFamily) {
|
||||
return key as FontPresetKey;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
// 创建数值调整器
|
||||
const fontSize = createNumberAdjuster('fontSize');
|
||||
const tabSize = createNumberAdjuster('tabSize');
|
||||
const lineHeight = createNumberAdjuster('lineHeight');
|
||||
|
||||
// 设置字体族
|
||||
async function setFontFamily(fontFamily: string): Promise<void> {
|
||||
await updateConfig('fontFamily', fontFamily);
|
||||
}
|
||||
// 创建切换器
|
||||
const toggleTabIndent = createToggler('enableTabIndent');
|
||||
const toggleAlwaysOnTop = createToggler('alwaysOnTop');
|
||||
const toggleTabType = createEnumToggler('tabType', CONFIG_LIMITS.tabType.values);
|
||||
|
||||
// 设置字体粗细
|
||||
async function setFontWeight(fontWeight: string): Promise<void> {
|
||||
await updateConfig('fontWeight', fontWeight);
|
||||
}
|
||||
|
||||
// 设置行高
|
||||
async function setLineHeight(lineHeight: number): Promise<void> {
|
||||
await updateConfig('lineHeight', lineHeight);
|
||||
}
|
||||
// 字符串配置设置器
|
||||
const setFontFamily = (value: string) => updateConfig('fontFamily', value);
|
||||
const setFontWeight = (value: string) => updateConfig('fontWeight', value);
|
||||
|
||||
return {
|
||||
// 状态
|
||||
config,
|
||||
configLoaded,
|
||||
isLoading,
|
||||
config: computed(() => state.config),
|
||||
configLoaded: computed(() => state.configLoaded),
|
||||
isLoading: computed(() => state.isLoading),
|
||||
|
||||
// 计算属性
|
||||
MIN_FONT_SIZE,
|
||||
MAX_FONT_SIZE,
|
||||
MIN_TAB_SIZE,
|
||||
MAX_TAB_SIZE,
|
||||
// 限制常量
|
||||
...limits,
|
||||
|
||||
// 核心方法
|
||||
loadConfig,
|
||||
initConfig,
|
||||
resetConfig,
|
||||
setLanguage,
|
||||
updateConfig,
|
||||
|
||||
// 语言相关方法
|
||||
setLanguage,
|
||||
setLocale,
|
||||
initializeLanguage,
|
||||
|
||||
// 字体大小操作
|
||||
increaseFontSize: () => adjustFontSize(1),
|
||||
decreaseFontSize: () => adjustFontSize(-1),
|
||||
resetFontSize: () => setFontSize(CONFIG_LIMITS.fontSize.default),
|
||||
setFontSize,
|
||||
...fontSize,
|
||||
increaseFontSize: fontSize.increase,
|
||||
decreaseFontSize: fontSize.decrease,
|
||||
resetFontSize: fontSize.reset,
|
||||
setFontSize: fontSize.set,
|
||||
|
||||
// Tab操作
|
||||
toggleTabIndent,
|
||||
increaseTabSize: () => adjustTabSize(1),
|
||||
decreaseTabSize: () => adjustTabSize(-1),
|
||||
...tabSize,
|
||||
increaseTabSize: tabSize.increase,
|
||||
decreaseTabSize: tabSize.decrease,
|
||||
setTabSize: tabSize.set,
|
||||
toggleTabType,
|
||||
setTabSize,
|
||||
|
||||
// 行高操作
|
||||
setLineHeight: lineHeight.set,
|
||||
|
||||
// 窗口操作
|
||||
toggleAlwaysOnTop,
|
||||
|
||||
// 字体预设相关方法
|
||||
setFontPreset,
|
||||
getCurrentFontPreset,
|
||||
// 字体操作
|
||||
setFontFamily,
|
||||
setFontWeight,
|
||||
setLineHeight
|
||||
};
|
||||
},{
|
||||
persist: true,
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { SupportedLocaleType } from '@/i18n';
|
||||
import type { SupportedLocaleType } from '@/stores/configStore';
|
||||
|
||||
/**
|
||||
* 配置工具类
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
updateFontConfig,
|
||||
} from './extensions';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { DocumentService } from '@/../bindings/voidraft/internal/services';
|
||||
import { DocumentService } from '../../../bindings/voidraft/internal/services';
|
||||
import Toolbar from '@/components/toolbar/Toolbar.vue';
|
||||
|
||||
const editorStore = useEditorStore();
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { EditorView, ViewPlugin, ViewUpdate } from '@codemirror/view';
|
||||
import { DocumentService } from '@/../bindings/voidraft/internal/services';
|
||||
import { DocumentService } from '../../../../bindings/voidraft/internal/services';
|
||||
import { useDebounceFn } from '@vueuse/core';
|
||||
|
||||
// 定义自动保存配置选项
|
||||
+1
-1
@@ -23,7 +23,7 @@ import {defaultKeymap, history, historyKeymap,} from '@codemirror/commands';
|
||||
import {highlightSelectionMatches, searchKeymap} from '@codemirror/search';
|
||||
import {autocompletion, closeBrackets, closeBracketsKeymap, completionKeymap} from '@codemirror/autocomplete';
|
||||
import {lintKeymap} from '@codemirror/lint';
|
||||
import {customHighlightActiveLine, defaultDark} from '@/editor/theme/default-dark';
|
||||
import {customHighlightActiveLine, defaultDark} from '@/views/editor/theme/default-dark';
|
||||
|
||||
// 基本编辑器设置,包含常用扩展
|
||||
export const createBasicSetup = (): Extension[] => {
|
||||
@@ -0,0 +1,111 @@
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { Extension, Compartment } from '@codemirror/state';
|
||||
|
||||
// 字体配置接口
|
||||
export interface FontConfig {
|
||||
fontFamily: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}
|
||||
|
||||
// 创建字体配置compartment
|
||||
export const fontCompartment = new Compartment();
|
||||
|
||||
// 默认字体配置
|
||||
export const DEFAULT_FONT_CONFIG: FontConfig = {
|
||||
fontFamily: '"HarmonyOS Sans SC", "HarmonyOS Sans", "Microsoft YaHei", "PingFang SC", "Helvetica Neue", Arial, sans-serif',
|
||||
fontSize: 14,
|
||||
lineHeight: 1.5,
|
||||
fontWeight: 'normal'
|
||||
};
|
||||
|
||||
// 从后端配置创建字体配置
|
||||
export function createFontConfigFromBackend(backendConfig: {
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}): FontConfig {
|
||||
return {
|
||||
fontFamily: backendConfig.fontFamily || DEFAULT_FONT_CONFIG.fontFamily,
|
||||
fontSize: backendConfig.fontSize || DEFAULT_FONT_CONFIG.fontSize,
|
||||
lineHeight: backendConfig.lineHeight || DEFAULT_FONT_CONFIG.lineHeight,
|
||||
fontWeight: backendConfig.fontWeight || DEFAULT_FONT_CONFIG.fontWeight,
|
||||
};
|
||||
}
|
||||
|
||||
// 创建字体样式扩展
|
||||
export function createFontExtension(config: Partial<FontConfig> = {}): Extension {
|
||||
const fontConfig = { ...DEFAULT_FONT_CONFIG, ...config };
|
||||
|
||||
const styles: Record<string, any> = {
|
||||
'&': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||
},
|
||||
'.cm-content': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
...(fontConfig.lineHeight && { lineHeight: fontConfig.lineHeight.toString() }),
|
||||
...(fontConfig.fontWeight && { fontWeight: fontConfig.fontWeight }),
|
||||
},
|
||||
'.cm-editor': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-scroller': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-gutters': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${fontConfig.fontSize}px` }),
|
||||
},
|
||||
'.cm-lineNumbers': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${Math.max(10, fontConfig.fontSize - 1)}px` }),
|
||||
},
|
||||
'.cm-tooltip': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
...(fontConfig.fontSize && { fontSize: `${Math.max(12, fontConfig.fontSize - 1)}px` }),
|
||||
},
|
||||
'.cm-completionLabel': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
},
|
||||
'.cm-completionDetail': {
|
||||
fontFamily: fontConfig.fontFamily,
|
||||
}
|
||||
};
|
||||
|
||||
return EditorView.theme(styles);
|
||||
}
|
||||
|
||||
// 创建响应式字体大小扩展
|
||||
export function createResponsiveFontExtension(baseFontSize: number = 14): Extension {
|
||||
return fontCompartment.of(createFontExtension({
|
||||
fontSize: baseFontSize,
|
||||
lineHeight: 1.5
|
||||
}));
|
||||
}
|
||||
|
||||
// 从后端配置创建字体扩展
|
||||
export function createFontExtensionFromBackend(backendConfig: {
|
||||
fontFamily?: string;
|
||||
fontSize?: number;
|
||||
lineHeight?: number;
|
||||
fontWeight?: string;
|
||||
}): Extension {
|
||||
const fontConfig = createFontConfigFromBackend(backendConfig);
|
||||
return fontCompartment.of(createFontExtension(fontConfig));
|
||||
}
|
||||
|
||||
// 动态更新字体配置
|
||||
export function updateFontConfig(view: EditorView, config: Partial<FontConfig>): void {
|
||||
const newFontExtension = createFontExtension(config);
|
||||
|
||||
// 使用compartment重新配置字体扩展
|
||||
view.dispatch({
|
||||
effects: fontCompartment.reconfigure(newFontExtension)
|
||||
});
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
+1
-1
@@ -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 '@/../bindings/voidraft/internal/models/models';
|
||||
import {TabType} from '../../../../bindings/voidraft/internal/models/models';
|
||||
|
||||
// Tab设置相关的compartment
|
||||
export const tabSizeCompartment = new Compartment();
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -3,6 +3,7 @@ import { useConfigStore } from '@/stores/configStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, watch } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import MemoryMonitor from '@/components/monitor/MemoryMonitor.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
@@ -26,9 +27,9 @@ const handleNavClick = (item: typeof navItems[0]) => {
|
||||
router.push(item.route);
|
||||
};
|
||||
|
||||
// 重置设置
|
||||
const resetSettings = async () => {
|
||||
await configStore.resetConfig();
|
||||
// 返回编辑器
|
||||
const goBackToEditor = () => {
|
||||
router.push('/');
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -36,8 +37,15 @@ const resetSettings = async () => {
|
||||
<div class="settings-container">
|
||||
<div class="settings-sidebar">
|
||||
<div class="settings-header">
|
||||
<div class="header-content">
|
||||
<button class="back-button" @click="goBackToEditor" :title="t('settings.backToEditor')">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<h1>{{ t('settings.title') }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-nav">
|
||||
<div
|
||||
v-for="item in navItems"
|
||||
@@ -50,10 +58,11 @@ const resetSettings = async () => {
|
||||
<span class="nav-text">{{ t(`settings.${item.id}`) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-actions">
|
||||
<button class="reset-button" @click="resetSettings">
|
||||
{{ t('settings.reset') }}
|
||||
</button>
|
||||
<div class="settings-footer">
|
||||
<div class="memory-info-section">
|
||||
<div class="section-title">{{ t('settings.systemInfo') }}</div>
|
||||
<MemoryMonitor />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings-content">
|
||||
@@ -88,6 +97,34 @@ const resetSettings = async () => {
|
||||
border-bottom: 1px solid #444444;
|
||||
background-color: #2d2d2d;
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.back-button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #a0a0a0;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
color: #e0e0e0;
|
||||
background-color: #3a3a3a;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
@@ -95,6 +132,7 @@ const resetSettings = async () => {
|
||||
color: #ffffff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settings-nav {
|
||||
flex: 1;
|
||||
@@ -144,29 +182,23 @@ const resetSettings = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
.settings-actions {
|
||||
padding: 16px;
|
||||
.settings-footer {
|
||||
padding: 12px 16px 16px 16px;
|
||||
border-top: 1px solid #444444;
|
||||
background-color: #2d2d2d;
|
||||
|
||||
.reset-button {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background-color: #3a3a3a;
|
||||
border: 1px solid #555555;
|
||||
color: #e0e0e0;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 14px;
|
||||
.memory-info-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
|
||||
&:hover {
|
||||
background-color: #444444;
|
||||
border-color: #666666;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
.section-title {
|
||||
font-size: 10px;
|
||||
color: #777777;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
+2
-8
@@ -4,9 +4,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { ref } from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import { LanguageType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { setLocale, SupportedLocaleType } from '@/i18n';
|
||||
import { ConfigUtils } from '@/utils/configUtils';
|
||||
import { LanguageType } from '../../../../bindings/voidraft/internal/models/models';
|
||||
|
||||
const { t } = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
@@ -23,12 +21,8 @@ const updateLanguage = async (event: Event) => {
|
||||
const selectedLanguage = select.value as LanguageType;
|
||||
|
||||
try {
|
||||
// 设置后端语言配置
|
||||
// 使用 configStore 的语言设置方法
|
||||
await configStore.setLanguage(selectedLanguage);
|
||||
|
||||
// 同步前端语言设置
|
||||
const frontendLocale = ConfigUtils.backendLanguageToFrontend(selectedLanguage);
|
||||
setLocale(frontendLocale);
|
||||
} catch (error) {
|
||||
console.error('Failed to update language:', error);
|
||||
}
|
||||
+92
-37
@@ -1,26 +1,37 @@
|
||||
<script setup lang="ts">
|
||||
import { useConfigStore } from '@/stores/configStore';
|
||||
import { FONT_OPTIONS } from '@/stores/configStore';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ref, computed } from 'vue';
|
||||
import { ref, computed, onMounted } from 'vue';
|
||||
import SettingSection from '../components/SettingSection.vue';
|
||||
import SettingItem from '../components/SettingItem.vue';
|
||||
import ToggleSwitch from '../components/ToggleSwitch.vue';
|
||||
import { TabType } from '@/../bindings/voidraft/internal/models/models';
|
||||
import { getFontPresetOptions, type FontPresetKey } from '@/editor/extensions/fontExtension';
|
||||
import { TabType } from '../../../../bindings/voidraft/internal/models/models';
|
||||
|
||||
const { t } = useI18n();
|
||||
const configStore = useConfigStore();
|
||||
|
||||
// 字体预设选项
|
||||
const fontPresetOptions = getFontPresetOptions();
|
||||
const currentFontPreset = computed(() => configStore.getCurrentFontPreset());
|
||||
// 确保配置已加载
|
||||
onMounted(async () => {
|
||||
if (!configStore.configLoaded) {
|
||||
await configStore.loadConfig();
|
||||
}
|
||||
});
|
||||
|
||||
// 字体预设选择
|
||||
const handleFontPresetChange = (event: Event) => {
|
||||
// 字体选择选项
|
||||
const fontFamilyOptions = FONT_OPTIONS;
|
||||
const currentFontFamily = computed(() => configStore.config.fontFamily);
|
||||
|
||||
// 字体选择
|
||||
const handleFontFamilyChange = async (event: Event) => {
|
||||
const target = event.target as HTMLSelectElement;
|
||||
const presetKey = target.value;
|
||||
if (presetKey && presetKey !== 'custom') {
|
||||
configStore.setFontPreset(presetKey as FontPresetKey);
|
||||
const fontFamily = target.value;
|
||||
if (fontFamily) {
|
||||
try {
|
||||
await configStore.setFontFamily(fontFamily);
|
||||
} catch (error) {
|
||||
console.error('Failed to set font family:', error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -38,29 +49,49 @@ const fontWeightOptions = [
|
||||
];
|
||||
|
||||
// 字体粗细选择
|
||||
const handleFontWeightChange = (event: Event) => {
|
||||
const handleFontWeightChange = async (event: Event) => {
|
||||
const target = event.target as HTMLSelectElement;
|
||||
configStore.setFontWeight(target.value);
|
||||
try {
|
||||
await configStore.setFontWeight(target.value);
|
||||
} catch (error) {
|
||||
console.error('Failed to set font weight:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 行高控制
|
||||
const increaseLineHeight = () => {
|
||||
const increaseLineHeight = async () => {
|
||||
const newLineHeight = Math.min(3.0, configStore.config.lineHeight + 0.1);
|
||||
configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||
try {
|
||||
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||
} catch (error) {
|
||||
console.error('Failed to increase line height:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const decreaseLineHeight = () => {
|
||||
const decreaseLineHeight = async () => {
|
||||
const newLineHeight = Math.max(1.0, configStore.config.lineHeight - 0.1);
|
||||
configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||
try {
|
||||
await configStore.setLineHeight(Math.round(newLineHeight * 10) / 10);
|
||||
} catch (error) {
|
||||
console.error('Failed to decrease line height:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 字体大小控制
|
||||
const increaseFontSize = () => {
|
||||
configStore.increaseFontSize();
|
||||
const increaseFontSize = async () => {
|
||||
try {
|
||||
await configStore.increaseFontSize();
|
||||
} catch (error) {
|
||||
console.error('Failed to increase font size:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const decreaseFontSize = () => {
|
||||
configStore.decreaseFontSize();
|
||||
const decreaseFontSize = async () => {
|
||||
try {
|
||||
await configStore.decreaseFontSize();
|
||||
} catch (error) {
|
||||
console.error('Failed to decrease font size:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Tab类型切换
|
||||
@@ -71,12 +102,37 @@ const tabTypeText = computed(() => {
|
||||
});
|
||||
|
||||
// Tab大小增减
|
||||
const increaseTabSize = () => {
|
||||
configStore.increaseTabSize();
|
||||
const increaseTabSize = async () => {
|
||||
try {
|
||||
await configStore.increaseTabSize();
|
||||
} catch (error) {
|
||||
console.error('Failed to increase tab size:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const decreaseTabSize = () => {
|
||||
configStore.decreaseTabSize();
|
||||
const decreaseTabSize = async () => {
|
||||
try {
|
||||
await configStore.decreaseTabSize();
|
||||
} catch (error) {
|
||||
console.error('Failed to decrease tab size:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Tab相关操作
|
||||
const handleToggleTabIndent = async () => {
|
||||
try {
|
||||
await configStore.toggleTabIndent();
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle tab indent:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleTabType = async () => {
|
||||
try {
|
||||
await configStore.toggleTabType();
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle tab type:', error);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -84,17 +140,16 @@ const decreaseTabSize = () => {
|
||||
<div class="settings-page">
|
||||
<SettingSection :title="t('settings.fontSettings')">
|
||||
<SettingItem
|
||||
:title="t('settings.fontPreset')"
|
||||
:description="t('settings.fontPresetDescription')"
|
||||
:title="t('settings.fontFamily')"
|
||||
:description="t('settings.fontFamilyDescription')"
|
||||
>
|
||||
<select
|
||||
class="font-preset-select"
|
||||
:value="currentFontPreset || 'custom'"
|
||||
@change="handleFontPresetChange"
|
||||
class="font-family-select"
|
||||
:value="currentFontFamily"
|
||||
@change="handleFontFamilyChange"
|
||||
>
|
||||
<option value="custom">自定义</option>
|
||||
<option
|
||||
v-for="option in fontPresetOptions"
|
||||
v-for="option in fontFamilyOptions"
|
||||
:key="option.value"
|
||||
:value="option.value"
|
||||
>
|
||||
@@ -163,14 +218,14 @@ const decreaseTabSize = () => {
|
||||
<SettingSection :title="t('settings.tabSettings')">
|
||||
<SettingItem :title="t('settings.tabSize')">
|
||||
<div class="number-control">
|
||||
<button @click="decreaseTabSize" class="control-button" :disabled="configStore.config.tabSize <= configStore.MIN_TAB_SIZE">-</button>
|
||||
<button @click="decreaseTabSize" class="control-button" :disabled="configStore.config.tabSize <= configStore.tabSize.min">-</button>
|
||||
<span>{{ configStore.config.tabSize }}</span>
|
||||
<button @click="increaseTabSize" class="control-button" :disabled="configStore.config.tabSize >= configStore.MAX_TAB_SIZE">+</button>
|
||||
<button @click="increaseTabSize" class="control-button" :disabled="configStore.config.tabSize >= configStore.tabSize.max">+</button>
|
||||
</div>
|
||||
</SettingItem>
|
||||
|
||||
<SettingItem :title="t('settings.tabType')">
|
||||
<button class="tab-type-toggle" @click="configStore.toggleTabType">
|
||||
<button class="tab-type-toggle" @click="handleToggleTabType">
|
||||
{{ tabTypeText }}
|
||||
</button>
|
||||
</SettingItem>
|
||||
@@ -178,7 +233,7 @@ const decreaseTabSize = () => {
|
||||
<SettingItem :title="t('settings.enableTabIndent')">
|
||||
<ToggleSwitch
|
||||
v-model="configStore.config.enableTabIndent"
|
||||
@update:modelValue="configStore.toggleTabIndent"
|
||||
@update:modelValue="handleToggleTabIndent"
|
||||
/>
|
||||
</SettingItem>
|
||||
</SettingSection>
|
||||
@@ -325,7 +380,7 @@ const decreaseTabSize = () => {
|
||||
}
|
||||
}
|
||||
|
||||
.font-preset-select,
|
||||
.font-family-select,
|
||||
.font-weight-select {
|
||||
min-width: 180px;
|
||||
padding: 8px 12px;
|
||||
+76
@@ -39,6 +39,13 @@ const updateSelectedKey = (event: Event) => {
|
||||
const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
||||
selectedModifiers.value[key] = !selectedModifiers.value[key];
|
||||
};
|
||||
|
||||
// 重置设置
|
||||
const resetSettings = async () => {
|
||||
if (confirm(t('settings.confirmReset'))) {
|
||||
await configStore.resetConfig();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -96,6 +103,20 @@ const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
||||
<button class="select-button">{{ t('settings.selectDirectory') }}</button>
|
||||
</div>
|
||||
</SettingSection>
|
||||
|
||||
<SettingSection :title="t('settings.dangerZone')">
|
||||
<div class="danger-zone">
|
||||
<div class="reset-section">
|
||||
<div class="reset-info">
|
||||
<h4>{{ t('settings.resetAllSettings') }}</h4>
|
||||
<p>{{ t('settings.resetDescription') }}</p>
|
||||
</div>
|
||||
<button class="reset-button" @click="resetSettings">
|
||||
{{ t('settings.reset') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</SettingSection>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -210,4 +231,59 @@ const toggleModifier = (key: keyof typeof selectedModifiers.value) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.danger-zone {
|
||||
padding: 20px;
|
||||
background-color: rgba(220, 53, 69, 0.05);
|
||||
border: 1px solid rgba(220, 53, 69, 0.2);
|
||||
border-radius: 6px;
|
||||
margin-top: 10px;
|
||||
|
||||
.reset-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 20px;
|
||||
|
||||
.reset-info {
|
||||
flex: 1;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 6px 0;
|
||||
color: #ff6b6b;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: #b0b0b0;
|
||||
font-size: 13px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
|
||||
.reset-button {
|
||||
padding: 8px 16px;
|
||||
background-color: #dc3545;
|
||||
border: 1px solid #c82333;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background-color: #c82333;
|
||||
border-color: #bd2130;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File renamed without changes.
File renamed without changes.
@@ -13,12 +13,12 @@ require (
|
||||
require (
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.2.0 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/adrg/xdg v0.5.3 // indirect
|
||||
github.com/bep/debounce v1.2.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/ebitengine/purego v0.8.3 // indirect
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
||||
@@ -33,7 +33,7 @@ require (
|
||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||
github.com/leaanthony/u v1.1.1 // indirect
|
||||
github.com/lmittmann/tint v1.0.7 // indirect
|
||||
github.com/lmittmann/tint v1.1.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
|
||||
@@ -3,8 +3,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProtonMail/go-crypto v1.2.0 h1:+PhXXn4SPGd+qk76TlEePBfOfivE0zkWFenhGhFLzWs=
|
||||
github.com/ProtonMail/go-crypto v1.2.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||
@@ -20,8 +20,8 @@ github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGL
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.8.3 h1:K+0AjQp63JEZTEMZiwsI9g0+hAMNohwUOtY0RPGexmc=
|
||||
github.com/ebitengine/purego v0.8.3/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw=
|
||||
github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
@@ -69,8 +69,8 @@ github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed
|
||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||
github.com/lmittmann/tint v1.0.7 h1:D/0OqWZ0YOGZ6AyC+5Y2kD8PBEzBk6rFHVSfOqCkF9Y=
|
||||
github.com/lmittmann/tint v1.0.7/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/lmittmann/tint v1.1.1 h1:xmmGuinUsCSxWdwH1OqMUQ4tzQsq3BdjJLAAmVKJ9Dw=
|
||||
github.com/lmittmann/tint v1.1.1/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
|
||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
// RegisterTrayEvents 注册与系统托盘相关的所有事件
|
||||
func RegisterTrayEvents(app *application.App, systray *application.SystemTray, mainWindow *application.WebviewWindow, settingsWindow *application.WebviewWindow) {
|
||||
func RegisterTrayEvents(app *application.App, systray *application.SystemTray, mainWindow *application.WebviewWindow) {
|
||||
// 不附加窗口到系统托盘,避免失去焦点自动缩小
|
||||
// systray.AttachWindow(mainWindow)
|
||||
|
||||
@@ -28,30 +28,14 @@ func RegisterTrayEvents(app *application.App, systray *application.SystemTray, m
|
||||
mainWindow.Hide()
|
||||
})
|
||||
|
||||
// 设置窗口关闭事件处理
|
||||
settingsWindow.RegisterHook(wailsevents.Common.WindowClosing, func(event *application.WindowEvent) {
|
||||
// 取消默认关闭行为
|
||||
event.Cancel()
|
||||
// 隐藏窗口
|
||||
settingsWindow.Hide()
|
||||
})
|
||||
|
||||
// 注册事件监听器,用于处理前端发送的显示设置窗口事件
|
||||
app.OnEvent("show_settings_window", func(event *application.CustomEvent) {
|
||||
settingsWindow.Show()
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterTrayMenuEvents 注册系统托盘菜单事件
|
||||
func RegisterTrayMenuEvents(app *application.App, menu *application.Menu, mainWindow *application.WebviewWindow, settingsWindow *application.WebviewWindow) {
|
||||
func RegisterTrayMenuEvents(app *application.App, menu *application.Menu, mainWindow *application.WebviewWindow) {
|
||||
menu.Add("主窗口").OnClick(func(data *application.Context) {
|
||||
mainWindow.Show()
|
||||
})
|
||||
|
||||
menu.Add("设置").OnClick(func(data *application.Context) {
|
||||
settingsWindow.Show()
|
||||
})
|
||||
|
||||
menu.AddSeparator()
|
||||
|
||||
menu.Add("退出").OnClick(func(data *application.Context) {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
type ServiceManager struct {
|
||||
configService *ConfigService
|
||||
documentService *DocumentService
|
||||
systemService *SystemService
|
||||
logger *log.LoggerService
|
||||
}
|
||||
|
||||
@@ -23,6 +24,9 @@ func NewServiceManager() *ServiceManager {
|
||||
// 初始化文档服务
|
||||
documentService := NewDocumentService(configService, logger)
|
||||
|
||||
// 初始化系统服务
|
||||
systemService := NewSystemService(logger)
|
||||
|
||||
// 初始化文档服务
|
||||
err := documentService.Initialize()
|
||||
if err != nil {
|
||||
@@ -33,6 +37,7 @@ func NewServiceManager() *ServiceManager {
|
||||
return &ServiceManager{
|
||||
configService: configService,
|
||||
documentService: documentService,
|
||||
systemService: systemService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
@@ -42,5 +47,6 @@ func (sm *ServiceManager) GetServices() []application.Service {
|
||||
return []application.Service{
|
||||
application.NewService(sm.configService),
|
||||
application.NewService(sm.documentService),
|
||||
application.NewService(sm.systemService),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||
)
|
||||
|
||||
// SystemService 系统监控服务
|
||||
type SystemService struct {
|
||||
logger *log.LoggerService
|
||||
}
|
||||
|
||||
// MemoryStats 内存统计信息
|
||||
type MemoryStats struct {
|
||||
// 当前堆内存使用量(字节)
|
||||
HeapInUse uint64 `json:"heapInUse"`
|
||||
// 堆内存分配总量(字节)
|
||||
HeapAlloc uint64 `json:"heapAlloc"`
|
||||
// 系统内存使用量(字节)
|
||||
Sys uint64 `json:"sys"`
|
||||
// GC 次数
|
||||
NumGC uint32 `json:"numGC"`
|
||||
// GC 暂停时间(纳秒)
|
||||
PauseTotalNs uint64 `json:"pauseTotalNs"`
|
||||
// Goroutine 数量
|
||||
NumGoroutine int `json:"numGoroutine"`
|
||||
}
|
||||
|
||||
// NewSystemService 创建新的系统服务实例
|
||||
func NewSystemService(logger *log.LoggerService) *SystemService {
|
||||
return &SystemService{
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMemoryStats 获取当前内存统计信息
|
||||
func (ss *SystemService) GetMemoryStats() MemoryStats {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
return MemoryStats{
|
||||
HeapInUse: m.HeapInuse,
|
||||
HeapAlloc: m.HeapAlloc,
|
||||
Sys: m.Sys,
|
||||
NumGC: m.NumGC,
|
||||
PauseTotalNs: m.PauseTotalNs,
|
||||
NumGoroutine: runtime.NumGoroutine(),
|
||||
}
|
||||
}
|
||||
|
||||
// FormatBytes 格式化字节数为人类可读的格式
|
||||
func (ss *SystemService) FormatBytes(bytes uint64) string {
|
||||
const unit = 1024
|
||||
if bytes < unit {
|
||||
return "< 1 KB"
|
||||
}
|
||||
div, exp := uint64(unit), 0
|
||||
for n := bytes / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
|
||||
switch exp {
|
||||
case 0:
|
||||
return "< 1 KB"
|
||||
case 1:
|
||||
return fmt.Sprintf("%.1f KB", float64(bytes)/float64(div))
|
||||
case 2:
|
||||
return fmt.Sprintf("%.1f MB", float64(bytes)/float64(div))
|
||||
case 3:
|
||||
return fmt.Sprintf("%.1f GB", float64(bytes)/float64(div))
|
||||
default:
|
||||
return fmt.Sprintf("%.1f TB", float64(bytes)/float64(div))
|
||||
}
|
||||
}
|
||||
|
||||
// TriggerGC 手动触发垃圾回收
|
||||
func (ss *SystemService) TriggerGC() {
|
||||
runtime.GC()
|
||||
ss.logger.Info("Manual GC triggered")
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
)
|
||||
|
||||
// SetupSystemTray 设置系统托盘及其功能
|
||||
func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, settingsWindow *application.WebviewWindow, assets embed.FS) {
|
||||
func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow, assets embed.FS) {
|
||||
// 创建系统托盘
|
||||
systray := app.NewSystemTray()
|
||||
|
||||
@@ -22,10 +22,10 @@ func SetupSystemTray(app *application.App, mainWindow *application.WebviewWindow
|
||||
menu := app.NewMenu()
|
||||
|
||||
// 注册托盘菜单事件
|
||||
events.RegisterTrayMenuEvents(app, menu, mainWindow, settingsWindow)
|
||||
events.RegisterTrayMenuEvents(app, menu, mainWindow)
|
||||
|
||||
systray.SetMenu(menu)
|
||||
|
||||
// 注册托盘相关事件
|
||||
events.RegisterTrayEvents(app, systray, mainWindow, settingsWindow)
|
||||
events.RegisterTrayEvents(app, systray, mainWindow)
|
||||
}
|
||||
@@ -88,28 +88,9 @@ func main() {
|
||||
URL: "/#/",
|
||||
})
|
||||
mainWindow.Center()
|
||||
settingsWindow := app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
|
||||
Title: "voidraft设置",
|
||||
Width: 700,
|
||||
Height: 800,
|
||||
Hidden: true,
|
||||
AlwaysOnTop: true,
|
||||
DisableResize: true,
|
||||
MinimiseButtonState: application.ButtonHidden,
|
||||
MaximiseButtonState: application.ButtonHidden,
|
||||
Mac: application.MacWindow{
|
||||
Backdrop: application.MacBackdropTranslucent,
|
||||
TitleBar: application.MacTitleBarHiddenInset,
|
||||
InvisibleTitleBarHeight: 50,
|
||||
},
|
||||
Windows: application.WindowsWindow{},
|
||||
BackgroundColour: application.NewRGB(27, 38, 54),
|
||||
URL: "/#/settings",
|
||||
})
|
||||
settingsWindow.Center()
|
||||
|
||||
// 设置系统托盘
|
||||
systray.SetupSystemTray(app, mainWindow, settingsWindow, assets)
|
||||
systray.SetupSystemTray(app, mainWindow, assets)
|
||||
|
||||
// Create a goroutine that emits an event containing the current time every second.
|
||||
// The frontend can listen to this event and update the UI accordingly.
|
||||
|
||||
Reference in new issue
Block a user