Auto commit: 2025-11-21T18:29:31.267Z
This commit is contained in:
parent
41111c794f
commit
6a4e5985e5
161
api/alarms.php
161
api/alarms.php
@ -3,98 +3,111 @@ require_once __DIR__ . '/../db/config.php';
|
|||||||
|
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
$response = ['success' => false, 'message' => 'Invalid request'];
|
$response = ['success' => false, 'error' => 'Invalid request'];
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'GET' && isset($_GET['action'])) {
|
$action = $_GET['action'] ?? $_POST['action'] ?? '';
|
||||||
if ($_GET['action'] === 'check') {
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'create') {
|
||||||
|
$alarm_time = $_POST['alarm_time'] ?? null;
|
||||||
|
|
||||||
|
if ($alarm_time) {
|
||||||
try {
|
try {
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
// Find active alarms that are due and lock the rows
|
// 1. Create a new note
|
||||||
$stmt = $pdo->prepare("SELECT * FROM alarms WHERE alarm_time <= CURTIME() AND is_active = 1 FOR UPDATE");
|
$noteStmt = $pdo->prepare("INSERT INTO notes (content) VALUES (?)");
|
||||||
$stmt->execute();
|
$noteStmt->execute(['']);
|
||||||
$alarms = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
$noteId = $pdo->lastInsertId();
|
||||||
|
|
||||||
if ($alarms) {
|
// 2. Create the alarm and link it to the new note
|
||||||
// Deactivate the found alarms to prevent them from ringing again
|
$alarmStmt = $pdo->prepare("INSERT INTO alarms (alarm_time, note_id, is_active) VALUES (?, ?, 1)");
|
||||||
$alarmIds = array_map(function($alarm) {
|
$alarmStmt->execute([$alarm_time, $noteId]);
|
||||||
return $alarm['id'];
|
$alarmId = $pdo->lastInsertId();
|
||||||
}, $alarms);
|
|
||||||
|
|
||||||
if (!empty($alarmIds)) {
|
|
||||||
$placeholders = implode(',', array_fill(0, count($alarmIds), '?'));
|
|
||||||
$updateStmt = $pdo->prepare("UPDATE alarms SET is_active = 0 WHERE id IN ($placeholders)");
|
|
||||||
$updateStmt->execute($alarmIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = ['success' => true, 'alarms' => $alarms];
|
|
||||||
} else {
|
|
||||||
$response = ['success' => true, 'alarms' => []];
|
|
||||||
}
|
|
||||||
|
|
||||||
$pdo->commit();
|
$pdo->commit();
|
||||||
|
|
||||||
|
$response = [
|
||||||
|
'success' => true,
|
||||||
|
'id' => $alarmId,
|
||||||
|
'note_id' => $noteId,
|
||||||
|
'alarm_time' => $alarm_time
|
||||||
|
];
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$pdo->rollBack();
|
$pdo->rollBack();
|
||||||
$response['message'] = 'Database error: ' . $e->getMessage();
|
$response['error'] = 'Database error: ' . $e->getMessage();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$response['error'] = 'Alarm time is required.';
|
||||||
}
|
}
|
||||||
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
}
|
||||||
// Simple routing based on a POST field
|
|
||||||
$action = $_POST['action'] ?? '';
|
|
||||||
|
|
||||||
if ($action === 'create') {
|
elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'get') {
|
||||||
$alarm_time = $_POST['alarm_time'] ?? null;
|
try {
|
||||||
$label = $_POST['label'] ?? '';
|
$stmt = $pdo->query("SELECT id, alarm_time, note_id, is_active FROM alarms ORDER BY alarm_time");
|
||||||
|
$alarms = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
$response = ['success' => true, 'alarms' => $alarms];
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$response['error'] = 'Database error: ' . $e->getMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($alarm_time) {
|
elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'delete') {
|
||||||
try {
|
$id = $_GET['id'] ?? null;
|
||||||
$stmt = $pdo->prepare("INSERT INTO alarms (alarm_time, label) VALUES (?, ?)");
|
if ($id) {
|
||||||
$stmt->execute([$alarm_time, $label]);
|
try {
|
||||||
$response = ['success' => true, 'message' => 'Alarm created successfully.', 'id' => $pdo->lastInsertId()];
|
$stmt = $pdo->prepare("DELETE FROM alarms WHERE id = ?");
|
||||||
} catch (PDOException $e) {
|
$stmt->execute([$id]);
|
||||||
$response['message'] = 'Database error: ' . $e->getMessage();
|
$response = ['success' => true];
|
||||||
}
|
} catch (PDOException $e) {
|
||||||
} else {
|
$response['error'] = 'Database error: ' . $e->getMessage();
|
||||||
$response['message'] = 'Alarm time is required.';
|
|
||||||
}
|
}
|
||||||
} elseif ($action === 'delete') {
|
} else {
|
||||||
$alarm_id = $_POST['alarm_id'] ?? null;
|
$response['error'] = 'ID is required.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ($alarm_id) {
|
elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && $action === 'toggle') {
|
||||||
try {
|
$id = $_POST['id'] ?? null;
|
||||||
$stmt = $pdo->prepare("DELETE FROM alarms WHERE id = ?");
|
$is_active = isset($_POST['is_active']) ? (int)$_POST['is_active'] : null;
|
||||||
$stmt->execute([$alarm_id]);
|
|
||||||
if ($stmt->rowCount()) {
|
|
||||||
$response = ['success' => true, 'message' => 'Alarm deleted successfully.'];
|
|
||||||
} else {
|
|
||||||
$response['message'] = 'Alarm not found.';
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$response['message'] = 'Database error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$response['message'] = 'Alarm ID is required.';
|
|
||||||
}
|
|
||||||
} elseif ($action === 'toggle') {
|
|
||||||
$alarm_id = $_POST['alarm_id'] ?? null;
|
|
||||||
$is_active = $_POST['is_active'] ?? null;
|
|
||||||
|
|
||||||
if ($alarm_id && $is_active !== null) {
|
if ($id && $is_active !== null) {
|
||||||
try {
|
try {
|
||||||
$stmt = $pdo->prepare("UPDATE alarms SET is_active = ? WHERE id = ?");
|
$stmt = $pdo->prepare("UPDATE alarms SET is_active = ? WHERE id = ?");
|
||||||
$stmt->execute([$is_active, $alarm_id]);
|
$stmt->execute([$is_active, $id]);
|
||||||
if ($stmt->rowCount()) {
|
$response = ['success' => true];
|
||||||
$response = ['success' => true, 'message' => 'Alarm status updated.'];
|
} catch (PDOException $e) {
|
||||||
} else {
|
$response['error'] = 'Database error: ' . $e->getMessage();
|
||||||
$response['message'] = 'Alarm not found or status unchanged.';
|
|
||||||
}
|
|
||||||
} catch (PDOException $e) {
|
|
||||||
$response['message'] = 'Database error: ' . $e->getMessage();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$response['message'] = 'Alarm ID and active status are required.';
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
$response['error'] = 'ID and active status are required.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elseif ($_SERVER['REQUEST_METHOD'] === 'GET' && $action === 'check') {
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare("SELECT * FROM alarms WHERE alarm_time <= CURTIME() AND is_active = 1 FOR UPDATE");
|
||||||
|
$stmt->execute();
|
||||||
|
$alarms = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
if ($alarms) {
|
||||||
|
$alarmIds = array_map(fn($a) => $a['id'], $alarms);
|
||||||
|
$placeholders = implode(',', array_fill(0, count($alarmIds), '?'));
|
||||||
|
$updateStmt = $pdo->prepare("UPDATE alarms SET is_active = 0 WHERE id IN ($placeholders)");
|
||||||
|
$updateStmt->execute($alarmIds);
|
||||||
|
|
||||||
|
$response = ['success' => true, 'alarms' => $alarms];
|
||||||
|
} else {
|
||||||
|
$response = ['success' => true, 'alarms' => []];
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
$response['error'] = 'Database error: ' . $e->getMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
api/fetch_bell_icon.php
Normal file
19
api/fetch_bell_icon.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
require_once __DIR__.'/../includes/pexels.php';
|
||||||
|
$q = 'bell';
|
||||||
|
$orientation = 'square';
|
||||||
|
$url = 'https://api.pexels.com/v1/search?query=' . urlencode($q) . '&orientation=' . urlencode($orientation) . '&per_page=1&page=1';
|
||||||
|
$data = pexels_get($url);
|
||||||
|
if (!$data || empty($data['photos'])) {
|
||||||
|
// Fallback to a generic image if Pexels fails
|
||||||
|
$src = 'https://picsum.photos/200';
|
||||||
|
$target = __DIR__ . '/../assets/images/bell.png';
|
||||||
|
download_to($src, $target);
|
||||||
|
echo json_encode(['success' => true, 'local' => 'assets/images/bell.png']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$photo = $data['photos'][0];
|
||||||
|
$src = $photo['src']['tiny'] ?? ($photo['src']['small'] ?? $photo['src']['original']);
|
||||||
|
$target = __DIR__ . '/../assets/images/bell.png';
|
||||||
|
download_to($src, $target);
|
||||||
|
echo json_encode(['success' => true, 'local' => 'assets/images/bell.png']);
|
||||||
BIN
assets/images/bell.png
Normal file
BIN
assets/images/bell.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@ -1,229 +1,187 @@
|
|||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// --- ELEMENTS ---
|
const alarmForm = document.getElementById('alarm-form');
|
||||||
const createAlarmForm = document.getElementById('createAlarmForm');
|
const alarmsList = document.getElementById('alarms-list');
|
||||||
const alarmList = document.getElementById('alarmList');
|
const alarmModal = new bootstrap.Modal(document.getElementById('alarm-modal'));
|
||||||
const noAlarmsMessage = document.getElementById('noAlarmsMessage');
|
const alarmSound = document.getElementById('alarm-sound');
|
||||||
const alarmModalEl = document.getElementById('alarmModal');
|
const dismissAlarmBtn = document.getElementById('dismiss-alarm');
|
||||||
const alarmModal = new bootstrap.Modal(alarmModalEl);
|
const enableNotificationsBtn = document.getElementById('enable-notifications');
|
||||||
const dismissAlarmBtn = document.getElementById('dismissAlarmBtn');
|
|
||||||
const alarmSound = document.getElementById('alarmSound');
|
|
||||||
const alarmModalMessage = document.getElementById('alarmModalMessage');
|
|
||||||
|
|
||||||
// --- STATE ---
|
let notificationPermission = false;
|
||||||
let isAlarmModalShown = false;
|
|
||||||
|
|
||||||
// --- FUNCTIONS ---
|
// Request notification permission
|
||||||
|
if (enableNotificationsBtn) {
|
||||||
/**
|
enableNotificationsBtn.addEventListener('click', () => {
|
||||||
* Handles the submission of the create alarm form.
|
Notification.requestPermission().then(permission => {
|
||||||
*/
|
if (permission === 'granted') {
|
||||||
const handleCreateAlarm = async (e) => {
|
notificationPermission = true;
|
||||||
e.preventDefault();
|
alert('Notifications enabled!');
|
||||||
const timeInput = document.getElementById('alarmTime');
|
enableNotificationsBtn.style.display = 'none';
|
||||||
const labelInput = document.getElementById('alarmLabel');
|
} else {
|
||||||
|
alert('Notification permission denied.');
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('action', 'create');
|
|
||||||
formData.append('alarm_time', timeInput.value);
|
|
||||||
formData.append('label', labelInput.value);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('api/alarms.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
addAlarmToList(result.id, timeInput.value, labelInput.value);
|
|
||||||
timeInput.value = '';
|
|
||||||
labelInput.value = '';
|
|
||||||
if (noAlarmsMessage) {
|
|
||||||
noAlarmsMessage.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
} else {
|
});
|
||||||
alert('Error: ' + result.message);
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to create alarm:', error);
|
|
||||||
alert('An error occurred while creating the alarm.');
|
// Function to fetch and display alarms
|
||||||
}
|
const fetchAlarms = () => {
|
||||||
|
fetch('/api/alarms.php?action=get')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
alarmsList.innerHTML = '';
|
||||||
|
if (data.success) {
|
||||||
|
data.alarms.forEach(alarm => {
|
||||||
|
addAlarmToList(alarm.id, alarm.alarm_time, alarm.note_id, alarm.is_active);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// Function to add a single alarm to the list
|
||||||
* Handles the click on a delete alarm form.
|
const addAlarmToList = (id, time, noteId, isActive) => {
|
||||||
*/
|
const listItem = document.createElement('li');
|
||||||
const handleDeleteAlarm = async (e) => {
|
listItem.className = 'list-group-item d-flex justify-content-between align-items-center';
|
||||||
if (!e.target.closest('.delete-alarm-form')) return;
|
listItem.dataset.id = id;
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const form = e.target.closest('.delete-alarm-form');
|
const timeText = document.createElement('span');
|
||||||
const alarmId = form.querySelector('input[name="alarm_id"]').value;
|
timeText.textContent = new Date(`1970-01-01T${time}`).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
|
|
||||||
if (!confirm('Are you sure you want to delete this alarm?')) return;
|
const noteLink = document.createElement('a');
|
||||||
|
noteLink.href = `/note.php?id=${noteId}`;
|
||||||
|
noteLink.textContent = `Note #${noteId}`;
|
||||||
|
noteLink.className = 'mx-3';
|
||||||
|
|
||||||
const formData = new FormData(form);
|
const controls = document.createElement('div');
|
||||||
|
|
||||||
try {
|
const toggleSwitch = document.createElement('div');
|
||||||
const response = await fetch('api/alarms.php', {
|
toggleSwitch.className = 'form-check form-switch';
|
||||||
|
const toggleInput = document.createElement('input');
|
||||||
|
toggleInput.className = 'form-check-input';
|
||||||
|
toggleInput.type = 'checkbox';
|
||||||
|
toggleInput.role = 'switch';
|
||||||
|
toggleInput.checked = isActive;
|
||||||
|
toggleInput.addEventListener('change', () => handleToggleAlarm(id, toggleInput.checked));
|
||||||
|
toggleSwitch.appendChild(toggleInput);
|
||||||
|
|
||||||
|
const deleteBtn = document.createElement('button');
|
||||||
|
deleteBtn.className = 'btn btn-danger btn-sm ms-3';
|
||||||
|
deleteBtn.textContent = 'Delete';
|
||||||
|
deleteBtn.addEventListener('click', () => handleDeleteAlarm(id));
|
||||||
|
|
||||||
|
controls.appendChild(toggleSwitch);
|
||||||
|
controls.appendChild(deleteBtn);
|
||||||
|
|
||||||
|
listItem.appendChild(timeText);
|
||||||
|
listItem.appendChild(noteLink);
|
||||||
|
listItem.appendChild(controls);
|
||||||
|
alarmsList.appendChild(listItem);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle form submission to create a new alarm
|
||||||
|
if (alarmForm) {
|
||||||
|
alarmForm.addEventListener('submit', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(alarmForm);
|
||||||
|
fetch('/api/alarms.php?action=create', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData
|
body: formData
|
||||||
});
|
})
|
||||||
const result = await response.json();
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
addAlarmToList(data.id, data.alarm_time, data.note_id, true);
|
||||||
|
alarmForm.reset();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (result.success) {
|
// Handle deleting an alarm
|
||||||
const listItem = form.closest('li');
|
const handleDeleteAlarm = (id) => {
|
||||||
listItem.remove();
|
if (!confirm('Are you sure you want to delete this alarm?')) return;
|
||||||
if (!alarmList.querySelector('li')) {
|
fetch(`/api/alarms.php?action=delete&id=${id}`, { method: 'GET' })
|
||||||
if (noAlarmsMessage) {
|
.then(response => response.json())
|
||||||
noAlarmsMessage.style.display = 'block';
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
document.querySelector(`li[data-id='${id}']`).remove();
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + data.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle toggling an alarm's active state
|
||||||
|
const handleToggleAlarm = (id, isActive) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('id', id);
|
||||||
|
formData.append('is_active', isActive ? 1 : 0);
|
||||||
|
|
||||||
|
fetch('/api/alarms.php?action=toggle', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
}).then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (!data.success) {
|
||||||
|
alert('Error updating alarm status.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Function to check for due alarms
|
||||||
|
const checkAlarms = () => {
|
||||||
|
fetch('/api/alarms.php?action=check')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success && data.alarms.length > 0) {
|
||||||
|
const alarm = data.alarms[0]; // Assuming one alarm for now
|
||||||
|
showNotification(alarm);
|
||||||
|
// Also update the toggle on the main page
|
||||||
|
const alarmToggle = document.querySelector(`li[data-id='${alarm.id}'] input[type='checkbox']`);
|
||||||
|
if (alarmToggle) {
|
||||||
|
alarmToggle.checked = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
alert('Error: ' + result.message);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to delete alarm:', error);
|
|
||||||
alert('An error occurred while deleting the alarm.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new alarm item to the DOM.
|
|
||||||
*/
|
|
||||||
const addAlarmToList = (id, time, label, isActive = true) => {
|
|
||||||
const date = new Date(`1970-01-01T${time}`);
|
|
||||||
const formattedTime = date.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true });
|
|
||||||
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.className = 'list-group-item d-flex justify-content-between align-items-center';
|
|
||||||
li.dataset.id = id;
|
|
||||||
li.innerHTML = `
|
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="form-check form-switch me-3">
|
|
||||||
<input class="form-check-input toggle-alarm-switch" type="checkbox" role="switch" id="toggle-${id}" ${isActive ? 'checked' : ''}>
|
|
||||||
<label class="form-check-label" for="toggle-${id}"></label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="fw-bold fs-5">${formattedTime}</span>
|
|
||||||
<span class="text-muted ms-2">${escapeHTML(label)}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<form class="delete-alarm-form">
|
|
||||||
<input type="hidden" name="action" value="delete">
|
|
||||||
<input type="hidden" name="alarm_id" value="${id}">
|
|
||||||
<button type="submit" class="btn btn-sm btn-outline-danger">
|
|
||||||
<i data-feather="trash-2" class="align-text-bottom"></i>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
alarmList.appendChild(li);
|
|
||||||
feather.replace();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles toggling the active state of an alarm.
|
|
||||||
*/
|
|
||||||
const handleToggleAlarm = async (e) => {
|
|
||||||
if (!e.target.classList.contains('toggle-alarm-switch')) return;
|
|
||||||
|
|
||||||
const switchEl = e.target;
|
|
||||||
const listItem = switchEl.closest('li');
|
|
||||||
const alarmId = listItem.dataset.id;
|
|
||||||
const isActive = switchEl.checked ? 1 : 0;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('action', 'toggle');
|
|
||||||
formData.append('alarm_id', alarmId);
|
|
||||||
formData.append('is_active', isActive);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('api/alarms.php', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
});
|
});
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (!result.success) {
|
|
||||||
alert('Error: ' + result.message);
|
|
||||||
// Revert the switch on failure
|
|
||||||
switchEl.checked = !switchEl.checked;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to toggle alarm:', error);
|
|
||||||
alert('An error occurred while updating the alarm.');
|
|
||||||
// Revert the switch on failure
|
|
||||||
switchEl.checked = !switchEl.checked;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
// Function to show notification
|
||||||
* Checks the server for any due alarms.
|
const showNotification = (alarm) => {
|
||||||
*/
|
const alarmTime = new Date(`1970-01-01T${alarm.alarm_time}`).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||||
const checkAlarms = async () => {
|
const notificationTitle = `Alarm! It's ${alarmTime}`;
|
||||||
if (isAlarmModalShown) return; // Don't check if an alarm is already ringing
|
const notificationBody = `Click to open your note.`;
|
||||||
|
|
||||||
try {
|
if (notificationPermission) {
|
||||||
const response = await fetch('api/alarms.php?action=check');
|
const notification = new Notification(notificationTitle, {
|
||||||
const result = await response.json();
|
body: notificationBody,
|
||||||
|
icon: '/assets/images/bell.png', // Optional: add an icon
|
||||||
|
sound: '/assets/sounds/alarm.mp3', // This doesn't work, sound is handled separately
|
||||||
|
requireInteraction: true // Keeps notification open until user interacts
|
||||||
|
});
|
||||||
|
|
||||||
if (result.success && result.alarms.length > 0) {
|
notification.onclick = () => {
|
||||||
const alarm = result.alarms[0];
|
window.open(`/note.php?id=${alarm.note_id}`, '_blank');
|
||||||
triggerAlarm(alarm);
|
notification.close();
|
||||||
}
|
};
|
||||||
} catch (error) {
|
|
||||||
console.error('Error checking alarms:', error);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
// Fallback to modal
|
||||||
* Triggers the visual and audible alarm.
|
alarmSound.play().catch(e => console.error("Audio playback failed:", e));
|
||||||
*/
|
|
||||||
const triggerAlarm = (alarm) => {
|
|
||||||
isAlarmModalShown = true;
|
|
||||||
if (alarm.label) {
|
|
||||||
alarmModalMessage.textContent = alarm.label;
|
|
||||||
} else {
|
|
||||||
alarmModalMessage.textContent = 'Time to write your notes.';
|
|
||||||
}
|
|
||||||
alarmModal.show();
|
alarmModal.show();
|
||||||
alarmSound.play().catch(e => console.error("Audio play failed:", e));
|
|
||||||
|
dismissAlarmBtn.onclick = () => {
|
||||||
|
alarmSound.pause();
|
||||||
|
alarmSound.currentTime = 0;
|
||||||
|
alarmModal.hide();
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Dismisses the alarm and redirects to the note page.
|
|
||||||
*/
|
|
||||||
const dismissAlarm = () => {
|
|
||||||
alarmSound.pause();
|
|
||||||
alarmSound.currentTime = 0;
|
|
||||||
alarmModal.hide();
|
|
||||||
isAlarmModalShown = false;
|
|
||||||
|
|
||||||
const today = new Date();
|
// Initial fetch and periodic check
|
||||||
const dateString = today.getFullYear() + '-' + String(today.getMonth() + 1).padStart(2, '0') + '-' + String(today.getDate()).padStart(2, '0');
|
fetchAlarms();
|
||||||
window.location.href = `note.php?date=${dateString}`;
|
setInterval(checkAlarms, 5000); // Check every 5 seconds
|
||||||
};
|
|
||||||
|
|
||||||
const escapeHTML = (str) => {
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.appendChild(document.createTextNode(str));
|
|
||||||
return p.innerHTML;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- EVENT LISTENERS ---
|
|
||||||
if (createAlarmForm) {
|
|
||||||
createAlarmForm.addEventListener('submit', handleCreateAlarm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alarmList) {
|
|
||||||
alarmList.addEventListener('click', handleDeleteAlarm);
|
|
||||||
alarmList.addEventListener('change', handleToggleAlarm);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dismissAlarmBtn) {
|
|
||||||
dismissAlarmBtn.addEventListener('click', dismissAlarm);
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- INITIALIZATION ---
|
|
||||||
setInterval(checkAlarms, 5000); // Check for alarms every 5 seconds
|
|
||||||
});
|
});
|
||||||
25
includes/pexels.php
Normal file
25
includes/pexels.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
function pexels_key() {
|
||||||
|
$k = getenv('PEXELS_KEY');
|
||||||
|
return $k && strlen($k) > 0 ? $k : 'Vc99rnmOhHhJAbgGQoKLZtsaIVfkeownoQNbTj78VemUjKh08ZYRbf18';
|
||||||
|
}
|
||||||
|
function pexels_get($url) {
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_URL => $url,
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_HTTPHEADER => [ 'Authorization: '. pexels_key() ],
|
||||||
|
CURLOPT_TIMEOUT => 15,
|
||||||
|
]);
|
||||||
|
$resp = curl_exec($ch);
|
||||||
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
if ($code >= 200 && $code < 300 && $resp) return json_decode($resp, true);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
function download_to($srcUrl, $destPath) {
|
||||||
|
$data = file_get_contents($srcUrl);
|
||||||
|
if ($data === false) return false;
|
||||||
|
if (!is_dir(dirname($destPath))) mkdir(dirname($destPath), 0775, true);
|
||||||
|
return file_put_contents($destPath, $data) !== false;
|
||||||
|
}
|
||||||
@ -107,7 +107,8 @@ try {
|
|||||||
<div class="modal-content text-center">
|
<div class="modal-content text-center">
|
||||||
<div class="modal-body p-5">
|
<div class="modal-body p-5">
|
||||||
<i data-feather="bell" class="feather-lg text-warning mb-3"></i>
|
<i data-feather="bell" class="feather-lg text-warning mb-3"></i>
|
||||||
<h1 class="modal-title fs-2" id="alarmModalLabel">Alarm!</h1>
|
<h1 class="my-4">Alarm Clock</h1>
|
||||||
|
<button id="enable-notifications" class="btn btn-primary mb-4">Enable Notifications</button>
|
||||||
<p id="alarmModalMessage" class="lead">Time to write your notes.</p>
|
<p id="alarmModalMessage" class="lead">Time to write your notes.</p>
|
||||||
<button type="button" class="btn btn-primary btn-lg mt-3" id="dismissAlarmBtn">Dismiss</button>
|
<button type="button" class="btn btn-primary btn-lg mt-3" id="dismissAlarmBtn">Dismiss</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user