-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
135 lines (117 loc) · 3.81 KB
/
script.js
File metadata and controls
135 lines (117 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
const FALL_ASLEEP_MINS = 14;
const CYCLE_MINS = 90;
const CYCLES = [1, 2, 3, 4, 5, 6, 7];
function pad(n) { return String(n).padStart(2, '0'); }
function formatTime(date) {
const h = date.getHours();
const m = date.getMinutes();
return {
display: `${pad(h)}:${pad(m)}`,
};
}
function cycleLabel(n) {
const totalMins = n * CYCLE_MINS;
const h = Math.floor(totalMins / 60);
const m = totalMins % 60;
const parts = [`${n} cycle${n !== 1 ? 's' : ''}`];
if (h > 0) parts.push(`${h} hour${h !== 1 ? 's' : ''}`);
if (m > 0) parts.push(`${m} minutes`);
return parts.join(' · ');
}
function icsDateTime(date) {
const Y = date.getFullYear();
const M = pad(date.getMonth() + 1);
const D = pad(date.getDate());
const h = pad(date.getHours());
const m = pad(date.getMinutes());
const s = pad(date.getSeconds());
return `${Y}${M}${D}T${h}${m}${s}`;
}
function downloadICS(wakeDate, cycles) {
const uid = `slumber-${Date.now()}@sleepcalc`;
const dtStart = icsDateTime(wakeDate);
const dtEnd = icsDateTime(new Date(wakeDate.getTime() + 15 * 60 * 1000));
const now = icsDateTime(new Date());
const { display } = formatTime(wakeDate);
const label = cycleLabel(cycles);
const ics = [
'BEGIN:VCALENDAR',
'VERSION:2.0',
'PRODID:-//SleepCalc//Wake Up Alarm//EN',
'CALSCALE:GREGORIAN',
'METHOD:PUBLISH',
'BEGIN:VEVENT',
`UID:${uid}`,
`DTSTAMP:${now}`,
`DTSTART:${dtStart}`,
`DTEND:${dtEnd}`,
`SUMMARY:Wake up \u2014 ${display}`,
`DESCRIPTION:${label}`,
'BEGIN:VALARM',
'ACTION:DISPLAY',
'DESCRIPTION:Time to wake up!',
'TRIGGER:PT0S',
'END:VALARM',
'END:VEVENT',
'END:VCALENDAR',
].join('\r\n');
const blob = new Blob([ics], { type: 'text/calendar;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = Object.assign(document.createElement('a'), {
href: url,
download: `wake-${display.replace(':', '')}.ics`,
});
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
function buildWakeList(now) {
const list = document.getElementById('wake-list');
list.innerHTML = '';
CYCLES.forEach((cycles, idx) => {
const totalMins = FALL_ASLEEP_MINS + cycles * CYCLE_MINS;
const wakeDate = new Date(now.getTime() + totalMins * 60 * 1000);
const { display } = formatTime(wakeDate);
const li = document.createElement('li');
li.className = 'wake-item';
li.style.setProperty('--item-delay', `${idx * 0.08}s`);
li.setAttribute('role', 'listitem');
li.setAttribute('aria-label', `${display} — ${cycleLabel(cycles)}`);
li.innerHTML = `
<div class="card-left">
<span class="wake-time">${display}</span>
<span class="wake-meta">${cycleLabel(cycles)}</span>
</div>
<button class="alarm-icon" aria-label="Add to calendar" title="Download .ics alarm" data-wake="${wakeDate.getTime()}" data-cycles="${cycles}">
<svg viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="13" r="7"/>
<path d="M12 10v3l2 2"/>
<path d="M5 3L2 6"/>
<path d="M19 3l3 3"/>
</svg>
</button>
`;
li.querySelector('.alarm-icon').addEventListener('click', (e) => {
e.stopPropagation();
const ts = parseInt(e.currentTarget.dataset.wake, 10);
const c = parseInt(e.currentTarget.dataset.cycles, 10);
downloadICS(new Date(ts), c);
});
list.appendChild(li);
});
}
let lastMinute = -1;
function tick() {
const now = new Date();
const m = now.getMinutes();
const { display } = formatTime(now);
const label = document.getElementById('sleep-at-time');
if (label) label.textContent = display;
if (m !== lastMinute) {
lastMinute = m;
buildWakeList(now);
}
}
tick();
setInterval(tick, 1000);