update
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
import type { ITheme } from 'xterm';
|
||||
import type { TerminalTheme } from '../types/terminal-theme.types';
|
||||
|
||||
// 定义基础的 ITheme 结构,用于类型提示
|
||||
interface PresetITheme extends ITheme {
|
||||
name?: string; // itermcolors 文件可能包含 name
|
||||
}
|
||||
|
||||
// 定义预设主题数组的类型,确保包含 preset_key
|
||||
type PresetThemeDefinition = Omit<TerminalTheme, '_id' | 'createdAt' | 'updatedAt'> & { preset_key: string };
|
||||
|
||||
// 默认主题 (可以从 default-themes.ts 导入或直接定义)
|
||||
const defaultThemeData: PresetITheme = {
|
||||
background: '#ffffff',
|
||||
foreground: '#000000',
|
||||
cursor: '#000000',
|
||||
cursorAccent: '#ffffff',
|
||||
selectionBackground: '#a7a7a7', // 使用更柔和的灰色作为选择背景
|
||||
black: '#000000',
|
||||
red: '#c50f1f',
|
||||
green: '#13a10e',
|
||||
yellow: '#c19c00',
|
||||
blue: '#0037da',
|
||||
magenta: '#881798',
|
||||
cyan: '#3a96dd',
|
||||
white: '#cccccc',
|
||||
brightBlack: '#767676',
|
||||
brightRed: '#e74856',
|
||||
brightGreen: '#16c60c',
|
||||
brightYellow: '#f9f1a5',
|
||||
brightBlue: '#3b78ff',
|
||||
brightMagenta: '#b4009e',
|
||||
brightCyan: '#61d6d6',
|
||||
brightWhite: '#ffffff',
|
||||
};
|
||||
|
||||
// 其他预设主题 (从 iterm-themes.ts 迁移并添加 preset_key)
|
||||
// 注意:需要将原 iterm-themes.ts 中的主题数据转换为 ITheme 格式
|
||||
// 这里仅作示例,你需要将实际的主题数据填充进来
|
||||
|
||||
const solarizedDark: PresetITheme = {
|
||||
background: '#002b36',
|
||||
foreground: '#839496',
|
||||
cursor: '#839496',
|
||||
selectionBackground: '#073642',
|
||||
black: '#073642',
|
||||
red: '#dc322f',
|
||||
green: '#859900',
|
||||
yellow: '#b58900',
|
||||
blue: '#268bd2',
|
||||
magenta: '#d33682',
|
||||
cyan: '#2aa198',
|
||||
white: '#eee8d5',
|
||||
brightBlack: '#002b36',
|
||||
brightRed: '#cb4b16',
|
||||
brightGreen: '#586e75',
|
||||
brightYellow: '#657b83',
|
||||
brightBlue: '#839496',
|
||||
brightMagenta: '#6c71c4', // Note: Original Solarized might use a different magenta
|
||||
brightCyan: '#93a1a1',
|
||||
brightWhite: '#fdf6e3',
|
||||
};
|
||||
|
||||
const oneDark: PresetITheme = {
|
||||
foreground: '#abb2bf',
|
||||
background: '#282c34',
|
||||
cursor: '#abb2bf',
|
||||
selectionBackground: '#3e4451', // A slightly lighter background for selection
|
||||
black: '#282c34', // Often same as background
|
||||
red: '#e06c75',
|
||||
green: '#98c379',
|
||||
yellow: '#e5c07b',
|
||||
blue: '#61afef',
|
||||
magenta: '#c678dd',
|
||||
cyan: '#56b6c2',
|
||||
white: '#abb2bf', // Often same as foreground
|
||||
brightBlack: '#5c6370', // A darker grey
|
||||
brightRed: '#e06c75', // Often same as normal red
|
||||
brightGreen: '#98c379', // Often same as normal green
|
||||
brightYellow: '#e5c07b', // Often same as normal yellow
|
||||
brightBlue: '#61afef', // Often same as normal blue
|
||||
brightMagenta: '#c678dd', // Often same as normal magenta
|
||||
brightCyan: '#56b6c2', // Often same as normal cyan
|
||||
brightWhite: '#ffffff', // Pure white for bright white
|
||||
};
|
||||
|
||||
// ... (添加其他所有需要的预设主题)
|
||||
|
||||
// 导出预设主题数组
|
||||
export const presetTerminalThemes: PresetThemeDefinition[] = [
|
||||
{
|
||||
preset_key: 'default', // 指定一个唯一的 key
|
||||
name: '默认', // 显示名称
|
||||
themeData: defaultThemeData,
|
||||
isPreset: true,
|
||||
},
|
||||
{
|
||||
preset_key: 'solarized-dark',
|
||||
name: 'Solarized Dark',
|
||||
themeData: solarizedDark,
|
||||
isPreset: true,
|
||||
},
|
||||
{
|
||||
preset_key: 'one-dark',
|
||||
name: 'One Dark',
|
||||
themeData: oneDark,
|
||||
isPreset: true,
|
||||
},
|
||||
// ... (添加其他所有预设主题对象)
|
||||
];
|
||||
@@ -1,9 +1,14 @@
|
||||
import sqlite3 from 'sqlite3';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
// 导入 Repository 模块以调用其初始化相关函数
|
||||
import * as appearanceRepository from './repositories/appearance.repository';
|
||||
import * as terminalThemeRepository from './repositories/terminal-theme.repository';
|
||||
// 导入预设主题定义
|
||||
import { presetTerminalThemes } from './config/preset-themes-definition';
|
||||
|
||||
// 数据库文件路径 (相对于 backend 项目根目录)
|
||||
const dbDir = path.resolve(__dirname, '../../data'); // 使用 '../../data' 定位到 monorepo 根目录下的 data 文件夹
|
||||
// 数据库文件路径
|
||||
const dbDir = path.resolve(__dirname, '../../data');
|
||||
const dbPath = path.join(dbDir, 'nexus-terminal.db');
|
||||
|
||||
// 确保数据库目录存在
|
||||
@@ -12,44 +17,88 @@ if (!fs.existsSync(dbDir)) {
|
||||
console.log(`数据库目录已创建: ${dbDir}`);
|
||||
}
|
||||
|
||||
// 使用详细模式以获得更丰富的错误信息
|
||||
const verboseSqlite3 = sqlite3.verbose();
|
||||
|
||||
// 创建并连接数据库
|
||||
// 使用单例 (singleton) 模式确保只有一个数据库连接实例
|
||||
let dbInstance: sqlite3.Database | null = null;
|
||||
|
||||
/**
|
||||
* 执行数据库初始化序列:创建表、插入预设主题、设置默认外观。
|
||||
* @param db 数据库实例
|
||||
*/
|
||||
const runDatabaseInitializations = (db: sqlite3.Database) => {
|
||||
db.serialize(() => {
|
||||
console.log('[DB Init] 开始数据库初始化序列...');
|
||||
|
||||
// 1. 启用外键约束 (必须在事务之外或序列化块的开始处)
|
||||
db.run('PRAGMA foreign_keys = ON;', (pragmaErr) => {
|
||||
if (pragmaErr) {
|
||||
console.error('[DB Init] 启用外键约束失败:', pragmaErr.message);
|
||||
// 根据需要处理错误,可能需要阻止后续初始化
|
||||
} else {
|
||||
console.log('[DB Init] 外键约束已启用。');
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 创建 terminal_themes 表
|
||||
db.run(terminalThemeRepository.SQL_CREATE_TABLE, (err) => {
|
||||
if (err) {
|
||||
console.error('[DB Init] 创建 terminal_themes 表失败:', err.message);
|
||||
} else {
|
||||
console.log('[DB Init] terminal_themes 表已存在或已创建。');
|
||||
// 3. 初始化预设主题 (只有在表创建成功或已存在后才执行)
|
||||
terminalThemeRepository.initializePresetThemes(presetTerminalThemes)
|
||||
.then(() => console.log('[DB Init] 预设主题初始化检查完成。'))
|
||||
.catch(initErr => console.error('[DB Init] 初始化预设主题时出错:', initErr));
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 创建 appearance_settings 表
|
||||
db.run(appearanceRepository.SQL_CREATE_TABLE, (err) => {
|
||||
if (err) {
|
||||
console.error('[DB Init] 创建 appearance_settings 表失败:', err.message);
|
||||
} else {
|
||||
console.log('[DB Init] appearance_settings 表已存在或已创建。');
|
||||
// 5. 确保默认设置存在并设置默认激活主题 (只有在表创建成功或已存在后才执行)
|
||||
// 注意:这需要等待预设主题初始化完成,但 serialize 会保证顺序
|
||||
appearanceRepository.ensureDefaultSettingsExist()
|
||||
.then(() => console.log('[DB Init] 外观设置初始化检查完成。'))
|
||||
.catch(initErr => console.error('[DB Init] 初始化外观设置时出错:', initErr));
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: 在这里添加其他表的创建和初始化逻辑...
|
||||
|
||||
console.log('[DB Init] 数据库初始化序列提交。');
|
||||
});
|
||||
};
|
||||
|
||||
export const getDb = (): sqlite3.Database => {
|
||||
if (!dbInstance) {
|
||||
console.log(`[DB] 尝试连接到数据库: ${dbPath}`);
|
||||
dbInstance = new verboseSqlite3.Database(dbPath, (err) => {
|
||||
if (err) {
|
||||
console.error('打开数据库时出错:', err.message);
|
||||
// 在实际应用中,这里可能需要更健壮的错误处理,例如直接退出进程
|
||||
console.error('[DB] 打开数据库时出错:', err.message);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`已连接到 SQLite 数据库: ${dbPath}`);
|
||||
// 可选:启用外键约束 (如果数据库设计中使用了外键)
|
||||
// dbInstance.run('PRAGMA foreign_keys = ON;', (pragmaErr) => {
|
||||
// if (pragmaErr) {
|
||||
// console.error('启用外键约束失败:', pragmaErr.message);
|
||||
// }
|
||||
// });
|
||||
console.log(`[DB] 已连接到 SQLite 数据库: ${dbPath}`);
|
||||
// 连接成功后运行初始化逻辑
|
||||
runDatabaseInitializations(dbInstance as sqlite3.Database);
|
||||
}
|
||||
});
|
||||
}
|
||||
return dbInstance;
|
||||
};
|
||||
|
||||
// 优雅停机:在应用接收到中断信号 (如 Ctrl+C) 时关闭数据库连接
|
||||
// 优雅停机
|
||||
process.on('SIGINT', () => {
|
||||
if (dbInstance) {
|
||||
console.log('[DB] 收到 SIGINT,正在关闭数据库连接...');
|
||||
dbInstance.close((err) => {
|
||||
if (err) {
|
||||
console.error('关闭数据库时出错:', err.message);
|
||||
console.error('[DB] 关闭数据库时出错:', err.message);
|
||||
} else {
|
||||
console.log('数据库连接已关闭。');
|
||||
console.log('[DB] 数据库连接已关闭。');
|
||||
}
|
||||
process.exit(0);
|
||||
process.exit(err ? 1 : 0);
|
||||
});
|
||||
} else {
|
||||
process.exit(0);
|
||||
@@ -57,3 +106,8 @@ process.on('SIGINT', () => {
|
||||
});
|
||||
|
||||
export default getDb;
|
||||
|
||||
// 注意:为了让这个文件工作,需要修改 repository 文件:
|
||||
// 1. 从 repository 文件中移除顶层的 createTableIfNotExists() 调用。
|
||||
// 2. 从 repository 文件中导出 SQL_CREATE_TABLE 字符串常量。
|
||||
// 3. 从 appearance.repository.ts 导出 ensureDefaultSettingsExist 函数。
|
||||
|
||||
@@ -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 中进行
|
||||
|
||||
@@ -18,24 +18,28 @@ export const getSettings = async (): Promise<AppearanceSettings> => {
|
||||
export const updateSettings = async (settingsDto: UpdateAppearanceDto): Promise<boolean> => {
|
||||
// 验证 activeTerminalThemeId (如果提供了)
|
||||
if (settingsDto.activeTerminalThemeId !== undefined && settingsDto.activeTerminalThemeId !== null) {
|
||||
const themeIdNum = parseInt(settingsDto.activeTerminalThemeId, 10);
|
||||
if (isNaN(themeIdNum)) {
|
||||
throw new Error(`无效的终端主题 ID 格式: ${settingsDto.activeTerminalThemeId}`);
|
||||
const themeIdNum = settingsDto.activeTerminalThemeId; // ID is now number | null
|
||||
// 验证 ID 是否为有效的数字
|
||||
if (typeof themeIdNum !== 'number') {
|
||||
console.error(`[AppearanceService] 收到的 activeTerminalThemeId 不是有效的数字: ${themeIdNum}`);
|
||||
throw new Error(`无效的终端主题 ID 类型,应为数字。`);
|
||||
}
|
||||
try {
|
||||
// 直接使用数字 ID 调用 findThemeById 进行验证
|
||||
const themeExists = await terminalThemeRepository.findThemeById(themeIdNum);
|
||||
if (!themeExists) {
|
||||
throw new Error(`指定的终端主题 ID 不存在: ${settingsDto.activeTerminalThemeId}`);
|
||||
console.warn(`[AppearanceService] 尝试更新为不存在的终端主题数字 ID: ${themeIdNum}`);
|
||||
throw new Error(`指定的终端主题 ID 不存在: ${themeIdNum}`);
|
||||
}
|
||||
} catch (e: any) { // Catch potential errors from findThemeById as well
|
||||
console.error(`验证终端主题 ID (${settingsDto.activeTerminalThemeId}) 时出错:`, e.message);
|
||||
// Rethrow a more specific error or the original one
|
||||
throw new Error(`验证终端主题 ID 时出错: ${e.message || settingsDto.activeTerminalThemeId}`);
|
||||
console.log(`[AppearanceService] 终端主题数字 ID ${themeIdNum} 验证通过。`);
|
||||
} catch (e: any) {
|
||||
console.error(`[AppearanceService] 验证终端主题数字 ID (${themeIdNum}) 时出错:`, e.message);
|
||||
throw new Error(`验证终端主题 ID 时出错: ${e.message || themeIdNum}`);
|
||||
}
|
||||
} else if (settingsDto.hasOwnProperty('activeTerminalThemeId')) {
|
||||
// Handle explicit setting to null/undefined (meaning reset to default/no theme)
|
||||
// The repository update logic handles null/undefined correctly, so no specific validation needed here.
|
||||
// We just need to ensure the key exists in the DTO if it's meant to be cleared.
|
||||
} else if (settingsDto.hasOwnProperty('activeTerminalThemeId') && settingsDto.activeTerminalThemeId === null) {
|
||||
// 处理显式设置为 null (表示重置为默认/无用户主题)
|
||||
console.log(`[AppearanceService] 接收到将 activeTerminalThemeId 设置为 null 的请求。`);
|
||||
// 仓库层会处理 null
|
||||
}
|
||||
|
||||
// 验证 terminalFontSize (如果提供了)
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface AppearanceSettings {
|
||||
_id?: string; // 通常只有一个文档,ID 固定或不使用
|
||||
userId?: string; // 如果需要区分用户设置 (当前假设为全局)
|
||||
customUiTheme?: string; // UI 主题 (CSS 变量 JSON 字符串)
|
||||
activeTerminalThemeId?: string; // 当前激活的终端主题 ID (对应 terminal_themes 表的 _id)
|
||||
activeTerminalThemeId?: number | null; // 修改为数字 ID 或 null
|
||||
terminalFontFamily?: string; // 终端字体列表字符串
|
||||
terminalFontSize?: number; // 终端字体大小 (px)
|
||||
terminalBackgroundImage?: string; // 终端背景图片 URL 或路径
|
||||
|
||||
Reference in New Issue
Block a user