feat(字幕样式): 添加字幕样式保存与加载功能
实现字幕样式的本地存储功能,包括保存当前设置、加载已保存设置和应用默认设置。新增样式提示对话框让用户选择是否应用已保存样式,并完善透明背景色的处理逻辑。
This commit is contained in:
@@ -601,6 +601,125 @@ function hexToRgba(hex, alpha) {
|
|||||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CAPTION_STYLE_KEY = 'captionStyleSettings';
|
||||||
|
|
||||||
|
function saveCaptionStyleToStorage() {
|
||||||
|
const styleData = {
|
||||||
|
containerBgColor: elements.containerBgColor.value,
|
||||||
|
containerBgOpacity: elements.containerBgOpacity.value,
|
||||||
|
containerBorderColor: elements.containerBorderColor.value,
|
||||||
|
containerBorderRadius: elements.containerBorderRadius.value,
|
||||||
|
containerPadding: elements.containerPadding.value,
|
||||||
|
tipFontSize: elements.tipFontSize.value,
|
||||||
|
tipFontWeight: elements.tipFontWeight.value,
|
||||||
|
tipColor: elements.tipColor.value,
|
||||||
|
tipLineHeight: elements.tipLineHeight.value,
|
||||||
|
tipBgColor: elements.tipBgColor.value,
|
||||||
|
tipBgOpacity: elements.tipBgOpacity.value,
|
||||||
|
tipBorderRadius: elements.tipBorderRadius.value,
|
||||||
|
tipPadding: elements.tipPadding.value,
|
||||||
|
messageFontSize: elements.messageFontSize.value,
|
||||||
|
messageFontWeight: elements.messageFontWeight.value,
|
||||||
|
messageColor: elements.messageColor.value,
|
||||||
|
messageLineHeight: elements.messageLineHeight.value,
|
||||||
|
messageBgColor: elements.messageBgColor.value,
|
||||||
|
messageBgOpacity: elements.messageBgOpacity.value,
|
||||||
|
messageBorderRadius: elements.messageBorderRadius.value,
|
||||||
|
messagePadding: elements.messagePadding.value,
|
||||||
|
hotTitleFontSize: elements.hotTitleFontSize.value,
|
||||||
|
hotTitleFontWeight: elements.hotTitleFontWeight.value,
|
||||||
|
hotTitleColor: elements.hotTitleColor.value,
|
||||||
|
hotTitleLineHeight: elements.hotTitleLineHeight.value,
|
||||||
|
hotTitleBgColor: elements.hotTitleBgColor.value,
|
||||||
|
hotTitleBgOpacity: elements.hotTitleBgOpacity.value,
|
||||||
|
hotTitleBorderRadius: elements.hotTitleBorderRadius.value,
|
||||||
|
hotTitlePadding: elements.hotTitlePadding.value,
|
||||||
|
hotIssuesFontSize: elements.hotIssuesFontSize.value,
|
||||||
|
hotIssuesFontWeight: elements.hotIssuesFontWeight.value,
|
||||||
|
hotIssuesColor: elements.hotIssuesColor.value,
|
||||||
|
hotIssuesLineHeight: elements.hotIssuesLineHeight.value,
|
||||||
|
hotIssuesBgColor: elements.hotIssuesBgColor.value,
|
||||||
|
hotIssuesBgOpacity: elements.hotIssuesBgOpacity.value,
|
||||||
|
hotIssuesBorderRadius: elements.hotIssuesBorderRadius.value,
|
||||||
|
hotIssuesPadding: elements.hotIssuesPadding.value
|
||||||
|
};
|
||||||
|
localStorage.setItem(CAPTION_STYLE_KEY, JSON.stringify(styleData));
|
||||||
|
logger.info('字幕样式已保存');
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCaptionStyleFromStorage() {
|
||||||
|
const savedData = localStorage.getItem(CAPTION_STYLE_KEY);
|
||||||
|
if (!savedData) return null;
|
||||||
|
try {
|
||||||
|
return JSON.parse(savedData);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error('加载保存的字幕样式失败', e.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasSavedCaptionStyle() {
|
||||||
|
return localStorage.getItem(CAPTION_STYLE_KEY) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySavedCaptionStyle(savedData) {
|
||||||
|
if (savedData.containerBgColor) elements.containerBgColor.value = savedData.containerBgColor;
|
||||||
|
if (savedData.containerBgOpacity) elements.containerBgOpacity.value = savedData.containerBgOpacity;
|
||||||
|
if (savedData.containerBorderColor) elements.containerBorderColor.value = savedData.containerBorderColor;
|
||||||
|
if (savedData.containerBorderRadius) elements.containerBorderRadius.value = savedData.containerBorderRadius;
|
||||||
|
if (savedData.containerPadding) elements.containerPadding.value = savedData.containerPadding;
|
||||||
|
if (savedData.tipFontSize) elements.tipFontSize.value = savedData.tipFontSize;
|
||||||
|
if (savedData.tipFontWeight) elements.tipFontWeight.value = savedData.tipFontWeight;
|
||||||
|
if (savedData.tipColor) elements.tipColor.value = savedData.tipColor;
|
||||||
|
if (savedData.tipLineHeight) elements.tipLineHeight.value = savedData.tipLineHeight;
|
||||||
|
if (savedData.tipBgColor) elements.tipBgColor.value = savedData.tipBgColor;
|
||||||
|
if (savedData.tipBgOpacity) elements.tipBgOpacity.value = savedData.tipBgOpacity;
|
||||||
|
if (savedData.tipBorderRadius) elements.tipBorderRadius.value = savedData.tipBorderRadius;
|
||||||
|
if (savedData.tipPadding) elements.tipPadding.value = savedData.tipPadding;
|
||||||
|
if (savedData.messageFontSize) elements.messageFontSize.value = savedData.messageFontSize;
|
||||||
|
if (savedData.messageFontWeight) elements.messageFontWeight.value = savedData.messageFontWeight;
|
||||||
|
if (savedData.messageColor) elements.messageColor.value = savedData.messageColor;
|
||||||
|
if (savedData.messageLineHeight) elements.messageLineHeight.value = savedData.messageLineHeight;
|
||||||
|
if (savedData.messageBgColor) elements.messageBgColor.value = savedData.messageBgColor;
|
||||||
|
if (savedData.messageBgOpacity) elements.messageBgOpacity.value = savedData.messageBgOpacity;
|
||||||
|
if (savedData.messageBorderRadius) elements.messageBorderRadius.value = savedData.messageBorderRadius;
|
||||||
|
if (savedData.messagePadding) elements.messagePadding.value = savedData.messagePadding;
|
||||||
|
if (savedData.hotTitleFontSize) elements.hotTitleFontSize.value = savedData.hotTitleFontSize;
|
||||||
|
if (savedData.hotTitleFontWeight) elements.hotTitleFontWeight.value = savedData.hotTitleFontWeight;
|
||||||
|
if (savedData.hotTitleColor) elements.hotTitleColor.value = savedData.hotTitleColor;
|
||||||
|
if (savedData.hotTitleLineHeight) elements.hotTitleLineHeight.value = savedData.hotTitleLineHeight;
|
||||||
|
if (savedData.hotTitleBgColor) elements.hotTitleBgColor.value = savedData.hotTitleBgColor;
|
||||||
|
if (savedData.hotTitleBgOpacity) elements.hotTitleBgOpacity.value = savedData.hotTitleBgOpacity;
|
||||||
|
if (savedData.hotTitleBorderRadius) elements.hotTitleBorderRadius.value = savedData.hotTitleBorderRadius;
|
||||||
|
if (savedData.hotTitlePadding) elements.hotTitlePadding.value = savedData.hotTitlePadding;
|
||||||
|
if (savedData.hotIssuesFontSize) elements.hotIssuesFontSize.value = savedData.hotIssuesFontSize;
|
||||||
|
if (savedData.hotIssuesFontWeight) elements.hotIssuesFontWeight.value = savedData.hotIssuesFontWeight;
|
||||||
|
if (savedData.hotIssuesColor) elements.hotIssuesColor.value = savedData.hotIssuesColor;
|
||||||
|
if (savedData.hotIssuesLineHeight) elements.hotIssuesLineHeight.value = savedData.hotIssuesLineHeight;
|
||||||
|
if (savedData.hotIssuesBgColor) elements.hotIssuesBgColor.value = savedData.hotIssuesBgColor;
|
||||||
|
if (savedData.hotIssuesBgOpacity) elements.hotIssuesBgOpacity.value = savedData.hotIssuesBgOpacity;
|
||||||
|
if (savedData.hotIssuesBorderRadius) elements.hotIssuesBorderRadius.value = savedData.hotIssuesBorderRadius;
|
||||||
|
if (savedData.hotIssuesPadding) elements.hotIssuesPadding.value = savedData.hotIssuesPadding;
|
||||||
|
|
||||||
|
document.querySelectorAll('#captionPanel input[type="range"]').forEach(slider => {
|
||||||
|
const valueSpan = slider.nextElementSibling;
|
||||||
|
if (valueSpan && valueSpan.classList.contains('range-value')) {
|
||||||
|
valueSpan.textContent = `${slider.value}%`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showCaptionStylePrompt(callback) {
|
||||||
|
const savedData = loadCaptionStyleFromStorage();
|
||||||
|
if (!savedData) {
|
||||||
|
callback(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmed = confirm('检测到已保存的字幕样式设置。\n\n点击"确定"应用保存的样式,\n点击"取消"使用全新默认设置。');
|
||||||
|
callback(confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
function loadCaptionStyleFromContainer() {
|
function loadCaptionStyleFromContainer() {
|
||||||
const captionContainer = document.querySelector('.caption-container-hub');
|
const captionContainer = document.querySelector('.caption-container-hub');
|
||||||
if (!captionContainer) return;
|
if (!captionContainer) return;
|
||||||
@@ -669,13 +788,18 @@ function loadCaptionStyleFromContainer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (styles.backgroundColor && styles.backgroundColor !== 'transparent' && styles.backgroundColor !== 'rgba(0, 0, 0, 0)') {
|
if (styles.backgroundColor) {
|
||||||
const rgbaMatch = styles.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/);
|
const rgbaMatch = styles.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/);
|
||||||
if (rgbaMatch) {
|
if (rgbaMatch) {
|
||||||
section.elements.bgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
|
section.elements.bgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
|
||||||
if (rgbaMatch[4]) {
|
if (rgbaMatch[4]) {
|
||||||
section.elements.bgOpacity.value = Math.round(parseFloat(rgbaMatch[4]) * 100);
|
section.elements.bgOpacity.value = Math.round(parseFloat(rgbaMatch[4]) * 100);
|
||||||
|
} else {
|
||||||
|
section.elements.bgOpacity.value = 0;
|
||||||
}
|
}
|
||||||
|
} else if (styles.backgroundColor === 'transparent' || styles.backgroundColor === 'rgba(0, 0, 0, 0)') {
|
||||||
|
section.elements.bgColor.value = '#000000';
|
||||||
|
section.elements.bgOpacity.value = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -831,6 +955,7 @@ function initCaptionDragAndResize(container) {
|
|||||||
|
|
||||||
let captionStyleObserver = null;
|
let captionStyleObserver = null;
|
||||||
let captionStyleApplied = false;
|
let captionStyleApplied = false;
|
||||||
|
let captionStylePromptShown = false;
|
||||||
|
|
||||||
function watchCaptionContainer() {
|
function watchCaptionContainer() {
|
||||||
const observer = new MutationObserver((mutations) => {
|
const observer = new MutationObserver((mutations) => {
|
||||||
@@ -947,6 +1072,7 @@ function bindEvents() {
|
|||||||
elements.getOnceCodeBtn.addEventListener('click', handleGetOnceCode);
|
elements.getOnceCodeBtn.addEventListener('click', handleGetOnceCode);
|
||||||
elements.applyCaptionStyleBtn.addEventListener('click', () => {
|
elements.applyCaptionStyleBtn.addEventListener('click', () => {
|
||||||
applyCaptionStyle();
|
applyCaptionStyle();
|
||||||
|
saveCaptionStyleToStorage();
|
||||||
captionStyleApplied = true;
|
captionStyleApplied = true;
|
||||||
watchCaptionContentChanges();
|
watchCaptionContentChanges();
|
||||||
});
|
});
|
||||||
@@ -958,6 +1084,7 @@ function bindEvents() {
|
|||||||
valueSpan.textContent = `${e.target.value}%`;
|
valueSpan.textContent = `${e.target.value}%`;
|
||||||
}
|
}
|
||||||
updateSingleStyle(e.target.id);
|
updateSingleStyle(e.target.id);
|
||||||
|
saveCaptionStyleToStorage();
|
||||||
captionStyleApplied = true;
|
captionStyleApplied = true;
|
||||||
watchCaptionContentChanges();
|
watchCaptionContentChanges();
|
||||||
});
|
});
|
||||||
@@ -966,11 +1093,13 @@ function bindEvents() {
|
|||||||
document.querySelectorAll('#captionPanel input[type="number"], #captionPanel input[type="color"], #captionPanel select').forEach(input => {
|
document.querySelectorAll('#captionPanel input[type="number"], #captionPanel input[type="color"], #captionPanel select').forEach(input => {
|
||||||
input.addEventListener('input', (e) => {
|
input.addEventListener('input', (e) => {
|
||||||
updateSingleStyle(e.target.id);
|
updateSingleStyle(e.target.id);
|
||||||
|
saveCaptionStyleToStorage();
|
||||||
captionStyleApplied = true;
|
captionStyleApplied = true;
|
||||||
watchCaptionContentChanges();
|
watchCaptionContentChanges();
|
||||||
});
|
});
|
||||||
input.addEventListener('change', (e) => {
|
input.addEventListener('change', (e) => {
|
||||||
updateSingleStyle(e.target.id);
|
updateSingleStyle(e.target.id);
|
||||||
|
saveCaptionStyleToStorage();
|
||||||
captionStyleApplied = true;
|
captionStyleApplied = true;
|
||||||
watchCaptionContentChanges();
|
watchCaptionContentChanges();
|
||||||
});
|
});
|
||||||
@@ -987,7 +1116,28 @@ function bindEvents() {
|
|||||||
elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel'));
|
elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel'));
|
||||||
elements.toggleCaptionBtn.addEventListener('click', () => {
|
elements.toggleCaptionBtn.addEventListener('click', () => {
|
||||||
togglePanel('captionPanel');
|
togglePanel('captionPanel');
|
||||||
|
if (!captionStylePromptShown && hasSavedCaptionStyle()) {
|
||||||
|
captionStylePromptShown = true;
|
||||||
|
showCaptionStylePrompt((applySaved) => {
|
||||||
|
if (applySaved) {
|
||||||
|
const savedData = loadCaptionStyleFromStorage();
|
||||||
|
if (savedData) {
|
||||||
|
applySavedCaptionStyle(savedData);
|
||||||
|
applyCaptionStyle();
|
||||||
|
captionStyleApplied = true;
|
||||||
|
watchCaptionContentChanges();
|
||||||
|
logger.info('已应用保存的字幕样式');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
loadCaptionStyleFromContainer();
|
loadCaptionStyleFromContainer();
|
||||||
|
localStorage.removeItem(CAPTION_STYLE_KEY);
|
||||||
|
captionStyleApplied = false;
|
||||||
|
logger.info('已清除保存的字幕样式,使用全新默认设置');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
loadCaptionStyleFromContainer();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel'));
|
elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel'));
|
||||||
elements.fullscreenBtn.addEventListener('click', toggleFullscreen);
|
elements.fullscreenBtn.addEventListener('click', toggleFullscreen);
|
||||||
|
|||||||
Reference in New Issue
Block a user