// 导入所有控制器模块 import { ThemeController } from './theme.js'; import { ScrollController } from './scroll.js'; import { TextController } from './text.js'; import { AudioController } from './audio.js'; import { SettingsController } from './settings.js'; import { WatermarkController } from './watermark.js'; import { FlipController } from './flip.js'; import { DraggableLine } from './draggable-line.js'; // 主应用程序入口 class TeleprompterApp { constructor() { this.isPlaying = false; this.isLocked = false; this.currentSettings = this.getDefaultSettings(); this.scrollController = null; this.activePanel = null; this.sidebarHidden = false; this.countdownDuration = 0; this.init(); } init() { // 先初始化各个模块 window.themeController = new ThemeController(); window.scrollController = new ScrollController(); window.textController = new TextController(); window.audioController = new AudioController(); window.settingsController = new SettingsController(); window.watermarkController = new WatermarkController(); window.flipController = new FlipController(); window.draggableLine = new DraggableLine(); window.draggableLine.loadPosition(); this.setupEventListeners(); this.loadDefaultText(); this.updateTimeDisplay(); this.initializeScrollbar(); this.setupSidebarBehavior(); this.setupLogoAutoFade(); // 每秒更新时间显示 setInterval(() => this.updateTimeDisplay(), 1000); } getDefaultSettings() { return { theme: 'dark', fontSize: 40, fontFamily: 'SimSun, serif', fontColor: '#ffffff', marginLeft: 100, marginRight: 50, letterSpacing: 1, lineHeight: 1.6, autoScroll: true, scrollType: 'line', scrollSpeed: 10, horizontalFlip: false, verticalFlip: false, watermarkEnabled: false, watermarkText: '', watermarkType: 'center', countdownDuration: 0 }; } setupEventListeners() { // 全屏切换 document.getElementById('fullscreenBtn').addEventListener('click', () => { this.toggleFullscreen(); }); // 主题切换 document.getElementById('themeBtn').addEventListener('click', () => { window.themeController.toggleTheme(); }); // 播放/暂停 - 检查自动滚动设置 document.getElementById('playBtn').addEventListener('click', () => { const autoScrollEnabled = document.getElementById('autoScroll').checked; if (!autoScrollEnabled) { alert('请先在滚动设置中启用自动滚动功能'); return; } if (this.isPlaying) { this.pauseReading(); } else { this.startCountdownAndPlay(); } }); // 文本锁定 document.getElementById('lockBtn').addEventListener('click', () => { this.toggleTextLock(); }); // 侧边栏隐藏/显示 document.getElementById('sidebarToggle').addEventListener('click', () => { this.toggleSidebar(); }); // 设置面板切换 this.setupSettingsPanels(); // 文件导入 document.getElementById('importBtn').addEventListener('click', () => { document.getElementById('fileInput').click(); }); document.getElementById('fileInput').addEventListener('change', (e) => { window.textController.handleFileImport(e); }); // 键盘快捷键 document.addEventListener('keydown', (e) => { this.handleKeyboardShortcuts(e); }); // 滚动检测 let scrollTimeout; document.addEventListener('scroll', () => { if (!this.sidebarHidden) { document.body.classList.add('scrolling'); clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { document.body.classList.remove('scrolling'); }, 1000); } }); // 鼠标滚轮事件 document.addEventListener('wheel', (e) => { this.handleMouseWheel(e); }); // 点击外部关闭设置面板 document.addEventListener('click', (e) => { if (!e.target.closest('.settings-panel') && !e.target.closest('.sidebar-btn')) { this.closeAllPanels(); } }); } setupSidebarBehavior() { const sidebar = document.getElementById('sidebar'); const trigger = document.getElementById('sidebarTrigger'); // 鼠标移入按钮显示设置面板 const buttons = document.querySelectorAll('.sidebar-btn:not(.sidebar-toggle)'); buttons.forEach(btn => { btn.addEventListener('mouseenter', () => { if (!this.sidebarHidden) { const panelId = this.getPanelIdForButton(btn.id); if (panelId) { this.showSettingsPanel(panelId, btn); } else { // 如果当前按钮没有对应的设置面板,关闭已打开的面板 this.closeAllPanels(); } } }); }); // 鼠标离开侧边栏区域隐藏面板 sidebar.addEventListener('mouseleave', () => { setTimeout(() => { if (!document.querySelector('.settings-panel:hover')) { this.closeAllPanels(); } }, 100); }); // 左侧边缘触发显示 trigger.addEventListener('mouseenter', () => { if (this.sidebarHidden) { this.showSidebar(); } }); } getPanelIdForButton(buttonId) { const mapping = { 'scrollBtn': 'scrollSettings', 'fontBtn': 'fontSettings', 'flipBtn': 'flipSettings', 'watermarkBtn': 'watermarkSettings', 'recordBtn': 'recordSettings' }; return mapping[buttonId]; } setupSettingsPanels() { // 移除点击事件,改为鼠标移入事件 // 这些事件现在在 setupSidebarBehavior 中处理 } showSettingsPanel(panelId, buttonElement) { this.closeAllPanels(); const panel = document.getElementById(panelId); if (!panel) return; // 显示面板 panel.style.display = 'block'; this.activePanel = panelId; // 定位面板 const rect = buttonElement.getBoundingClientRect(); let leftPos = rect.right + 10; let topPos = rect.top; // 检查面板是否会超出屏幕底部 const panelHeight = panel.offsetHeight || 200; // 默认高度 const windowHeight = window.innerHeight; if (topPos + panelHeight > windowHeight) { // 如果超出底部,将面板向上调整 topPos = windowHeight - panelHeight - 20; // 留20px边距 } // 确保面板不会超出屏幕顶部 if (topPos < 20) { topPos = 20; } panel.style.left = `${leftPos}px`; panel.style.top = `${topPos}px`; // 激活按钮样式 buttonElement.classList.add('active'); } toggleSidebar() { const sidebar = document.getElementById('sidebar'); const trigger = document.getElementById('sidebarTrigger'); const toggleBtn = document.getElementById('sidebarToggle'); const toggleIcon = toggleBtn.querySelector('img'); if (this.sidebarHidden) { this.showSidebar(); } else { this.hideSidebar(); } } showSidebar() { const sidebar = document.getElementById('sidebar'); const trigger = document.getElementById('sidebarTrigger'); const toggleIcon = document.querySelector('#sidebarToggle img'); this.sidebarHidden = false; sidebar.classList.remove('hidden'); trigger.classList.remove('active'); toggleIcon.src = 'images/chevron-left.svg'; document.getElementById('sidebarToggle').title = '隐藏功能区'; } hideSidebar() { const sidebar = document.getElementById('sidebar'); const trigger = document.getElementById('sidebarTrigger'); const toggleIcon = document.querySelector('#sidebarToggle img'); this.sidebarHidden = true; sidebar.classList.add('hidden'); trigger.classList.add('active'); toggleIcon.src = 'images/chevron-right.svg'; document.getElementById('sidebarToggle').title = '显示功能区'; this.closeAllPanels(); } closeAllPanels() { const panels = document.querySelectorAll('.settings-panel'); const buttons = document.querySelectorAll('.sidebar-btn'); panels.forEach(panel => { panel.style.display = 'none'; }); buttons.forEach(btn => { btn.classList.remove('active'); }); this.activePanel = null; } toggleFullscreen() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen().then(() => { document.body.classList.add('fullscreen'); }); } else { document.exitFullscreen().then(() => { document.body.classList.remove('fullscreen'); }); } } startCountdownAndPlay() { this.countdownDuration = parseInt(document.getElementById('countdownDuration').value) || 0; this.closeAllPanels(); if (this.countdownDuration > 0) { this.showCountdown().then(() => { this.startReading(); }); } else { this.startReading(); } } startReading() { this.isPlaying = true; this.updatePlayButton(); if (window.scrollController) { window.scrollController.startAutoScroll(); } if (window.audioController && window.audioController.isRecording) { // 音频同步逻辑 } } pauseReading() { this.isPlaying = false; this.updatePlayButton(); if (window.scrollController) { window.scrollController.stopAutoScroll(); } } updatePlayButton() { const playBtn = document.getElementById('playBtn'); const icon = playBtn.querySelector('img'); if (this.isPlaying) { icon.src = 'images/pause.svg'; playBtn.title = '暂停'; } else { icon.src = 'images/play.svg'; playBtn.title = '播放/暂停'; } } showCountdown() { return new Promise((resolve) => { const countdown = document.getElementById('countdownDisplay'); const text = document.getElementById('countdownText'); let count = this.countdownDuration; countdown.style.display = 'flex'; const timer = setInterval(() => { text.textContent = count; count--; if (count < 0) { clearInterval(timer); countdown.style.display = 'none'; resolve(); } }, 1000); }); } toggleTextLock() { const editor = document.getElementById('textEditor'); const lockIcon = document.getElementById('lockIcon'); this.isLocked = !this.isLocked; if (this.isLocked) { editor.contentEditable = false; editor.classList.add('locked'); lockIcon.src = 'images/lock.svg'; document.getElementById('lockBtn').title = '解锁文本'; } else { editor.contentEditable = true; editor.classList.remove('locked'); lockIcon.src = 'images/unlock.svg'; document.getElementById('lockBtn').title = '锁定文本'; } } handleKeyboardShortcuts(e) { // Esc 键退出全屏 if (e.key === 'Escape' && document.fullscreenElement) { document.exitFullscreen(); } // 空格键播放/暂停 if (e.key === ' ' && !e.target.contentEditable) { e.preventDefault(); if (this.isPlaying) { this.pauseReading(); } else { this.startCountdownAndPlay(); } } // F11 全屏 if (e.key === 'F11') { e.preventDefault(); this.toggleFullscreen(); } } handleMouseWheel(e) { const editor = document.getElementById('textEditor'); const delta = e.deltaY; // 手动滚动不中断自动滚动,但会更新当前位置 if (window.scrollController && window.scrollController.isAutoScrolling) { // 更新自动滚动的当前位置 window.scrollController.currentScrollPosition = editor.scrollTop + delta; editor.scrollTop = window.scrollController.currentScrollPosition; // 更新滚动条位置 this.updateScrollbar(); } } loadDefaultText() { try { window.textController.setText('我是一个提词器的示例文本。\n你可以直接编辑我,或者粘贴内容。\n可以点击左侧功能区的文件夹图标,上传文件。需要是"*.txt"的文本文档格式。\n本项目支持翻转(水平、垂直)\n在开始使用之前,请注意设置以下内容:字体,字体大小,滚动速度\n项目已开源到Github,地址为https://github.com/Song2770/ez4prompt\n如果你觉得好用,不妨为我的项目点一颗Star。\n如果你遇到了问题,欢迎留言。'); this.updateScrollbar(); } catch (error) { console.error('加载默认文本失败:', error); } } updateTimeDisplay() { const now = new Date(); const timeString = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); document.getElementById('timeDisplay').textContent = timeString; } initializeScrollbar() { const editor = document.getElementById('textEditor'); const scrollbar = document.getElementById('customScrollbar'); const thumb = document.getElementById('scrollbarThumb'); // 更新滚动条 const updateScrollbar = () => { const scrollRatio = editor.scrollTop / (editor.scrollHeight - editor.clientHeight); const thumbHeight = Math.max(20, (editor.clientHeight / editor.scrollHeight) * scrollbar.clientHeight); const thumbTop = scrollRatio * (scrollbar.clientHeight - thumbHeight); thumb.style.height = `${thumbHeight}px`; thumb.style.transform = `translateY(${thumbTop}px)`; }; // 监听文本编辑器滚动 editor.addEventListener('scroll', updateScrollbar); // 拖拽滚动条 let isDragging = false; let wasAutoScrolling = false; thumb.addEventListener('mousedown', (e) => { isDragging = true; const startY = e.clientY; const startScrollTop = editor.scrollTop; // 如果正在自动滚动,暂停自动滚动 if (window.scrollController && window.scrollController.isAutoScrolling) { wasAutoScrolling = true; window.scrollController.stopAutoScroll(); } const mouseMoveHandler = (e) => { if (!isDragging) return; const deltaY = e.clientY - startY; const scrollRatio = deltaY / (scrollbar.clientHeight - thumb.clientHeight); const newScrollTop = startScrollTop + scrollRatio * (editor.scrollHeight - editor.clientHeight); editor.scrollTop = Math.max(0, Math.min(newScrollTop, editor.scrollHeight - editor.clientHeight)); }; const mouseUpHandler = () => { isDragging = false; // 如果之前在自动滚动,更新滚动控制器的当前位置并恢复自动滚动 if (wasAutoScrolling && window.scrollController) { window.scrollController.currentScrollPosition = editor.scrollTop; window.scrollController.startAutoScroll(); } wasAutoScrolling = false; document.removeEventListener('mousemove', mouseMoveHandler); document.removeEventListener('mouseup', mouseUpHandler); }; document.addEventListener('mousemove', mouseMoveHandler); document.addEventListener('mouseup', mouseUpHandler); }); // 点击滚动条跳转 scrollbar.addEventListener('click', (e) => { if (e.target === thumb) return; const clickRatio = e.offsetY / scrollbar.clientHeight; const newScrollTop = clickRatio * (editor.scrollHeight - editor.clientHeight); // 如果正在自动滚动,暂停并更新位置 const wasAutoScrollingOnClick = window.scrollController && window.scrollController.isAutoScrolling; if (wasAutoScrollingOnClick) { window.scrollController.stopAutoScroll(); } // 使用平滑滚动 if (window.scrollController) { window.scrollController.smoothScrollTo(newScrollTop); // 如果之前在自动滚动,在平滑滚动完成后恢复自动滚动 if (wasAutoScrollingOnClick) { setTimeout(() => { if (window.scrollController) { window.scrollController.currentScrollPosition = newScrollTop; window.scrollController.startAutoScroll(); } }, 500); // 等待平滑滚动完成(500ms) } } else { editor.scrollTop = newScrollTop; // 如果之前在自动滚动,立即恢复 if (wasAutoScrollingOnClick && window.scrollController) { window.scrollController.currentScrollPosition = newScrollTop; window.scrollController.startAutoScroll(); } } }); // 初始更新 setTimeout(updateScrollbar, 100); // 保存更新函数到实例 this.updateScrollbar = updateScrollbar; } // Logo自动透明度设置 setupLogoAutoFade() { const logoWidget = document.querySelector('.logo-widget'); if (logoWidget) { // 5秒后自动降低透明度到70% setTimeout(() => { // logoWidget.style.transition = 'opacity 1s ease'; logoWidget.style.opacity = '0.5'; }, 5000); } } } // 添加CSS动画 const style = document.createElement('style'); style.textContent = ` @keyframes fadeInOut { 0% { opacity: 0; transform: translateY(-10px); } 20% { opacity: 1; transform: translateY(0); } 80% { opacity: 1; transform: translateY(0); } 100% { opacity: 0; transform: translateY(-10px); } } `; document.head.appendChild(style); // 应用程序启动 document.addEventListener('DOMContentLoaded', () => { window.app = new TeleprompterApp(); });