update
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
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 TABLE_NAME = 'appearance_settings';
|
||||
const SETTINGS_ID = 1; // Use a fixed ID for the single row of global settings
|
||||
|
||||
/**
|
||||
* 创建 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_background_image TEXT,
|
||||
terminal_background_opacity REAL, -- Use REAL for floating point numbers
|
||||
page_background_image TEXT,
|
||||
page_background_opacity REAL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
`;
|
||||
db.run(sql, (err) => {
|
||||
if (err) {
|
||||
console.error(`创建 ${TABLE_NAME} 表失败:`, err.message);
|
||||
} else {
|
||||
// 确保默认设置行存在
|
||||
ensureDefaultSettingsExist();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 辅助函数:将数据库行转换为 AppearanceSettings 对象
|
||||
const mapRowToAppearanceSettings = (row: any): AppearanceSettings => {
|
||||
if (!row) return getDefaultAppearanceSettings(); // Return default if no row found
|
||||
return {
|
||||
_id: row.id.toString(),
|
||||
customUiTheme: row.custom_ui_theme,
|
||||
activeTerminalThemeId: row.active_terminal_theme_id,
|
||||
terminalFontFamily: row.terminal_font_family,
|
||||
terminalBackgroundImage: row.terminal_background_image,
|
||||
terminalBackgroundOpacity: row.terminal_background_opacity,
|
||||
pageBackgroundImage: row.page_background_image,
|
||||
pageBackgroundOpacity: row.page_background_opacity,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
};
|
||||
|
||||
// 获取默认外观设置
|
||||
const getDefaultAppearanceSettings = (): AppearanceSettings => {
|
||||
// TODO: Find the ID of the default preset theme from terminal_themes table later
|
||||
// For now, leave activeTerminalThemeId null or undefined
|
||||
return {
|
||||
_id: SETTINGS_ID.toString(),
|
||||
customUiTheme: JSON.stringify(defaultUiTheme), // Use default UI theme
|
||||
activeTerminalThemeId: undefined, // Needs to be set after querying default theme ID
|
||||
terminalFontFamily: 'Consolas, "Courier New", monospace, "Microsoft YaHei", "微软雅黑"', // Default font
|
||||
terminalBackgroundImage: undefined,
|
||||
terminalBackgroundOpacity: 1.0,
|
||||
pageBackgroundImage: undefined,
|
||||
pageBackgroundOpacity: 1.0,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 确保默认设置行存在
|
||||
*/
|
||||
const ensureDefaultSettingsExist = () => {
|
||||
const defaults = getDefaultAppearanceSettings();
|
||||
const sqlSelect = `SELECT id FROM ${TABLE_NAME} WHERE id = ?`;
|
||||
db.get(sqlSelect, [SETTINGS_ID], (err, row) => {
|
||||
if (err) {
|
||||
console.error(`检查默认外观设置时出错:`, err.message);
|
||||
return;
|
||||
}
|
||||
if (!row) {
|
||||
const sqlInsert = `
|
||||
INSERT INTO ${TABLE_NAME} (
|
||||
id, custom_ui_theme, active_terminal_theme_id, terminal_font_family,
|
||||
terminal_background_image, terminal_background_opacity,
|
||||
page_background_image, page_background_opacity, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
db.run(sqlInsert, [
|
||||
SETTINGS_ID,
|
||||
defaults.customUiTheme,
|
||||
defaults.activeTerminalThemeId, // Initially undefined
|
||||
defaults.terminalFontFamily,
|
||||
defaults.terminalBackgroundImage,
|
||||
defaults.terminalBackgroundOpacity,
|
||||
defaults.pageBackgroundImage,
|
||||
defaults.pageBackgroundOpacity,
|
||||
defaults.updatedAt
|
||||
], (insertErr) => {
|
||||
if (insertErr) {
|
||||
console.error('插入默认外观设置失败:', insertErr.message);
|
||||
} else {
|
||||
console.log('默认外观设置已初始化。');
|
||||
// Now try to find and set the default theme ID
|
||||
findAndSetDefaultThemeId();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// If row exists, still check if default theme ID needs setting
|
||||
findAndSetDefaultThemeId();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查找默认终端主题 ID 并更新外观设置
|
||||
*/
|
||||
const findAndSetDefaultThemeId = async () => {
|
||||
try {
|
||||
// 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) => {
|
||||
if (err) {
|
||||
console.error("查找默认终端主题 ID 失败:", err.message);
|
||||
return;
|
||||
}
|
||||
if (defaultThemeRow) {
|
||||
const defaultThemeId = defaultThemeRow.id.toString();
|
||||
// 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 });
|
||||
}
|
||||
} else {
|
||||
console.warn("未找到系统默认终端主题,无法设置 activeTerminalThemeId。");
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("设置默认终端主题 ID 时出错:", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 获取外观设置
|
||||
* @returns Promise<AppearanceSettings>
|
||||
*/
|
||||
export const getAppearanceSettings = async (): Promise<AppearanceSettings> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
db.get(`SELECT * FROM ${TABLE_NAME} WHERE id = ?`, [SETTINGS_ID], (err, row) => {
|
||||
if (err) {
|
||||
console.error('获取外观设置失败:', err.message);
|
||||
reject(new Error('获取外观设置失败'));
|
||||
} else {
|
||||
resolve(mapRowToAppearanceSettings(row));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新外观设置
|
||||
* @param settingsDto 更新的数据
|
||||
* @returns Promise<boolean> 是否成功更新
|
||||
*/
|
||||
export const updateAppearanceSettings = async (settingsDto: UpdateAppearanceDto): Promise<boolean> => {
|
||||
const now = Date.now();
|
||||
let sql = `UPDATE ${TABLE_NAME} SET updated_at = ?`;
|
||||
const params: any[] = [now];
|
||||
|
||||
// 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
|
||||
if (['custom_ui_theme', 'active_terminal_theme_id', 'terminal_font_family', 'terminal_background_image', 'terminal_background_opacity', 'page_background_image', 'page_background_opacity'].includes(dbKey)) {
|
||||
updates.push(`${dbKey} = ?`);
|
||||
params.push((settingsDto as any)[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return true; // Nothing to update
|
||||
}
|
||||
|
||||
sql += `, ${updates.join(', ')} WHERE id = ?`;
|
||||
params.push(SETTINGS_ID);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, params, function (err) {
|
||||
if (err) {
|
||||
console.error('更新外观设置失败:', err.message);
|
||||
reject(new Error('更新外观设置失败'));
|
||||
} else {
|
||||
resolve(this.changes > 0);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化时创建表
|
||||
createTableIfNotExists();
|
||||
@@ -0,0 +1,203 @@
|
||||
import { getDb } from '../database';
|
||||
import { TerminalTheme, CreateTerminalThemeDto, UpdateTerminalThemeDto } from '../types/terminal-theme.types';
|
||||
import { defaultXtermTheme } from '../config/default-themes'; // 假设默认主题配置在此
|
||||
|
||||
const db = getDb();
|
||||
|
||||
/**
|
||||
* 创建 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) => {
|
||||
if (err) {
|
||||
console.error('创建 terminal_themes 表失败:', err.message);
|
||||
} else {
|
||||
// 表创建成功后,初始化预设主题
|
||||
initializePresetThemes();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 辅助函数:将数据库行转换为 TerminalTheme 对象
|
||||
const mapRowToTerminalTheme = (row: any): TerminalTheme => {
|
||||
return {
|
||||
_id: row.id.toString(), // SQLite ID 是数字,转换为字符串以匹配 NeDB 风格
|
||||
name: row.name,
|
||||
themeData: JSON.parse(row.theme_data), // 解析 JSON 字符串
|
||||
isPreset: !!row.is_preset, // 转换为布尔值
|
||||
isSystemDefault: !!row.is_system_default,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 查找所有终端主题
|
||||
* @returns Promise<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) => {
|
||||
if (err) {
|
||||
console.error('查询所有终端主题失败:', err.message);
|
||||
reject(new Error('查询终端主题失败'));
|
||||
} else {
|
||||
resolve(rows.map(mapRowToTerminalTheme));
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据 ID 查找终端主题
|
||||
* @param id 主题 ID (注意:这里是 SQLite 的数字 ID)
|
||||
* @returns Promise<TerminalTheme | null>
|
||||
*/
|
||||
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) => {
|
||||
if (err) {
|
||||
console.error(`查询 ID 为 ${id} 的终端主题失败:`, err.message);
|
||||
reject(new Error('查询终端主题失败'));
|
||||
} else {
|
||||
resolve(row ? mapRowToTerminalTheme(row) : null);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建一个新的终端主题
|
||||
* @param themeDto 创建主题所需的数据
|
||||
* @returns Promise<TerminalTheme> 新创建的主题
|
||||
*/
|
||||
export const createTheme = async (themeDto: CreateTerminalThemeDto): Promise<TerminalTheme> => {
|
||||
const now = Date.now();
|
||||
const themeDataJson = JSON.stringify(themeDto.themeData); // 将 ITheme 转换为 JSON 字符串
|
||||
const sql = `
|
||||
INSERT INTO terminal_themes (name, theme_data, is_preset, created_at, updated_at)
|
||||
VALUES (?, ?, 0, ?, ?)
|
||||
`;
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, [themeDto.name, themeDataJson, now, now], function (err) {
|
||||
if (err) {
|
||||
console.error('创建新终端主题失败:', err.message);
|
||||
// 特别处理唯一约束错误
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
reject(new Error(`主题名称 "${themeDto.name}" 已存在。`));
|
||||
} else {
|
||||
reject(new Error('创建终端主题失败'));
|
||||
}
|
||||
} else {
|
||||
// 获取新插入行的 ID 并查询返回完整对象
|
||||
findThemeById(this.lastID)
|
||||
.then(newTheme => {
|
||||
if (newTheme) {
|
||||
resolve(newTheme);
|
||||
} else {
|
||||
// 理论上不应该发生,但作为回退
|
||||
reject(new Error('创建主题后未能检索到该主题'));
|
||||
}
|
||||
})
|
||||
.catch(reject);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新一个终端主题
|
||||
* @param id 要更新的主题 ID (SQLite 数字 ID)
|
||||
* @param themeDto 更新的数据
|
||||
* @returns Promise<boolean> 是否成功更新
|
||||
*/
|
||||
export const updateTheme = async (id: number, themeDto: UpdateTerminalThemeDto): Promise<boolean> => {
|
||||
const now = Date.now();
|
||||
const themeDataJson = JSON.stringify(themeDto.themeData);
|
||||
// 只允许更新非预设主题的 name 和 theme_data
|
||||
const sql = `
|
||||
UPDATE terminal_themes
|
||||
SET name = ?, theme_data = ?, updated_at = ?
|
||||
WHERE id = ? AND is_preset = 0
|
||||
`;
|
||||
return new Promise((resolve, reject) => {
|
||||
db.run(sql, [themeDto.name, themeDataJson, now, id], function (err) {
|
||||
if (err) {
|
||||
console.error(`更新 ID 为 ${id} 的终端主题失败:`, err.message);
|
||||
if (err.message.includes('UNIQUE constraint failed')) {
|
||||
reject(new Error(`主题名称 "${themeDto.name}" 已存在。`));
|
||||
} else {
|
||||
reject(new Error('更新终端主题失败'));
|
||||
}
|
||||
} else {
|
||||
resolve(this.changes > 0); // 如果有行被改变,则更新成功
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除一个终端主题
|
||||
* @param id 要删除的主题 ID (SQLite 数字 ID)
|
||||
* @returns Promise<boolean> 是否成功删除
|
||||
*/
|
||||
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) {
|
||||
if (err) {
|
||||
console.error(`删除 ID 为 ${id} 的终端主题失败:`, err.message);
|
||||
reject(new Error('删除终端主题失败'));
|
||||
} else {
|
||||
resolve(this.changes > 0); // 如果有行被改变,则删除成功
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化预设主题 (如果不存在)
|
||||
*/
|
||||
export const initializePresetThemes = async () => {
|
||||
const defaultPresetName = '默认暗色'; // Default Dark
|
||||
const themeDataJson = JSON.stringify(defaultXtermTheme);
|
||||
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;
|
||||
}
|
||||
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}" 已初始化。`);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 在这里可以添加更多预设主题的初始化逻辑
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化时创建表
|
||||
createTableIfNotExists();
|
||||
Reference in New Issue
Block a user