This commit is contained in:
Baobhan Sith
2025-04-19 14:05:26 +08:00
parent ea9ce19b60
commit 6f403834ac
9 changed files with 471 additions and 233 deletions
@@ -2,33 +2,40 @@ import { getDb } from '../database';
import { AppearanceSettings, UpdateAppearanceDto } from '../types/appearance.types';
import { defaultUiTheme } from '../config/default-themes'; // Assuming default UI theme is here too
const db = getDb();
// const db = getDb(); // Removed top-level call to avoid circular dependency issues
const TABLE_NAME = 'appearance_settings';
const SETTINGS_ID = 1; // Use a fixed ID for the single row of global settings
/**
* 创建 appearance_settings 表 (如果不存在)
* SQL语句:创建 appearance_settings 表
*/
export const SQL_CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY, -- Fixed ID for the single settings row
custom_ui_theme TEXT,
active_terminal_theme_id INTEGER NULL, -- 修改为 INTEGER NULL
terminal_font_family TEXT,
terminal_font_size INTEGER,
editor_font_size INTEGER, -- 新增:编辑器字体大小
terminal_background_image TEXT,
page_background_image TEXT,
updated_at INTEGER NOT NULL,
FOREIGN KEY(active_terminal_theme_id) REFERENCES terminal_themes(id) -- 添加外键约束
);
`;
/**
* 创建 appearance_settings 表 (如果不存在) - 不再自动调用
*/
const createTableIfNotExists = () => {
const sql = `
CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY, -- Fixed ID for the single settings row
custom_ui_theme TEXT,
active_terminal_theme_id TEXT,
terminal_font_family TEXT,
terminal_font_size INTEGER,
editor_font_size INTEGER, -- 新增:编辑器字体大小
terminal_background_image TEXT,
page_background_image TEXT,
updated_at INTEGER NOT NULL
);
`;
db.run(sql, (err) => {
// This function is no longer called automatically, initialization is handled in database.ts
getDb().run(SQL_CREATE_TABLE, (err) => {
if (err) {
console.error(`创建 ${TABLE_NAME} 表失败:`, err.message);
} else {
// 确保默认设置行存在
ensureDefaultSettingsExist();
console.log(`${TABLE_NAME} 表已存在或已创建。`);
// 确保默认设置行存在 - 这个调用也应该移到 database.ts
// ensureDefaultSettingsExist();
}
});
};
@@ -39,7 +46,7 @@ const mapRowToAppearanceSettings = (row: any): AppearanceSettings => {
return {
_id: row.id.toString(),
customUiTheme: row.custom_ui_theme,
activeTerminalThemeId: row.active_terminal_theme_id,
activeTerminalThemeId: row.active_terminal_theme_id, // 直接返回数字或 null
terminalFontFamily: row.terminal_font_family,
terminalFontSize: row.terminal_font_size,
editorFontSize: row.editor_font_size, // 新增:编辑器字体大小映射
@@ -58,7 +65,7 @@ const getDefaultAppearanceSettings = (): AppearanceSettings => {
return {
_id: SETTINGS_ID.toString(),
customUiTheme: JSON.stringify(defaultUiTheme), // Use default UI theme
activeTerminalThemeId: undefined, // Needs to be set after querying default theme ID
activeTerminalThemeId: null, // 初始应为 null,待 findAndSetDefaultThemeId 设置
terminalFontFamily: 'Consolas, "Courier New", monospace, "Microsoft YaHei", "微软雅黑"', // Default font
terminalFontSize: 14,
editorFontSize: 14, // 新增:默认编辑器字体大小
@@ -72,12 +79,14 @@ const getDefaultAppearanceSettings = (): AppearanceSettings => {
/**
* 确保默认设置行存在
* 确保默认设置行存在,并在需要时设置默认激活主题 ID。
* 这个函数应该在数据库初始化时,在预设主题初始化之后调用。
*/
const ensureDefaultSettingsExist = () => {
export const ensureDefaultSettingsExist = async () => { // 改为 async 以便内部 await
const defaults = getDefaultAppearanceSettings();
const sqlSelect = `SELECT id FROM ${TABLE_NAME} WHERE id = ?`;
db.get(sqlSelect, [SETTINGS_ID], (err, row) => {
const sqlSelect = `SELECT id, active_terminal_theme_id FROM ${TABLE_NAME} WHERE id = ?`; // 同时查询当前 ID
// 将回调函数改为 async
getDb().get(sqlSelect, [SETTINGS_ID], async (err, row) => {
if (err) {
console.error(`检查默认外观设置时出错:`, err.message);
return;
@@ -91,10 +100,10 @@ const ensureDefaultSettingsExist = () => {
updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) -- 调整占位符数量
`;
db.run(sqlInsert, [
SETTINGS_ID,
defaults.customUiTheme,
defaults.activeTerminalThemeId, // Initially undefined
getDb().run(sqlInsert, [
SETTINGS_ID,
defaults.customUiTheme,
defaults.activeTerminalThemeId, // Initially null
defaults.terminalFontFamily,
defaults.terminalFontSize,
defaults.editorFontSize, // 添加 editor_font_size 默认值参数
@@ -113,8 +122,8 @@ const ensureDefaultSettingsExist = () => {
}
});
} else {
// If row exists, still check if default theme ID needs setting
findAndSetDefaultThemeId();
// 如果行已存在,直接调用 findAndSetDefaultThemeId 检查并设置默认 ID
await findAndSetDefaultThemeId(); // 使用 await
}
});
};
@@ -127,19 +136,22 @@ const findAndSetDefaultThemeId = async () => {
// Find the default theme from the other table
const defaultThemeSql = `SELECT id FROM terminal_themes WHERE is_system_default = 1 LIMIT 1`;
// Explicitly type the row or use type assertion
db.get(defaultThemeSql, [], async (err, defaultThemeRow: { id: number } | undefined) => {
getDb().get(defaultThemeSql, [], async (err, defaultThemeRow: { id: number } | undefined) => {
if (err) {
console.error("查找默认终端主题 ID 失败:", err.message);
return;
}
if (defaultThemeRow) {
const defaultThemeId = defaultThemeRow.id.toString();
const defaultThemeIdNum = defaultThemeRow.id; // 直接使用数字 ID
// Check current appearance settings
const currentSettings = await getAppearanceSettings();
if (currentSettings && currentSettings.activeTerminalThemeId !== defaultThemeId) {
// Update only if the active ID is not already the default
console.log(`设置默认激活终端主题 ID: ${defaultThemeId}`);
await updateAppearanceSettings({ activeTerminalThemeId: defaultThemeId });
// Only set the default theme ID if no active theme ID is currently set (i.e., it's null in the DB)
if (currentSettings && currentSettings.activeTerminalThemeId === null) {
console.log(`数据库中未设置激活终端主题,设置为默认数字 ID: ${defaultThemeIdNum}`);
// 更新时传递数字 ID
await updateAppearanceSettings({ activeTerminalThemeId: defaultThemeIdNum });
} else {
console.log(`数据库中已设置激活终端主题数字 ID (${currentSettings?.activeTerminalThemeId}) 或未找到默认主题,跳过设置默认 ID。`);
}
} else {
console.warn("未找到系统默认终端主题,无法设置 activeTerminalThemeId。");
@@ -157,7 +169,7 @@ const findAndSetDefaultThemeId = async () => {
*/
export const getAppearanceSettings = async (): Promise<AppearanceSettings> => {
return new Promise((resolve, reject) => {
db.get(`SELECT * FROM ${TABLE_NAME} WHERE id = ?`, [SETTINGS_ID], (err, row) => {
getDb().get(`SELECT * FROM ${TABLE_NAME} WHERE id = ?`, [SETTINGS_ID], (err, row) => {
if (err) {
console.error('获取外观设置失败:', err.message);
reject(new Error('获取外观设置失败'));
@@ -180,14 +192,22 @@ export const updateAppearanceSettings = async (settingsDto: UpdateAppearanceDto)
// Dynamically build the SET part of the query
const updates: string[] = [];
for (const key in settingsDto) {
if (Object.prototype.hasOwnProperty.call(settingsDto, key)) {
const dbKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); // Convert camelCase to snake_case
// Ensure only valid keys are updated (Added editor_font_size)
if (['custom_ui_theme', 'active_terminal_theme_id', 'terminal_font_family', 'terminal_font_size', 'editor_font_size', 'terminal_background_image', 'page_background_image'].includes(dbKey)) {
updates.push(`${dbKey} = ?`);
params.push((settingsDto as any)[key]);
const validDbKeys = ['custom_ui_theme', 'active_terminal_theme_id', 'terminal_font_family', 'terminal_font_size', 'editor_font_size', 'terminal_background_image', 'page_background_image'];
// Iterate over potential keys to update
for (const key of Object.keys(settingsDto) as Array<keyof UpdateAppearanceDto>) {
const dbKey = key.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`); // Convert camelCase to snake_case
if (validDbKeys.includes(dbKey)) {
const value = settingsDto[key];
// active_terminal_theme_id 应该是数字或 null
if (dbKey === 'active_terminal_theme_id' && typeof value !== 'number' && value !== null) {
console.error(`[AppearanceRepo] 更新 active_terminal_theme_id 时收到无效类型值: ${value} (类型: ${typeof value}),应为数字或 null。跳过此字段。`);
continue; // 跳过无效类型
}
updates.push(`${dbKey} = ?`);
// 直接推入值 (数字或 null)
params.push(value);
}
}
@@ -199,16 +219,21 @@ export const updateAppearanceSettings = async (settingsDto: UpdateAppearanceDto)
params.push(SETTINGS_ID);
return new Promise((resolve, reject) => {
db.run(sql, params, function (err) {
// --- 增加详细日志 ---
console.log(`[AppearanceRepo] Executing SQL: ${sql}`);
console.log(`[AppearanceRepo] With Params: ${JSON.stringify(params)}`);
// --- 日志结束 ---
getDb().run(sql, params, function (err) {
if (err) {
console.error('更新外观设置失败:', err.message);
reject(new Error('更新外观设置失败'));
} else {
console.log(`[AppearanceRepo] 更新外观设置成功,影响行数: ${this.changes}`);
resolve(this.changes > 0);
}
});
});
};
// 初始化时创建表
createTableIfNotExists();
// 初始化时创建表 - Removed: Initialization is now handled in database.ts
// createTableIfNotExists();
@@ -2,29 +2,34 @@ import { getDb } from '../database';
import { TerminalTheme, CreateTerminalThemeDto, UpdateTerminalThemeDto } from '../types/terminal-theme.types';
import { defaultXtermTheme } from '../config/default-themes'; // 假设默认主题配置在此
const db = getDb();
// const db = getDb(); // Removed top-level call to avoid circular dependency issues
/**
* 创建 terminal_themes 表 (如果不存在)
* SQL语句:创建 terminal_themes 表
*/
export const SQL_CREATE_TABLE = `
CREATE TABLE IF NOT EXISTS terminal_themes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
theme_data TEXT NOT NULL, -- Store ITheme as JSON string
is_preset BOOLEAN NOT NULL DEFAULT 0,
preset_key TEXT NULL UNIQUE, -- 可选,用于识别预设主题
is_system_default BOOLEAN NOT NULL DEFAULT 0, -- 新增:标记是否为系统默认主题
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
`;
/**
* 创建 terminal_themes 表 (如果不存在) - 不再自动调用
*/
const createTableIfNotExists = () => {
const sql = `
CREATE TABLE IF NOT EXISTS terminal_themes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
theme_data TEXT NOT NULL, -- Store ITheme as JSON string
is_preset BOOLEAN NOT NULL DEFAULT 0,
is_system_default BOOLEAN DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
`;
db.run(sql, (err) => {
// This function is no longer called automatically, initialization is handled in database.ts
getDb().run(SQL_CREATE_TABLE, (err) => {
if (err) {
console.error('创建 terminal_themes 表失败:', err.message);
} else {
// 表创建成功后,初始化预设主题
initializePresetThemes();
console.log('terminal_themes 表已存在或已创建。');
}
});
};
@@ -36,7 +41,7 @@ const mapRowToTerminalTheme = (row: any): TerminalTheme => {
name: row.name,
themeData: JSON.parse(row.theme_data), // 解析 JSON 字符串
isPreset: !!row.is_preset, // 转换为布尔值
isSystemDefault: !!row.is_system_default,
isSystemDefault: !!row.is_system_default, // 映射新增的列
createdAt: row.created_at,
updatedAt: row.updated_at,
};
@@ -48,7 +53,7 @@ const mapRowToTerminalTheme = (row: any): TerminalTheme => {
*/
export const findAllThemes = async (): Promise<TerminalTheme[]> => {
return new Promise((resolve, reject) => {
db.all('SELECT * FROM terminal_themes ORDER BY is_preset DESC, name ASC', [], (err, rows) => {
getDb().all('SELECT * FROM terminal_themes ORDER BY is_preset DESC, name ASC', [], (err, rows) => {
if (err) {
console.error('查询所有终端主题失败:', err.message);
reject(new Error('查询终端主题失败'));
@@ -66,7 +71,7 @@ export const findAllThemes = async (): Promise<TerminalTheme[]> => {
*/
export const findThemeById = async (id: number): Promise<TerminalTheme | null> => {
return new Promise((resolve, reject) => {
db.get('SELECT * FROM terminal_themes WHERE id = ?', [id], (err, row) => {
getDb().get('SELECT * FROM terminal_themes WHERE id = ?', [id], (err, row) => {
if (err) {
console.error(`查询 ID 为 ${id} 的终端主题失败:`, err.message);
reject(new Error('查询终端主题失败'));
@@ -90,7 +95,7 @@ export const createTheme = async (themeDto: CreateTerminalThemeDto): Promise<Ter
VALUES (?, ?, 0, ?, ?)
`;
return new Promise((resolve, reject) => {
db.run(sql, [themeDto.name, themeDataJson, now, now], function (err) {
getDb().run(sql, [themeDto.name, themeDataJson, now, now], function (err) {
if (err) {
console.error('创建新终端主题失败:', err.message);
// 特别处理唯一约束错误
@@ -132,7 +137,7 @@ export const updateTheme = async (id: number, themeDto: UpdateTerminalThemeDto):
WHERE id = ? AND is_preset = 0
`;
return new Promise((resolve, reject) => {
db.run(sql, [themeDto.name, themeDataJson, now, id], function (err) {
getDb().run(sql, [themeDto.name, themeDataJson, now, id], function (err) {
if (err) {
console.error(`更新 ID 为 ${id} 的终端主题失败:`, err.message);
if (err.message.includes('UNIQUE constraint failed')) {
@@ -156,7 +161,7 @@ export const deleteTheme = async (id: number): Promise<boolean> => {
// 只允许删除非预设主题
const sql = 'DELETE FROM terminal_themes WHERE id = ? AND is_preset = 0';
return new Promise((resolve, reject) => {
db.run(sql, [id], function (err) {
getDb().run(sql, [id], function (err) {
if (err) {
console.error(`删除 ID 为 ${id} 的终端主题失败:`, err.message);
reject(new Error('删除终端主题失败'));
@@ -168,36 +173,62 @@ export const deleteTheme = async (id: number): Promise<boolean> => {
};
/**
* 初始化预设主题 (如果不存在)
* 初始化预设主题到数据库 (如果不存在)
* 这个函数应该在数据库连接成功后,由应用初始化逻辑调用。
* @param presets 预设主题定义数组 (包含 name, themeData, isPreset=true, 可选 preset_key)
*/
export const initializePresetThemes = async () => {
const defaultPresetName = '默认'; // Default Light
const themeDataJson = JSON.stringify(defaultXtermTheme);
const now = Date.now();
export const initializePresetThemes = async (presets: Array<Omit<TerminalTheme, '_id' | 'createdAt' | 'updatedAt'> & { preset_key?: string }>) => {
console.log('[DB Init] 开始检查并初始化预设主题...');
const now = Date.now();
// 检查默认预设是否存在
db.get('SELECT id FROM terminal_themes WHERE name = ? AND is_preset = 1', [defaultPresetName], (err, row) => {
if (err) {
console.error('检查预设主题时出错:', err.message);
return;
// 使用 for...of 循环确保顺序执行检查和插入(避免并发 UNIQUE 约束问题)
for (const preset of presets) {
await new Promise<void>((resolve, reject) => {
// 优先使用 preset_key 检查,如果提供了的话
const checkColumn = preset.preset_key ? 'preset_key' : 'name';
const checkValue = preset.preset_key ?? preset.name;
getDb().get(`SELECT id FROM terminal_themes WHERE ${checkColumn} = ? AND is_preset = 1`, [checkValue], (err, row) => {
if (err) {
console.error(`[DB Init] 检查预设主题 "${preset.name}" (Key: ${checkValue}) 时出错:`, err.message);
return reject(err);
}
if (!row) {
const themeDataJson = JSON.stringify(preset.themeData);
const isDefault = preset.preset_key === 'default' ? 1 : 0;
// 始终包含 preset_key 列,如果不存在则插入 NULL
const columns = ['name', 'theme_data', 'is_preset', 'is_system_default', 'preset_key', 'created_at', 'updated_at']; // 7 columns
const values = [preset.name, themeDataJson, 1, isDefault, preset.preset_key ?? null, now, now]; // 7 values
const placeholders = ['?', '?', '?', '?', '?', '?', '?']; // 7 placeholders
// 移除动态添加 preset_key 的逻辑
// if (preset.preset_key) {
// values.push(preset.preset_key);
// placeholders.push('?');
// }
const insertSql = `
INSERT INTO terminal_themes (${columns.join(', ')})
VALUES (${placeholders.join(', ')})
`;
getDb().run(insertSql, values, (insertErr) => {
if (insertErr) {
console.error(`[DB Init] 初始化预设主题 "${preset.name}" (Key: ${preset.preset_key ?? 'N/A'}) 失败:`, insertErr.message); // 调整日志输出
return reject(insertErr);
} else {
console.log(`[DB Init] 预设主题 "${preset.name}" (Key: ${checkValue}) 已初始化到数据库。`);
resolve();
}
});
} else {
// console.log(`[DB Init] 预设主题 "${preset.name}" (Key: ${checkValue}) 已存在,跳过初始化。`);
resolve();
}
});
});
}
if (!row) {
// 如果不存在,则插入
const insertSql = `
INSERT INTO terminal_themes (name, theme_data, is_preset, is_system_default, created_at, updated_at)
VALUES (?, ?, 1, 1, ?, ?)
`;
db.run(insertSql, [defaultPresetName, themeDataJson, now, now], (insertErr) => {
if (insertErr) {
console.error(`初始化预设主题 "${defaultPresetName}" 失败:`, insertErr.message);
} else {
console.log(`预设主题 "${defaultPresetName}" 已初始化。`);
}
});
}
// 在这里可以添加更多预设主题的初始化逻辑
});
console.log('[DB Init] 预设主题检查和初始化完成。');
};
// 初始化时创建表
createTableIfNotExists();
// 移除所有在此文件中的初始化调用和相关导入,它们应该在 database.ts 或 app.ts 中进行