2023-02-02 17:39:08 +00:00
|
|
|
<html>
|
|
|
|
<head>
|
|
|
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
|
|
|
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
|
|
|
<script src="../../core/lib/interface.js"></script>
|
2023-05-02 20:31:21 +00:00
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
|
2023-02-02 17:39:08 +00:00
|
|
|
<script>
|
|
|
|
let dataElement = document.getElementById("data");
|
|
|
|
let alarms;
|
|
|
|
let schedSettings;
|
|
|
|
|
|
|
|
function readFile(input) {
|
|
|
|
document.getElementById('upload').disabled = true;
|
|
|
|
const offsetMinutes = document.getElementById("offsetMinutes").value;
|
|
|
|
|
|
|
|
for(let i=0; i<input.files.length; i++) {
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.addEventListener("load", () => {
|
|
|
|
const jCalData = ICAL.parse(reader.result);
|
2023-05-02 20:31:21 +00:00
|
|
|
const comp = new ICAL.Component(jCalData);
|
2023-02-02 17:39:08 +00:00
|
|
|
// Fetch the VEVENT part
|
|
|
|
comp.getAllSubcomponents('vevent').forEach(vevent => {
|
|
|
|
event = new ICAL.Event(vevent);
|
|
|
|
const exists = alarms.some(alarm => alarm.id === event.uid);
|
|
|
|
|
|
|
|
const alarm = eventToAlarm(event, offsetMinutes*60*1000);
|
|
|
|
renderAlarm(alarm, exists);
|
|
|
|
|
|
|
|
if (exists) {
|
|
|
|
alarms = alarms.filter(alarm => alarm.id !== event.uid); // remove if already exists
|
|
|
|
const tr = document.querySelector(`.event-row[data-uid='${event.uid}']`);
|
|
|
|
document.getElementById('events').removeChild(tr);
|
|
|
|
}
|
|
|
|
alarms.push(alarm);
|
|
|
|
});
|
|
|
|
}, false);
|
|
|
|
|
|
|
|
reader.readAsText(input.files[i], "UTF-8");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function dateToMsSinceMidnight(date) {
|
|
|
|
const dateMidnight = new Date(date);
|
|
|
|
dateMidnight.setHours(0,0,0,0);
|
|
|
|
return date - dateMidnight;
|
|
|
|
}
|
|
|
|
|
|
|
|
function dateFromAlarm(alarm) {
|
|
|
|
const date = new Date(alarm.date);
|
|
|
|
return new Date(date.getTime() + alarm.t);
|
|
|
|
}
|
|
|
|
|
2023-05-02 20:31:21 +00:00
|
|
|
function formatDate(d) {
|
|
|
|
return d.getFullYear() + "-" + (d.getMonth() + 1).toString().padStart(2, '0') + "-" + d.getDate().toString().padStart(2, '0');
|
|
|
|
}
|
|
|
|
|
2023-02-02 17:39:08 +00:00
|
|
|
function getAlarmDefaults() {
|
|
|
|
const date = new Date();
|
|
|
|
return {
|
|
|
|
on: true,
|
|
|
|
t: dateToMsSinceMidnight(date),
|
|
|
|
dow: 127,
|
2023-05-02 20:31:21 +00:00
|
|
|
date: formatDate(date),
|
2023-02-02 17:39:08 +00:00
|
|
|
last: 0,
|
|
|
|
rp: "defaultRepeat" in schedSettings ? schedSettings.defaultRepeat : false,
|
|
|
|
vibrate: "defaultAlarmPattern" in schedSettings ? schedSettings.defaultAlarmPattern : "::",
|
|
|
|
as: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function eventToAlarm(event, offsetMs) {
|
|
|
|
const dateOrig = event.startDate.toJSDate();
|
|
|
|
const date = offsetMs ? new Date(dateOrig - offsetMs) : dateOrig;
|
|
|
|
|
|
|
|
const alarm = {...getAlarmDefaults(), ...{
|
|
|
|
id: event.uid,
|
|
|
|
msg: event.summary,
|
|
|
|
t: dateToMsSinceMidnight(date),
|
2023-05-02 20:31:21 +00:00
|
|
|
date: formatDate(date),
|
2023-02-02 17:39:08 +00:00
|
|
|
data: {end: event.endDate.toJSDate().toISOString()}
|
|
|
|
}};
|
|
|
|
if (offsetMs) { // Alarm time is not real event time, so do a backup
|
|
|
|
alarm.data.time = dateOrig.toISOString();
|
|
|
|
}
|
|
|
|
return alarm;
|
|
|
|
}
|
|
|
|
|
|
|
|
function upload() {
|
|
|
|
Util.showModal("Saving...");
|
|
|
|
Util.writeStorage("sched.json", JSON.stringify(alarms), () => {
|
2023-06-05 10:02:25 +00:00
|
|
|
Puck.write(`\x10E.showMessage("Hold button to\\nreload alarms")\n`, () => {
|
|
|
|
location.reload(); // reload so we see current data
|
2023-05-29 12:58:06 +00:00
|
|
|
});
|
2023-02-02 17:39:08 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function renderAlarm(alarm, exists) {
|
2023-05-29 12:32:25 +00:00
|
|
|
const localDate = alarm.date ? dateFromAlarm(alarm) : null;
|
2023-02-02 17:39:08 +00:00
|
|
|
|
|
|
|
const tr = document.createElement('tr');
|
|
|
|
tr.classList.add('event-row');
|
|
|
|
tr.dataset.uid = alarm.id;
|
2023-05-29 12:32:25 +00:00
|
|
|
const tdType = document.createElement('td');
|
|
|
|
tdType.type = "text";
|
|
|
|
tdType.classList.add('event-summary');
|
|
|
|
tr.appendChild(tdType);
|
2023-02-02 17:39:08 +00:00
|
|
|
const inputTime = document.createElement('input');
|
2023-05-29 12:32:25 +00:00
|
|
|
if (localDate) {
|
2023-05-29 19:51:23 +00:00
|
|
|
tdType.textContent = "Event";
|
2023-05-29 12:32:25 +00:00
|
|
|
inputTime.type = "datetime-local";
|
|
|
|
inputTime.value = localDate.toISOString().slice(0,16);
|
|
|
|
inputTime.onchange = (e => {
|
|
|
|
const date = new Date(inputTime.value);
|
|
|
|
alarm.t = dateToMsSinceMidnight(date);
|
|
|
|
alarm.date = formatDate(date);
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const [hours, mins, secs] = msToHMS(alarm.timer || alarm.t);
|
|
|
|
|
|
|
|
inputTime.type = "time";
|
|
|
|
inputTime.step = 1; // display seconds
|
|
|
|
inputTime.value = `${hours}:${mins}:${secs}`;
|
|
|
|
|
|
|
|
if (alarm.timer) {
|
|
|
|
tdType.textContent = "Timer";
|
|
|
|
inputTime.onchange = e => {
|
|
|
|
alarm.timer = hmsToMs(inputTime.value);
|
|
|
|
const now = new Date();
|
|
|
|
const currentTime = (now.getHours()*3600000)+(now.getMinutes()*60000)+(now.getSeconds()*1000);
|
|
|
|
alarm.t = currentTime + alarm.timer;
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
tdType.textContent = "Alarm";
|
|
|
|
inputTime.onchange = e => {
|
|
|
|
alarm.t = hmsToMs(inputTime.value);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2023-05-29 20:01:17 +00:00
|
|
|
if (!exists) {
|
|
|
|
const asterisk = document.createElement('sup');
|
|
|
|
asterisk.textContent = '*';
|
|
|
|
tdType.appendChild(asterisk);
|
|
|
|
}
|
2023-02-02 17:39:08 +00:00
|
|
|
inputTime.classList.add('event-date');
|
|
|
|
inputTime.classList.add('form-input');
|
|
|
|
inputTime.dataset.uid = alarm.id;
|
2023-05-29 12:32:25 +00:00
|
|
|
const tdTime = document.createElement('td');
|
|
|
|
tr.appendChild(tdTime);
|
2023-02-02 17:39:08 +00:00
|
|
|
tdTime.appendChild(inputTime);
|
|
|
|
|
|
|
|
const tdSummary = document.createElement('td');
|
|
|
|
tr.appendChild(tdSummary);
|
|
|
|
const inputSummary = document.createElement('input');
|
|
|
|
inputSummary.type = "text";
|
|
|
|
inputSummary.classList.add('event-summary');
|
|
|
|
inputSummary.classList.add('form-input');
|
|
|
|
inputSummary.dataset.uid = alarm.id;
|
|
|
|
inputSummary.maxLength=40;
|
|
|
|
const realHumanStartTime = alarm.data?.time ? ' ' + (new Date(alarm.data.time)).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '';
|
|
|
|
const summary = (alarm.msg?.substring(0, inputSummary.maxLength) || "");
|
|
|
|
inputSummary.value = summary.endsWith(realHumanStartTime) ? summary : summary + realHumanStartTime;
|
|
|
|
inputSummary.onchange = (e => {
|
|
|
|
alarm.msg = inputSummary.value;
|
|
|
|
});
|
|
|
|
tdSummary.appendChild(inputSummary);
|
|
|
|
inputSummary.onchange();
|
|
|
|
|
2023-05-29 13:18:01 +00:00
|
|
|
const tdOptions = document.createElement('td');
|
|
|
|
tr.appendChild(tdOptions);
|
2023-02-02 17:39:08 +00:00
|
|
|
|
2023-05-29 13:09:23 +00:00
|
|
|
const onOffCheck = document.createElement('input');
|
|
|
|
onOffCheck.type = 'checkbox';
|
|
|
|
onOffCheck.checked = alarm.on;
|
|
|
|
onOffCheck.onchange = e => {
|
|
|
|
alarm.on = !alarm.on;
|
|
|
|
};
|
|
|
|
const onOffIcon = document.createElement('i');
|
|
|
|
onOffIcon.classList.add('form-icon');
|
|
|
|
const onOff = document.createElement('label');
|
|
|
|
onOff.classList.add('form-switch');
|
|
|
|
onOff.appendChild(onOffCheck);
|
|
|
|
onOff.appendChild(onOffIcon);
|
2023-05-29 13:18:01 +00:00
|
|
|
tdOptions.appendChild(onOff);
|
|
|
|
|
|
|
|
const tdInfo = document.createElement('td');
|
|
|
|
tr.appendChild(tdInfo);
|
2023-05-29 13:09:23 +00:00
|
|
|
|
2023-02-02 17:39:08 +00:00
|
|
|
const buttonDelete = document.createElement('button');
|
|
|
|
buttonDelete.classList.add('btn');
|
|
|
|
buttonDelete.classList.add('btn-action');
|
2023-05-29 13:09:23 +00:00
|
|
|
tdInfo.appendChild(buttonDelete);
|
2023-02-02 17:39:08 +00:00
|
|
|
const iconDelete = document.createElement('i');
|
|
|
|
iconDelete.classList.add('icon');
|
|
|
|
iconDelete.classList.add('icon-delete');
|
|
|
|
buttonDelete.appendChild(iconDelete);
|
|
|
|
buttonDelete.onclick = (e => {
|
|
|
|
alarms = alarms.filter(a => a !== alarm);
|
|
|
|
document.getElementById('events').removeChild(tr);
|
|
|
|
});
|
|
|
|
|
|
|
|
document.getElementById('events').appendChild(tr);
|
|
|
|
document.getElementById('upload').disabled = false;
|
|
|
|
}
|
|
|
|
|
2023-05-29 12:32:25 +00:00
|
|
|
function msToHMS(ms) {
|
|
|
|
let secs = Math.floor(ms / 1000) % 60;
|
|
|
|
let mins = Math.floor(ms / 1000 / 60) % 60;
|
|
|
|
let hours = Math.floor(ms / 1000 / 60 / 60);
|
|
|
|
if (secs < 10) secs = "0" + secs;
|
|
|
|
if (mins < 10) mins = "0" + mins;
|
|
|
|
if (hours < 10) hours = "0" + hours;
|
|
|
|
return [hours, mins, secs];
|
|
|
|
}
|
|
|
|
|
|
|
|
function hmsToMs(hms) {
|
|
|
|
let [hours, mins, secs] = hms.split(":");
|
|
|
|
hours = Number(hours);
|
|
|
|
mins = Number(mins);
|
|
|
|
secs = Number(secs);
|
|
|
|
return ((hours * 60 + mins) * 60 + secs) * 1000;
|
|
|
|
}
|
|
|
|
|
2023-05-29 19:51:23 +00:00
|
|
|
function addEvent() {
|
|
|
|
const event = getAlarmDefaults();
|
|
|
|
renderAlarm(event);
|
|
|
|
alarms.push(event);
|
2023-02-02 17:39:08 +00:00
|
|
|
}
|
|
|
|
|
2023-05-29 19:51:23 +00:00
|
|
|
function addAlarm() {
|
2023-05-29 12:49:33 +00:00
|
|
|
const alarm = getAlarmDefaults();
|
|
|
|
delete alarm.date;
|
|
|
|
renderAlarm(alarm);
|
|
|
|
alarms.push(alarm);
|
|
|
|
}
|
|
|
|
|
|
|
|
function addTimer() {
|
|
|
|
const alarmDefaults = getAlarmDefaults();
|
|
|
|
const timer = {
|
|
|
|
timer: hmsToMs("00:00:30"),
|
|
|
|
t: 0,
|
2023-05-29 19:51:01 +00:00
|
|
|
on: alarmDefaults.on,
|
2023-05-29 12:49:33 +00:00
|
|
|
dow: alarmDefaults.dow,
|
|
|
|
last: alarmDefaults.last,
|
|
|
|
rp: alarmDefaults.rp,
|
|
|
|
vibrate: alarmDefaults.vibrate,
|
|
|
|
as: alarmDefaults.as,
|
|
|
|
};;
|
|
|
|
renderAlarm(timer);
|
|
|
|
alarms.push(timer);
|
|
|
|
}
|
|
|
|
|
2023-02-02 17:39:08 +00:00
|
|
|
function getData() {
|
|
|
|
Util.showModal("Loading...");
|
|
|
|
Util.readStorage('sched.json',data=>{
|
|
|
|
alarms = JSON.parse(data || "[]") || [];
|
|
|
|
|
|
|
|
Util.readStorage('sched.settings.json',data=>{
|
|
|
|
schedSettings = JSON.parse(data || "{}") || {};
|
|
|
|
Util.hideModal();
|
2023-05-29 12:32:39 +00:00
|
|
|
alarms.sort((a, b) => {
|
|
|
|
let x;
|
|
|
|
|
|
|
|
x = !!b.date - !!a.date;
|
|
|
|
if(x) return x;
|
|
|
|
|
|
|
|
x = !!a.timer - !!b.timer;
|
|
|
|
if(x) return x;
|
|
|
|
|
|
|
|
return a.t - b.t;
|
|
|
|
});
|
2023-02-02 17:39:08 +00:00
|
|
|
alarms.forEach(alarm => {
|
2023-05-29 12:32:25 +00:00
|
|
|
renderAlarm(alarm, true);
|
2023-02-02 17:39:08 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// Called when app starts
|
|
|
|
function onInit() {
|
|
|
|
getData();
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h4>Manage dated events</h4>
|
|
|
|
|
|
|
|
<div class="float-right">
|
2023-05-29 19:51:23 +00:00
|
|
|
<button class="btn" onclick="addEvent()">
|
2023-02-02 17:39:08 +00:00
|
|
|
<i class="icon icon-plus"></i>
|
2023-05-29 19:51:23 +00:00
|
|
|
Event
|
2023-05-29 12:49:33 +00:00
|
|
|
</button>
|
2023-05-29 19:51:23 +00:00
|
|
|
<button class="btn" onclick="addAlarm()">
|
2023-05-29 12:49:33 +00:00
|
|
|
<i class="icon icon-plus"></i>
|
|
|
|
Alarm
|
|
|
|
</button>
|
|
|
|
<button class="btn" onclick="addTimer()">
|
|
|
|
<i class="icon icon-plus"></i>
|
|
|
|
Timer
|
2023-02-02 17:39:08 +00:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<table class="table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
2023-05-29 12:32:25 +00:00
|
|
|
<th>Type</th>
|
|
|
|
<th>Date/Time</th>
|
2023-02-02 17:39:08 +00:00
|
|
|
<th>Summary</th>
|
2023-05-29 19:50:38 +00:00
|
|
|
<th>On?</th>
|
2023-05-29 13:18:01 +00:00
|
|
|
<th></th>
|
2023-02-02 17:39:08 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody id="events">
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<div class="divider"></div>
|
|
|
|
<div class="form-horizontal">
|
|
|
|
<div class="form-group">
|
|
|
|
<div class="col-5 col-xs-12">
|
|
|
|
<label class="form-label" for="fileinput">Add from iCalendar file</label>
|
|
|
|
</div>
|
|
|
|
<div class="col-7 col-xs-12">
|
|
|
|
<input id="fileinput" class="form-input" type="file" onchange="readFile(this)" accept=".ics,.ifb,.ical,.ifbf" multiple/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
|
|
<div class="col-5 col-xs-12">
|
|
|
|
<label class="form-label" for="fileinput">Minutes to alarm in advance</label>
|
|
|
|
</div>
|
|
|
|
<div class="col-7 col-xs-12">
|
|
|
|
<input id="offsetMinutes" class="form-input" type="number" value="0" min="0" step="5"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="divider"></div>
|
|
|
|
|
|
|
|
<button id="upload" class="btn btn-primary" onClick="upload()" disabled>Upload</button>
|
|
|
|
<button id="reload" class="btn" onClick="location.reload()">Reload</button>
|
|
|
|
</body>
|
|
|
|
</html>
|