// ==UserScript==
// @name Enhanced Hourglass Timer
// @namespace http://tampermonkey.net/
// @version 1.0
// @description An enhanced hourglass timer with multiple timer management and visualization
// @author Your name
// @match *://*/*
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_notification
// @license MIT
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.min.js
// ==/UserScript==
(function() {
'use strict';
// Constants
const COLORS = {
BLUE: '#3498db',
RED: '#e74c3c',
GREEN: '#2ecc71',
YELLOW: '#f1c40f',
PURPLE: '#9b59b6',
ORANGE: '#e67e22'
};
// Styles
const styles = `
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap');
#enhanced-hourglass-button {
position: fixed;
bottom: 0;
right: 0;
width: 30px;
height: 30px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
color: #3498db;
border: 1px solid rgba(52, 152, 219, 0.3);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
z-index: 999999;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
transition: all 0.2s ease;
backdrop-filter: blur(2px);
user-select: none;
}
#enhanced-hourglass-button:hover {
background: rgba(255, 255, 255, 0.6);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15);
}
#enhanced-hourglass-button.active {
background: rgba(231, 76, 60, 0.2);
color: #e74c3c;
transform: rotate(180deg);
}
#enhanced-hourglass-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.98);
z-index: 99999;
font-family: 'Poppins', sans-serif;
display: none;
overflow-y: auto;
}
.hourglass-content {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
position: relative;
}
.hourglass-header {
background: #fff;
padding: 20px;
border-radius: 20px;
margin-bottom: 15px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
text-align: center;
}
.hourglass-title {
margin: 0;
font-size: 24px;
color: #333;
font-weight: 600;
}
.hourglass-controls {
background: #fff;
padding: 20px;
border-radius: 20px;
margin-bottom: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
}
.hourglass-controls .btn {
flex: 1;
min-width: 0;
padding: 10px;
margin: 0;
text-align: center;
}
.hourglass-presets {
background: #fff;
padding: 20px;
border-radius: 20px;
margin-bottom: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
}
.hourglass-presets h3 {
margin-top: 0;
font-size: 20px;
color: #333;
font-weight: 500;
margin-bottom: 20px;
}
.preset-buttons {
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.preset-buttons .btn {
flex: 1;
min-width: 120px;
}
.hourglass-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 15px;
margin-top: 20px;
}
.hourglass-item {
background: #fff;
border-radius: 15px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.05);
padding: 15px;
transition: all 0.3s ease;
cursor: move;
position: relative;
overflow: hidden;
}
.hourglass-item:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
}
.hourglass-item h3 {
margin: 0 0 15px 0;
font-size: 16px;
color: #333;
font-weight: 600;
}
.hourglass-item.dragging {
opacity: 0.7;
transform: scale(0.95);
}
.hourglass-item.drag-over {
border: 2px dashed #3498db;
box-shadow: 0 0 20px rgba(52, 152, 219, 0.3);
}
/* 沙漏视觉效果和动画 - 简化版 */
.hourglass-visual {
position: relative;
width: 50px;
height: 80px;
margin: 10px auto;
}
.hourglass-top,
.hourglass-bottom {
position: absolute;
width: 100%;
height: 47%;
background: rgba(240, 240, 240, 0.8);
overflow: hidden;
border-radius: 8px;
}
.hourglass-top {
top: 0;
clip-path: polygon(20% 0%, 80% 0%, 100% 100%, 0% 100%);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.hourglass-bottom {
bottom: 0;
clip-path: polygon(0% 0%, 100% 0%, 80% 100%, 20% 100%);
border-top: 1px solid rgba(0, 0, 0, 0.1);
}
.sand {
position: absolute;
width: 100%;
background: currentColor;
transition: height 1s linear;
}
.sand-top {
top: 0;
clip-path: polygon(20% 0%, 80% 0%, 100% 100%, 0% 100%);
}
.sand-bottom {
bottom: 0;
clip-path: polygon(0% 0%, 100% 0%, 80% 100%, 20% 100%);
}
.sand-flow {
position: absolute;
width: 2px;
left: 50%;
height: 60%;
top: 20%;
transform: translateX(-50%);
background: #f1c40f;
opacity: 0.7;
}
/* 进度条 */
.progress-bar {
height: 4px;
background: #f0f0f0;
border-radius: 2px;
overflow: hidden;
margin: 8px 0;
}
.progress-fill {
height: 100%;
background: #f1c40f;
transition: width 0.5s ease;
}
/* 时间显示 */
.time-display {
font-size: 18px;
font-weight: 600;
text-align: center;
color: #333;
margin: 10px 0;
}
/* 计时器信息 */
.timer-info {
padding: 10px;
margin: 8px 0;
background: rgba(240, 240, 250, 0.5);
border-radius: 10px;
font-size: 13px;
}
.timer-info p {
margin: 5px 0;
font-size: 13px;
color: #555;
}
/* 链接标签 */
.linked-badge {
display: inline-flex;
align-items: center;
padding: 6px 10px;
background: rgba(52, 152, 219, 0.1);
border-radius: 10px;
font-size: 12px;
color: #3498db;
margin-top: 8px;
}
.linked-badge:before {
content: '🔗';
margin-right: 5px;
}
/* 状态标签 */
.status-badge {
position: absolute;
top: 10px;
right: 10px;
padding: 3px 8px;
border-radius: 10px;
font-size: 11px;
font-weight: 500;
color: white;
}
.status-badge.running {
background: linear-gradient(135deg, #2ecc71, #27ae60);
}
.status-badge.paused {
background: linear-gradient(135deg, #f1c40f, #f39c12);
}
.status-badge.completed {
background: linear-gradient(135deg, #3498db, #2980b9);
}
/* 控制按钮 */
.timer-controls {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.btn {
padding: 8px 12px;
border: none;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
font-family: 'Poppins', sans-serif;
font-size: 13px;
font-weight: 500;
}
.btn-primary {
background: linear-gradient(135deg, #3498db, #2980b9);
color: white;
box-shadow: 0 2px 5px rgba(52, 152, 219, 0.3);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(52, 152, 219, 0.4);
}
.btn-danger {
background: linear-gradient(135deg, #e74c3c, #c0392b);
color: white;
box-shadow: 0 2px 5px rgba(231, 76, 60, 0.3);
}
.btn-danger:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(231, 76, 60, 0.4);
}
.btn-warning {
background: linear-gradient(135deg, #f1c40f, #f39c12);
color: white;
box-shadow: 0 2px 5px rgba(241, 196, 15, 0.3);
}
.btn-warning:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(241, 196, 15, 0.4);
}
/* 模态框 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 100000;
backdrop-filter: blur(3px);
}
.modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: #fff;
padding: 25px;
border-radius: 15px;
width: 400px;
max-width: 90%;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.modal-content h3 {
margin-top: 0;
margin-bottom: 20px;
font-size: 20px;
color: #333;
font-weight: 600;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
font-size: 14px;
}
.form-group input,
.form-group select {
width: 100%;
padding: 10px 12px;
border: 1px solid #ddd;
border-radius: 10px;
font-size: 14px;
transition: all 0.3s ease;
font-family: 'Poppins', sans-serif;
}
.form-group input:focus,
.form-group select:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
outline: none;
}
.error {
color: #e74c3c;
font-size: 12px;
margin-top: 5px;
font-weight: 400;
}
`;
// Timer class
class Timer {
constructor(config) {
this.id = Date.now();
this.name = config.name;
this.duration = config.duration;
this.unit = config.unit;
this.repeat = config.repeat || 'none';
this.color = config.color || '#f1c40f'; // 默认黄色
this.remainingTime = this.getTotalSeconds();
this.isRunning = false;
this.isPaused = false;
this.isCompleted = false;
this.linkedTimer = null;
this.lastCompletionDate = null;
}
getTotalSeconds() {
return this.duration * (this.unit === 'minutes' ? 60 : 3600);
}
start() {
if (!this.isRunning && !this.isCompleted) {
this.isRunning = true;
this.isPaused = false;
this.tick();
}
}
pause() {
this.isPaused = true;
this.isRunning = false;
}
resume() {
if (this.isPaused) {
this.isRunning = true;
this.isPaused = false;
this.tick();
}
}
reset() {
this.remainingTime = this.getTotalSeconds();
this.isRunning = false;
this.isPaused = false;
this.isCompleted = false;
}
tick() {
if (!this.isRunning) return;
if (this.remainingTime > 0) {
this.remainingTime--;
setTimeout(() => this.tick(), 1000);
} else {
this.isCompleted = true;
this.isRunning = false;
this.lastCompletionDate = new Date();
// 只设置完成标志,不记录统计数据
const app = window.hourglassApp;
if (app) {
// 设置完成标志,修改按钮为对勾
app.hasCompletedTimer = true;
const button = document.getElementById('enhanced-hourglass-button');
if (button) {
button.innerHTML = '✅';
}
// 从计时器列表中删除已完成的计时器
app.removeCompletedTimer(this);
}
// 显示通知
GM_notification({
title: '计时器完成',
text: `${this.name} 已完成!`,
timeout: 5000
});
if (this.repeat === 'continuous') {
this.reset();
this.start();
} else if (this.repeat === 'daily') {
// 安排明天的运行
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
tomorrow.setHours(0, 0, 0, 0);
const delay = tomorrow.getTime() - Date.now();
setTimeout(() => {
this.reset();
this.start();
}, delay);
} else if (this.linkedTimer) {
this.linkedTimer.start();
}
}
}
getFormattedTime() {
const hours = Math.floor(this.remainingTime / 3600);
const minutes = Math.floor((this.remainingTime % 3600) / 60);
const seconds = this.remainingTime % 60;
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
}
getProgress() {
return ((this.getTotalSeconds() - this.remainingTime) / this.getTotalSeconds()) * 100;
}
setLinkedTimer(timer) {
this.linkedTimer = timer;
}
removeLinkedTimer() {
this.linkedTimer = null;
}
getLinkedTimerId() {
return this.linkedTimer ? this.linkedTimer.id : null;
}
toJSON() {
return {
id: this.id,
name: this.name,
duration: this.duration,
unit: this.unit,
repeat: this.repeat,
color: this.color,
remainingTime: this.remainingTime,
isRunning: this.isRunning,
isPaused: this.isPaused,
isCompleted: this.isCompleted,
linkedTimerId: this.getLinkedTimerId(),
lastCompletionDate: this.lastCompletionDate
};
}
isSameDay(date1, date2) {
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
}
}
// Main class
class EnhancedHourglassTimer {
constructor() {
this.timers = [];
this.presets = [
{
name: "25分钟番茄钟",
duration: 25,
unit: "minutes",
repeat: "none",
color: "#f1c40f"
},
{
name: "5分钟休息",
duration: 5,
unit: "minutes",
repeat: "none",
color: "#f1c40f"
},
{
name: "15分钟会议",
duration: 15,
unit: "minutes",
repeat: "none",
color: "#f1c40f"
},
{
name: "1小时专注",
duration: 1,
unit: "hours",
repeat: "none",
color: "#f1c40f"
}
];
this.draggedTimer = null;
this.updateInterval = null;
this.isVisible = false;
this.hasCompletedTimer = false;
this.init();
this.checkDailyTimers();
}
init() {
this.createUI();
this.loadData();
this.attachEventListeners();
this.startAutoUpdate();
}
startAutoUpdate() {
// Update UI every second
this.updateInterval = setInterval(() => {
this.updateUI();
}, 1000);
}
createUI() {
// 创建浮动按钮
const button = document.createElement('button');
button.id = 'enhanced-hourglass-button';
button.innerHTML = '⌛';
button.title = '显示/隐藏计时器面板';
document.body.appendChild(button);
// 添加按钮点击事件,重置完成标志和切换面板
button.addEventListener('click', (e) => {
console.log("Button clicked"); // 添加调试日志
e.stopPropagation();
if (this.hasCompletedTimer) {
// 如果有计时器完成,点击后重置按钮
this.hasCompletedTimer = false;
button.innerHTML = '⌛';
}
this.togglePanel();
});
// 创建主容器并翻译为中文
const container = document.createElement('div');
container.id = 'enhanced-hourglass-container';
container.innerHTML = `
<div class="hourglass-content">
<div class="hourglass-header">
<h2 class="hourglass-title">增强型沙漏计时器</h2>
</div>
<div class="hourglass-controls">
<button class="btn btn-primary" id="add-timer">添加计时器</button>
<button class="btn btn-primary" id="pause-all">全部暂停</button>
<button class="btn btn-danger" id="reset-all">全部重置</button>
<button class="btn btn-danger" id="clear-all">全部清除</button>
<button class="btn btn-primary" id="export-data">导出数据</button>
<input type="file" id="import-file" accept=".json" style="display: none;">
<button class="btn btn-primary" id="import-data">导入数据</button>
</div>
<div class="hourglass-presets">
<h3>快速预设</h3>
<div class="preset-buttons">
${this.presets.map(preset => `
<button class="btn btn-primary" data-preset='${JSON.stringify(preset)}'>
${preset.name}
</button>
`).join('')}
</div>
</div>
<div class="hourglass-grid" id="timer-grid"></div>
</div>
<div class="modal" id="add-timer-modal">
<div class="modal-content">
<h3>添加新计时器</h3>
<form id="timer-form">
<div class="form-group">
<label for="timer-name">名称</label>
<input type="text" id="timer-name" required>
<div class="error" id="name-error"></div>
</div>
<div class="form-group">
<label for="timer-duration">时长</label>
<input type="number" id="timer-duration" min="1" required>
<div class="error" id="duration-error"></div>
</div>
<div class="form-group">
<label for="timer-unit">单位</label>
<select id="timer-unit">
<option value="minutes">分钟</option>
<option value="hours">小时</option>
</select>
</div>
<div class="form-group">
<label for="timer-repeat">重复模式</label>
<select id="timer-repeat">
<option value="none">不重复</option>
<option value="continuous">持续重复</option>
<option value="daily">每日重复</option>
</select>
</div>
<button type="submit" class="btn btn-primary">创建计时器</button>
<button type="button" class="btn btn-danger" id="cancel-timer">取消</button>
</form>
</div>
</div>
`;
document.body.appendChild(container);
GM_addStyle(styles);
}
attachEventListeners() {
document.getElementById('add-timer').addEventListener('click', () => this.showAddTimerModal());
document.getElementById('pause-all').addEventListener('click', () => this.pauseAllTimers());
document.getElementById('reset-all').addEventListener('click', () => this.resetAllTimers());
document.getElementById('clear-all').addEventListener('click', () => this.clearAllTimers());
// 添加预设按钮监听器
const presetButtons = document.querySelectorAll('.preset-buttons .btn');
presetButtons.forEach(button => {
button.addEventListener('click', (e) => {
const preset = JSON.parse(e.target.dataset.preset);
const timer = new Timer(preset);
this.timers.push(timer);
this.saveData();
this.updateUI();
});
});
// 添加导入/导出监听器
document.getElementById('export-data').addEventListener('click', () => this.exportData());
document.getElementById('import-data').addEventListener('click', () => document.getElementById('import-file').click());
document.getElementById('import-file').addEventListener('change', (e) => this.importData(e));
// 按ESC键关闭面板
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.isVisible) {
this.togglePanel();
}
});
}
togglePanel() {
console.log("togglePanel called"); // 添加调试日志
const button = document.getElementById('enhanced-hourglass-button');
const container = document.getElementById('enhanced-hourglass-container');
this.isVisible = !this.isVisible;
container.style.display = this.isVisible ? 'block' : 'none';
button.classList.toggle('active', this.isVisible);
// 防止背景滚动
document.body.style.overflow = this.isVisible ? 'hidden' : '';
}
loadData() {
const savedData = GM_getValue('hourglassData');
if (savedData) {
const data = JSON.parse(savedData);
this.timers = data.map(timerData => {
const timer = new Timer({
name: timerData.name,
duration: timerData.duration,
unit: timerData.unit,
repeat: timerData.repeat,
color: timerData.color || '#f1c40f'
});
timer.id = timerData.id;
timer.remainingTime = timerData.remainingTime;
timer.isRunning = timerData.isRunning;
timer.isPaused = timerData.isPaused;
timer.isCompleted = timerData.isCompleted;
timer.lastCompletionDate = timerData.lastCompletionDate;
return timer;
});
// 恢复计时器链接
data.forEach((timerData, index) => {
if (timerData.linkedTimerId) {
const linkedTimer = this.timers.find(t => t.id === timerData.linkedTimerId);
if (linkedTimer) {
this.timers[index].setLinkedTimer(linkedTimer);
}
}
});
// 恢复运行中的计时器
this.timers.forEach(timer => {
if (timer.isRunning) {
timer.start();
}
});
}
}
saveData() {
GM_setValue('hourglassData', JSON.stringify(this.timers));
}
showAddTimerModal() {
const modal = document.getElementById('add-timer-modal');
modal.style.display = 'block';
const form = document.getElementById('timer-form');
const cancelBtn = document.getElementById('cancel-timer');
const closeModal = () => {
modal.style.display = 'none';
form.reset();
};
cancelBtn.onclick = closeModal;
modal.onclick = (e) => {
if (e.target === modal) closeModal();
};
form.onsubmit = (e) => {
e.preventDefault();
const name = document.getElementById('timer-name').value;
const duration = parseInt(document.getElementById('timer-duration').value);
const unit = document.getElementById('timer-unit').value;
const repeat = document.getElementById('timer-repeat').value;
const color = "#f1c40f"; // 固定黄色
if (!this.validateTimerForm(name, duration)) return;
const timer = new Timer({ name, duration, unit, repeat, color });
this.timers.push(timer);
this.saveData();
this.updateUI();
closeModal();
};
}
validateTimerForm(name, duration) {
let isValid = true;
const nameError = document.getElementById('name-error');
const durationError = document.getElementById('duration-error');
nameError.textContent = '';
durationError.textContent = '';
if (!name.trim()) {
nameError.textContent = 'Name is required';
isValid = false;
}
if (isNaN(duration) || duration < 1) {
durationError.textContent = 'Duration must be a positive number';
isValid = false;
}
return isValid;
}
pauseAllTimers() {
this.timers.forEach(timer => {
if (timer.isRunning) {
timer.pause();
}
});
this.updateUI();
}
resetAllTimers() {
this.timers.forEach(timer => {
timer.reset();
});
this.updateUI();
}
clearAllTimers() {
// 移除确认对话框,直接清除
this.timers = [];
this.saveData();
this.updateUI();
}
updateUI() {
const grid = document.getElementById('timer-grid');
grid.innerHTML = '';
// 显示活动计时器
this.timers.forEach(timer => {
const timerElement = this.createTimerElement(timer);
grid.appendChild(timerElement);
});
}
createTimerElement(timer) {
const element = document.createElement('div');
element.className = 'hourglass-item';
element.setAttribute('draggable', true);
element.setAttribute('data-timer-id', timer.id);
// 固定沙子为黄色
const color = "#f1c40f";
// 状态标签
const statusBadge = timer.isRunning ?
'<div class="status-badge running">运行中</div>' :
timer.isPaused ?
'<div class="status-badge paused">已暂停</div>' :
timer.isCompleted ?
'<div class="status-badge completed">已完成</div>' : '';
// 修正沙漏流向 - 沙子从上往下流
const progress = timer.getProgress();
const hourglassVisual = `
<div class="hourglass-visual">
<div class="hourglass-container">
<div class="hourglass-top">
<div class="sand sand-top" style="color: ${color}; height: ${100 - progress}%"></div>
</div>
${timer.isRunning ? `
<div class="sand-flow" style="color: ${color};"></div>
` : ''}
<div class="hourglass-bottom">
<div class="sand sand-bottom" style="color: ${color}; height: ${progress}%"></div>
</div>
</div>
</div>
`;
const timeDisplay = `<div class="time-display">${timer.getFormattedTime()}</div>`;
const progressBar = `
<div class="progress-bar">
<div class="progress-fill" style="color: ${color}; width: ${progress}%"></div>
</div>
`;
const timerInfo = `
<div class="timer-info">
<p><strong>重复:</strong> ${this.translateRepeat(timer.repeat)}</p>
${timer.linkedTimer ? `
<div class="linked-badge">链接到: ${timer.linkedTimer.name}</div>
` : ''}
</div>
`;
const timerControls = `
<div class="timer-controls">
<button class="btn btn-primary start-pause">
${timer.isRunning ? '暂停' : '开始'}
</button>
<button class="btn btn-danger reset">重置</button>
<button class="btn btn-danger delete">删除</button>
${timer.linkedTimer ?
`<button class="btn btn-warning unlink">解除链接</button>` :
''}
</div>
`;
element.innerHTML = `
${statusBadge}
<h3>${timer.name}</h3>
${hourglassVisual}
${timeDisplay}
${progressBar}
${timerInfo}
${timerControls}
`;
// 不为归档计时器添加拖拽事件
element.addEventListener('dragstart', (e) => {
this.draggedTimer = timer;
element.classList.add('dragging');
e.dataTransfer.setData('text/plain', timer.id);
});
element.addEventListener('dragend', () => {
element.classList.remove('dragging');
this.draggedTimer = null;
});
element.addEventListener('dragover', (e) => {
e.preventDefault();
if (this.draggedTimer && this.draggedTimer !== timer) {
element.classList.add('drag-over');
}
});
element.addEventListener('dragleave', () => {
element.classList.remove('drag-over');
});
element.addEventListener('drop', (e) => {
e.preventDefault();
element.classList.remove('drag-over');
if (this.draggedTimer && this.draggedTimer !== timer) {
this.draggedTimer.setLinkedTimer(timer);
this.saveData();
this.updateUI();
}
});
// 添加控制按钮事件
const startPauseBtn = element.querySelector('.start-pause');
const resetBtn = element.querySelector('.reset');
const deleteBtn = element.querySelector('.delete');
const unlinkBtn = element.querySelector('.unlink');
if (startPauseBtn) {
startPauseBtn.addEventListener('click', () => {
if (timer.isRunning) {
timer.pause();
} else {
timer.start();
}
this.updateUI();
});
}
if (resetBtn) {
resetBtn.addEventListener('click', () => {
timer.reset();
this.updateUI();
});
}
if (deleteBtn) {
deleteBtn.addEventListener('click', () => {
// 移除链接到此计时器的引用
this.timers.forEach(t => {
if (t.linkedTimer === timer) {
t.removeLinkedTimer();
}
});
// 删除计时器
this.timers = this.timers.filter(t => t.id !== timer.id);
this.saveData();
this.updateUI();
});
}
if (unlinkBtn) {
unlinkBtn.addEventListener('click', () => {
timer.removeLinkedTimer();
this.saveData();
this.updateUI();
});
}
return element;
}
translateRepeat(repeat) {
const translations = {
'none': '不重复',
'continuous': '持续重复',
'daily': '每日重复'
};
return translations[repeat] || repeat;
}
exportData() {
const data = JSON.stringify(this.timers, null, 2);
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'hourglass-timers.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
importData(e) {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
try {
const data = JSON.parse(event.target.result);
this.timers = data.map(timerData => {
const timer = new Timer({
name: timerData.name,
duration: timerData.duration,
unit: timerData.unit,
repeat: timerData.repeat,
color: timerData.color || '#f1c40f'
});
timer.id = timerData.id;
timer.remainingTime = timerData.remainingTime;
timer.isRunning = false; // Start imported timers in stopped state
timer.isPaused = false;
timer.isCompleted = timerData.isCompleted;
timer.lastCompletionDate = timerData.lastCompletionDate;
return timer;
});
this.saveData();
this.updateUI();
alert('Data imported successfully!');
} catch (error) {
alert('Error importing data: Invalid file format');
}
};
reader.readAsText(file);
}
}
checkDailyTimers() {
// Check daily timers every minute
setInterval(() => {
const now = new Date();
this.timers.forEach(timer => {
if (timer.repeat === 'daily' && !timer.isRunning) {
const lastCompletion = timer.lastCompletionDate ? new Date(timer.lastCompletionDate) : null;
if (!lastCompletion || !this.isSameDay(lastCompletion, now)) {
timer.reset();
timer.start();
}
}
});
}, 60000);
}
isSameDay(date1, date2) {
return date1.getFullYear() === date2.getFullYear() &&
date1.getMonth() === date2.getMonth() &&
date1.getDate() === date2.getDate();
}
// 添加删除已完成计时器的方法
removeCompletedTimer(timer) {
// 如果有链接,先解除链接
this.timers.forEach(t => {
if (t.linkedTimer === timer) {
t.removeLinkedTimer();
}
});
// 从计时器列表中移除
this.timers = this.timers.filter(t => t.id !== timer.id);
this.saveData();
this.updateUI();
}
}
// Initialize the application
const app = new EnhancedHourglassTimer();
window.hourglassApp = app;
})();