feat: 添加终端样式预览功能
This commit is contained in:
@@ -918,52 +918,91 @@ let originalModalContentOpacity: string | null = null;
|
|||||||
let originalModalRootTransition: string | null = null;
|
let originalModalRootTransition: string | null = null;
|
||||||
let originalModalContentTransition: string | null = null;
|
let originalModalContentTransition: string | null = null;
|
||||||
|
|
||||||
const handlePreviewButtonMouseDown = () => {
|
const handlePreviewButtonMouseDown = async (event: MouseEvent, themeToPreview: TerminalTheme) => {
|
||||||
if (modalRootRef.value) {
|
// 阻止可能的文本选择等默认行为
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (modalRootRef.value && themeToPreview._id) {
|
||||||
const modalContentElement = modalRootRef.value.firstElementChild as HTMLElement;
|
const modalContentElement = modalRootRef.value.firstElementChild as HTMLElement;
|
||||||
|
try {
|
||||||
|
// 1. 加载要预览的主题数据
|
||||||
|
const themeData = await appearanceStore.loadTerminalThemeData(themeToPreview._id);
|
||||||
|
if (!themeData) {
|
||||||
|
console.error('Preview failed: Could not load theme data for', themeToPreview.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Save original styles if not already saved (for this mousedown session)
|
// 2. 通知 store 开始预览 (假设 store 有此 action)
|
||||||
// These are saved once per mousedown-mouseup cycle
|
// 这个 action 内部会保存当前主题ID/数据,并设置一个临时预览主题
|
||||||
if (originalModalRootBackgroundColor === null) { // Check one to assume all are not set
|
appearanceStore.startTerminalThemePreview(themeData); // 期望参数类型为 ITheme
|
||||||
originalModalRootBackgroundColor = window.getComputedStyle(modalRootRef.value).backgroundColor;
|
|
||||||
originalModalRootTransition = modalRootRef.value.style.transition; // Save inline or computed transition
|
// 3. 使模态框和背景透明
|
||||||
if (modalContentElement) {
|
if (originalModalRootBackgroundColor === null && modalContentElement) { // 保存原始样式
|
||||||
|
originalModalRootBackgroundColor = window.getComputedStyle(modalRootRef.value).backgroundColor;
|
||||||
|
originalModalRootTransition = modalRootRef.value.style.transition;
|
||||||
originalModalContentOpacity = window.getComputedStyle(modalContentElement).opacity;
|
originalModalContentOpacity = window.getComputedStyle(modalContentElement).opacity;
|
||||||
originalModalContentTransition = modalContentElement.style.transition; // Save inline or computed transition
|
originalModalContentTransition = modalContentElement.style.transition;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Apply transparency effects
|
modalRootRef.value.style.transition = 'background-color 0.05s ease-out, opacity 0.05s ease-out';
|
||||||
// Make the background overlay (modalRootRef) transparent
|
modalRootRef.value.style.backgroundColor = 'transparent';
|
||||||
modalRootRef.value.style.transition = 'background-color 0.05s ease-out';
|
// modalRootRef.value.style.opacity = '0'; // 如果背景遮罩就是 modalRootRef
|
||||||
modalRootRef.value.style.backgroundColor = 'transparent';
|
|
||||||
|
|
||||||
if (modalContentElement) {
|
|
||||||
// Make the modal content (the box including the button) completely transparent
|
|
||||||
modalContentElement.style.transition = 'opacity 0.05s ease-out';
|
|
||||||
modalContentElement.style.opacity = '0'; // Changed from 0.05 to 0
|
|
||||||
}
|
|
||||||
|
|
||||||
const restoreModalAppearance = () => {
|
|
||||||
if (modalRootRef.value) {
|
|
||||||
modalRootRef.value.style.backgroundColor = originalModalRootBackgroundColor || ''; // Revert to original or default
|
|
||||||
modalRootRef.value.style.transition = originalModalRootTransition || ''; // Revert to original or default
|
|
||||||
}
|
|
||||||
if (modalContentElement) {
|
if (modalContentElement) {
|
||||||
modalContentElement.style.opacity = originalModalContentOpacity || '1'; // Revert to original or default
|
modalContentElement.style.transition = 'opacity 0.05s ease-out';
|
||||||
modalContentElement.style.transition = originalModalContentTransition || ''; // Revert to original or default
|
modalContentElement.style.opacity = '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset stored original styles for the next interaction cycle
|
// 4. 设置 mouseup 监听器以恢复
|
||||||
originalModalRootBackgroundColor = null;
|
const restoreOnMouseUp = () => {
|
||||||
originalModalContentOpacity = null;
|
// 4a. 通知 store 停止预览
|
||||||
originalModalRootTransition = null;
|
appearanceStore.stopTerminalThemePreview(); // 这个 action 会恢复原始主题
|
||||||
originalModalContentTransition = null;
|
|
||||||
|
|
||||||
document.removeEventListener('mouseup', restoreModalAppearance);
|
// 4b. 恢复模态框可见性
|
||||||
};
|
if (modalRootRef.value) {
|
||||||
// Listen for mouseup anywhere on the document to restore
|
modalRootRef.value.style.backgroundColor = originalModalRootBackgroundColor || '';
|
||||||
document.addEventListener('mouseup', restoreModalAppearance, { once: true });
|
// modalRootRef.value.style.opacity = '1'; // Or original opacity if it wasn't 1
|
||||||
|
modalRootRef.value.style.transition = originalModalRootTransition || '';
|
||||||
|
}
|
||||||
|
if (modalContentElement) {
|
||||||
|
modalContentElement.style.opacity = originalModalContentOpacity || '1';
|
||||||
|
modalContentElement.style.transition = originalModalContentTransition || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置保存的原始样式
|
||||||
|
originalModalRootBackgroundColor = null;
|
||||||
|
originalModalContentOpacity = null;
|
||||||
|
originalModalRootTransition = null;
|
||||||
|
originalModalContentTransition = null;
|
||||||
|
|
||||||
|
document.removeEventListener('mouseup', restoreOnMouseUp);
|
||||||
|
const currentTargetButton = event.currentTarget as HTMLElement | null;
|
||||||
|
if (currentTargetButton) {
|
||||||
|
currentTargetButton.removeEventListener('mouseleave', restoreOnMouseUp);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', restoreOnMouseUp, { once: true });
|
||||||
|
const currentTargetButton = event.currentTarget as HTMLElement | null;
|
||||||
|
if (currentTargetButton) {
|
||||||
|
currentTargetButton.addEventListener('mouseleave', restoreOnMouseUp, { once: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during theme preview:', error);
|
||||||
|
appearanceStore.stopTerminalThemePreview(); // 尝试停止预览
|
||||||
|
// 可选:恢复模态框样式,如果需要更复杂的错误处理
|
||||||
|
if (modalRootRef.value && modalContentElement) {
|
||||||
|
modalRootRef.value.style.backgroundColor = originalModalRootBackgroundColor || '';
|
||||||
|
modalRootRef.value.style.transition = originalModalRootTransition || '';
|
||||||
|
modalContentElement.style.opacity = originalModalContentOpacity || '1';
|
||||||
|
modalContentElement.style.transition = originalModalContentTransition || '';
|
||||||
|
originalModalRootBackgroundColor = null;
|
||||||
|
originalModalContentOpacity = null;
|
||||||
|
originalModalRootTransition = null;
|
||||||
|
originalModalContentTransition = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1158,8 +1197,8 @@ const handlePreviewButtonMouseDown = () => {
|
|||||||
{{ t('styleCustomizer.applyButton', 'Apply') }}
|
{{ t('styleCustomizer.applyButton', 'Apply') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@mousedown="handlePreviewButtonMouseDown"
|
@mousedown="(event) => handlePreviewButtonMouseDown(event, theme)"
|
||||||
:title="t('styleCustomizer.previewThemeTooltip', 'Hold to preview transparency')"
|
:title="t('styleCustomizer.previewThemeTooltip', 'Hold to preview theme and hide modal')"
|
||||||
:class="[
|
:class="[
|
||||||
'px-3 py-1.5 text-xs md:text-sm border rounded transition-colors duration-200 ease-in-out whitespace-nowrap',
|
'px-3 py-1.5 text-xs md:text-sm border rounded transition-colors duration-200 ease-in-out whitespace-nowrap',
|
||||||
theme._id === activeTerminalThemeId?.toString() ? 'text-button-text border-white/30 bg-white/10 hover:bg-white/20 hover:border-white/50' : 'border-border bg-header text-foreground hover:bg-border hover:border-text-secondary'
|
theme._id === activeTerminalThemeId?.toString() ? 'text-button-text border-white/30 bg-white/10 hover:bg-white/20 hover:border-white/50' : 'border-border bg-header text-foreground hover:bg-border hover:border-text-secondary'
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ let currentFontSizeOnPinchStart = 0;
|
|||||||
// --- Appearance Store ---
|
// --- Appearance Store ---
|
||||||
const appearanceStore = useAppearanceStore();
|
const appearanceStore = useAppearanceStore();
|
||||||
const {
|
const {
|
||||||
currentTerminalTheme,
|
effectiveTerminalTheme,
|
||||||
currentTerminalFontFamily,
|
currentTerminalFontFamily,
|
||||||
terminalBackgroundImage,
|
terminalBackgroundImage,
|
||||||
currentTerminalFontSize,
|
currentTerminalFontSize,
|
||||||
@@ -223,9 +223,9 @@ onMounted(() => {
|
|||||||
if (terminalRef.value) {
|
if (terminalRef.value) {
|
||||||
terminal = new Terminal({
|
terminal = new Terminal({
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
fontSize: currentTerminalFontSize.value, // <-- 使用 store 中的字体大小
|
fontSize: currentTerminalFontSize.value,
|
||||||
fontFamily: currentTerminalFontFamily.value, // 使用 store 中的字体设置
|
fontFamily: currentTerminalFontFamily.value, // 使用 store 中的字体设置
|
||||||
theme: currentTerminalTheme.value, // 使用 store 中的当前 xterm 主题
|
theme: effectiveTerminalTheme.value, // 使用 store 中的当前 xterm 主题 (now effectiveTerminalTheme)
|
||||||
rows: 24, // 初始行数
|
rows: 24, // 初始行数
|
||||||
cols: 80, // 初始列数
|
cols: 80, // 初始列数
|
||||||
allowTransparency: true,
|
allowTransparency: true,
|
||||||
@@ -406,9 +406,9 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// --- 监听外观变化 ---
|
// --- 监听外观变化 ---
|
||||||
watch(currentTerminalTheme, (newTheme) => {
|
watch(effectiveTerminalTheme, (newTheme) => { // Changed from currentTerminalTheme
|
||||||
if (terminal) {
|
if (terminal) {
|
||||||
console.log(`[Terminal ${props.sessionId}] 应用新终端主题。`);
|
console.log(`[Terminal ${props.sessionId}] 应用新终端主题 (effective)。`);
|
||||||
// 直接修改 options 对象
|
// 直接修改 options 对象
|
||||||
terminal.options.theme = newTheme;
|
terminal.options.theme = newTheme;
|
||||||
// 修改选项后需要刷新终端才能生效
|
// 修改选项后需要刷新终端才能生效
|
||||||
|
|||||||
@@ -30,6 +30,10 @@ export const useAppearanceStore = defineStore('appearance', () => {
|
|||||||
const appearanceSettings = ref<Partial<AppearanceSettings>>({}); // 从 API 获取的原始设置
|
const appearanceSettings = ref<Partial<AppearanceSettings>>({}); // 从 API 获取的原始设置
|
||||||
const allTerminalThemes = ref<TerminalTheme[]>([]); // 重命名: 存储从后端获取的所有主题
|
const allTerminalThemes = ref<TerminalTheme[]>([]); // 重命名: 存储从后端获取的所有主题
|
||||||
|
|
||||||
|
// State for theme preview
|
||||||
|
const isPreviewingTerminalTheme = ref(false);
|
||||||
|
const previewTerminalThemeData = ref<ITheme | null>(null);
|
||||||
|
|
||||||
// --- Computed Properties (Getters) ---
|
// --- Computed Properties (Getters) ---
|
||||||
|
|
||||||
// 移除 availableTerminalThemes 计算属性,直接使用 allTerminalThemes
|
// 移除 availableTerminalThemes 计算属性,直接使用 allTerminalThemes
|
||||||
@@ -55,6 +59,21 @@ export const useAppearanceStore = defineStore('appearance', () => {
|
|||||||
return activeTheme ? activeTheme.themeData : defaultXtermTheme; // 找不到也回退到 xterm 默认
|
return activeTheme ? activeTheme.themeData : defaultXtermTheme; // 找不到也回退到 xterm 默认
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Effective terminal theme, considering preview
|
||||||
|
const effectiveTerminalTheme = computed<ITheme>(() => {
|
||||||
|
if (isPreviewingTerminalTheme.value && previewTerminalThemeData.value) {
|
||||||
|
return previewTerminalThemeData.value;
|
||||||
|
}
|
||||||
|
// Fallback to the currently set theme if not previewing
|
||||||
|
const activeId = activeTerminalThemeId.value;
|
||||||
|
if (activeId === null || activeId === undefined || allTerminalThemes.value.length === 0) {
|
||||||
|
const defaultPresetTheme = allTerminalThemes.value.find(t => t.name === '默认'); // Adjust if default theme identified differently
|
||||||
|
return defaultPresetTheme ? defaultPresetTheme.themeData : defaultXtermTheme;
|
||||||
|
}
|
||||||
|
const activeSetTheme = allTerminalThemes.value.find(t => parseInt(t._id ?? '-1', 10) === activeId);
|
||||||
|
return activeSetTheme ? activeSetTheme.themeData : defaultXtermTheme;
|
||||||
|
});
|
||||||
|
|
||||||
// 当前终端字体设置
|
// 当前终端字体设置
|
||||||
const currentTerminalFontFamily = computed<string>(() => {
|
const currentTerminalFontFamily = computed<string>(() => {
|
||||||
return appearanceSettings.value.terminalFontFamily || 'Consolas, "Courier New", monospace, "Microsoft YaHei", "微软雅黑"'; // 提供默认值
|
return appearanceSettings.value.terminalFontFamily || 'Consolas, "Courier New", monospace, "Microsoft YaHei", "微软雅黑"'; // 提供默认值
|
||||||
@@ -506,6 +525,18 @@ export const useAppearanceStore = defineStore('appearance', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Terminal Theme Preview Actions ---
|
||||||
|
function startTerminalThemePreview(themeData: ITheme) {
|
||||||
|
previewTerminalThemeData.value = themeData;
|
||||||
|
isPreviewingTerminalTheme.value = true;
|
||||||
|
console.log('[AppearanceStore] Started terminal theme preview.');
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopTerminalThemePreview() {
|
||||||
|
previewTerminalThemeData.value = null;
|
||||||
|
isPreviewingTerminalTheme.value = false;
|
||||||
|
console.log('[AppearanceStore] Stopped terminal theme preview.');
|
||||||
|
}
|
||||||
|
|
||||||
// --- Helper Functions ---
|
// --- Helper Functions ---
|
||||||
/**
|
/**
|
||||||
@@ -597,20 +628,20 @@ export const useAppearanceStore = defineStore('appearance', () => {
|
|||||||
// State refs (原始数据)
|
// State refs (原始数据)
|
||||||
appearanceSettings,
|
appearanceSettings,
|
||||||
allTerminalThemes, // 导出重命名后的 ref
|
allTerminalThemes, // 导出重命名后的 ref
|
||||||
// Computed Getters
|
isPreviewingTerminalTheme,
|
||||||
|
previewTerminalThemeData,
|
||||||
currentUiTheme,
|
currentUiTheme,
|
||||||
activeTerminalThemeId,
|
activeTerminalThemeId,
|
||||||
currentTerminalTheme,
|
currentTerminalTheme,
|
||||||
|
effectiveTerminalTheme,
|
||||||
currentTerminalFontFamily,
|
currentTerminalFontFamily,
|
||||||
currentTerminalFontSize, // 这个 getter 会自动根据设备类型返回正确的字体大小
|
currentTerminalFontSize, // 这个 getter 会自动根据设备类型返回正确的字体大小
|
||||||
terminalFontSizeDesktop, // + 用于在设置中分别显示/设置桌面端字号
|
terminalFontSizeDesktop, // + 用于在设置中分别显示/设置桌面端字号
|
||||||
terminalFontSizeMobile, // + 用于在设置中分别显示/设置移动端字号
|
terminalFontSizeMobile, // + 用于在设置中分别显示/设置移动端字号
|
||||||
currentEditorFontSize, // <-- 新增
|
currentEditorFontSize,
|
||||||
pageBackgroundImage,
|
pageBackgroundImage,
|
||||||
// pageBackgroundOpacity, // Removed
|
|
||||||
terminalBackgroundImage,
|
terminalBackgroundImage,
|
||||||
// terminalBackgroundOpacity, // Removed
|
currentTerminalBackgroundOverlayOpacity,
|
||||||
currentTerminalBackgroundOverlayOpacity, // <-- 新增导出
|
|
||||||
// Actions
|
// Actions
|
||||||
loadInitialAppearanceData,
|
loadInitialAppearanceData,
|
||||||
updateAppearanceSettings,
|
updateAppearanceSettings,
|
||||||
@@ -620,22 +651,22 @@ export const useAppearanceStore = defineStore('appearance', () => {
|
|||||||
setTerminalFontFamily,
|
setTerminalFontFamily,
|
||||||
setTerminalFontSize, // 设置桌面端字体大小
|
setTerminalFontSize, // 设置桌面端字体大小
|
||||||
setTerminalFontSizeMobile, // + 设置移动端字体大小
|
setTerminalFontSizeMobile, // + 设置移动端字体大小
|
||||||
setEditorFontSize, // <-- 新增
|
setEditorFontSize,
|
||||||
setTerminalBackgroundEnabled, // <-- 新增
|
setTerminalBackgroundEnabled,
|
||||||
createTerminalTheme, // 保留
|
createTerminalTheme,
|
||||||
updateTerminalTheme, // 保留
|
updateTerminalTheme,
|
||||||
deleteTerminalTheme, // 保留
|
deleteTerminalTheme,
|
||||||
importTerminalTheme, // 保留
|
importTerminalTheme,
|
||||||
exportTerminalTheme,
|
exportTerminalTheme,
|
||||||
uploadPageBackground,
|
uploadPageBackground,
|
||||||
uploadTerminalBackground,
|
uploadTerminalBackground,
|
||||||
// setPageBackgroundOpacity, // Removed
|
setTerminalBackgroundOverlayOpacity,
|
||||||
// setTerminalBackgroundOpacity, // Removed
|
|
||||||
setTerminalBackgroundOverlayOpacity, // <-- 新增导出
|
|
||||||
removePageBackground,
|
removePageBackground,
|
||||||
removeTerminalBackground,
|
removeTerminalBackground,
|
||||||
loadTerminalThemeData, // <-- 新增导出
|
loadTerminalThemeData,
|
||||||
isTerminalBackgroundEnabled, // <-- 新增导出
|
isTerminalBackgroundEnabled,
|
||||||
|
startTerminalThemePreview,
|
||||||
|
stopTerminalThemePreview,
|
||||||
// Visibility control
|
// Visibility control
|
||||||
isStyleCustomizerVisible,
|
isStyleCustomizerVisible,
|
||||||
toggleStyleCustomizer,
|
toggleStyleCustomizer,
|
||||||
|
|||||||
Reference in New Issue
Block a user