Basic support for loading schedules, creating and loading backups, changing title, and adding/removing people. Cannot edit events.
parent
72a63f8356
commit
d95ed2cb14
@ -0,0 +1 @@
|
||||
IDB-Keyval from https://www.npmjs.com/package/idb-keyval Apache-2.0 licensed.
|
@ -1,4 +1,275 @@
|
||||
import { get as idb_get,
|
||||
set as idb_set,
|
||||
del as idb_del } from './idb-keyval.js';
|
||||
import Schedule from './schedule.js';
|
||||
import * as utils from './utils.js';
|
||||
|
||||
// XXX For debugging:
|
||||
window.Schedule = Schedule;
|
||||
|
||||
const openDirButton = document.getElementById('openDir');
|
||||
const reopenDirButton = document.getElementById('reopenDir');
|
||||
const closeDirButton = document.getElementById('closeDir');
|
||||
const schedulesDiv = document.getElementById('allSchedules');
|
||||
const scheduleSettingsDiv = document.getElementById('scheduleSettings');
|
||||
|
||||
let dirHandle = null;
|
||||
let backupsDir = null;
|
||||
let fileHandle = null;
|
||||
let basefileHandle = null;
|
||||
let schedule = null;
|
||||
|
||||
function closeDir() {
|
||||
dirHandle = null;
|
||||
idb_del('dirHandle');
|
||||
openDirButton.style.display = '';
|
||||
closeDirButton.style.display = 'none';
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
async function saveSchedule() {
|
||||
if (fileHandle !== basefileHandle) {
|
||||
alert("Cannot modify backups. This should not be reachable.");
|
||||
return;
|
||||
}
|
||||
|
||||
schedule.lastModified = new Date();
|
||||
const writable = await basefileHandle.createWritable();
|
||||
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
|
||||
await writable.close();
|
||||
|
||||
refreshDisplay();
|
||||
}
|
||||
|
||||
async function updateBackupsList() {
|
||||
backupsDir = await dirHandle.getDirectoryHandle(
|
||||
basefileHandle.name.slice(0, -5), {create: true});
|
||||
|
||||
const backupsList = document.getElementById('backups');
|
||||
utils.clearChildren(backupsList);
|
||||
const options = [];
|
||||
|
||||
for await (const entry of backupsDir.values()) {
|
||||
if (entry.kind === 'file' && entry.name.endsWith('.json')) {
|
||||
const fileObj = await entry.getFile()
|
||||
const timestamp = fileObj.lastModified;
|
||||
const option = document.createElement('option');
|
||||
option.innerText = entry.name.slice(0, -5) + " ["
|
||||
+ new Date(timestamp) + "]";
|
||||
option.timestamp = timestamp;
|
||||
option.file = entry;
|
||||
option.isBackup = true;
|
||||
if (entry.name === fileHandle.name) option.selected = true;
|
||||
options.push(option);
|
||||
}
|
||||
}
|
||||
|
||||
options.sort((a, b) => b.timestamp - a.timestamp);
|
||||
|
||||
const current = document.createElement('option');
|
||||
const currentTimestamp = (await basefileHandle.getFile()).lastModified;
|
||||
current.innerText = "(latest) [" + new Date(currentTimestamp) + "]";
|
||||
current.timestamp = currentTimestamp;
|
||||
current.file = basefileHandle;
|
||||
current.isBackup = false;
|
||||
options.unshift(current);
|
||||
|
||||
options.forEach(opt => backupsList.appendChild(opt));
|
||||
}
|
||||
|
||||
document.getElementById('createBackup').addEventListener('click', async e => {
|
||||
let name = document.getElementById('backupName').value;
|
||||
if (name.length === 0) {
|
||||
name = new Date().toISOString().replaceAll(':', '-');
|
||||
}
|
||||
|
||||
const file = await backupsDir.getFileHandle(name + '.json', {create: true});
|
||||
if ((await file.getFile()).size !== 0) {
|
||||
alert("Failed to create backup. Backup \"" + name + "\" already exists.");
|
||||
return;
|
||||
}
|
||||
|
||||
const writable = await file.createWritable();
|
||||
await writable.write(JSON.stringify(schedule.asJsonObject(), null, 2));
|
||||
await writable.close();
|
||||
|
||||
updateBackupsList();
|
||||
});
|
||||
|
||||
const titleInput = document.getElementById('title');
|
||||
let allowEdits = false;
|
||||
async function updateScheduleSettings() {
|
||||
updateBackupsList();
|
||||
titleInput.value = schedule.base_title;
|
||||
titleInput.disabled = !allowEdits;
|
||||
|
||||
document.getElementById('scheduleMetadata')
|
||||
.querySelectorAll('*')
|
||||
.forEach(el => el.disabled = !allowEdits);
|
||||
|
||||
schedule.recomputeNotAssigned();
|
||||
|
||||
const staffList = document.getElementById('removeStaff');
|
||||
utils.clearChildren(staffList);
|
||||
schedule.notAssignedStaff.forEach(name => {
|
||||
const option = document.createElement('option');
|
||||
option.value = name;
|
||||
option.innerText = name;
|
||||
staffList.appendChild(option);
|
||||
});
|
||||
const canDelStaff = allowEdits && schedule.notAssignedStaff.size > 0;
|
||||
staffList.disabled = !canDelStaff;
|
||||
document.getElementById('delStaff').disabled = !canDelStaff;
|
||||
|
||||
const studentList = document.getElementById('removeStudent');
|
||||
utils.clearChildren(studentList);
|
||||
schedule.notAssignedStudents.forEach(name => {
|
||||
const option = document.createElement('option');
|
||||
option.value = name;
|
||||
option.innerText = name;
|
||||
studentList.appendChild(option);
|
||||
});
|
||||
const canDelStudent = allowEdits && schedule.notAssignedStudents.size > 0;
|
||||
studentList.disabled = !canDelStudent;
|
||||
document.getElementById('delStudent').disabled = !canDelStudent;
|
||||
|
||||
scheduleSettingsDiv.style.display = '';
|
||||
}
|
||||
|
||||
async function loadFile(file, isBackup) {
|
||||
fileHandle = file;
|
||||
if (!isBackup) basefileHandle = fileHandle;
|
||||
allowEdits = !isBackup;
|
||||
const fileObj = await file.getFile();
|
||||
schedule = new Schedule(JSON.parse(await fileObj.text()));
|
||||
schedule.lastModified = fileObj.lastModified;
|
||||
updateScheduleSettings();
|
||||
refreshDisplay();
|
||||
}
|
||||
|
||||
document.getElementById('schedules').addEventListener('change', async e => {
|
||||
loadFile(e.target.selectedOptions[0].file, false);
|
||||
});
|
||||
|
||||
document.getElementById('backups').addEventListener('change', async e => {
|
||||
const opt = e.target.selectedOptions[0];
|
||||
loadFile(opt.file, opt.isBackup);
|
||||
});
|
||||
|
||||
async function loadDir(dir) {
|
||||
dirHandle = dir;
|
||||
openDirButton.style.display = 'none';
|
||||
reopenDirButton.style.display = 'none';
|
||||
closeDirButton.style.display = '';
|
||||
|
||||
const schedulesList = document.getElementById('schedules');
|
||||
utils.clearChildren(schedulesList);
|
||||
const options = [];
|
||||
|
||||
let newest = undefined;
|
||||
let newestTimestamp = undefined;
|
||||
for await (const entry of dir.values()) {
|
||||
if (entry.kind === 'file' && entry.name.endsWith('.json')) {
|
||||
const fileObj = await entry.getFile()
|
||||
const timestamp = fileObj.lastModified;
|
||||
if (!newestTimestamp || timestamp > newestTimestamp) {
|
||||
newest = entry;
|
||||
newestTimestamp = timestamp;
|
||||
}
|
||||
const option = document.createElement('option');
|
||||
const title = JSON.parse(await fileObj.text()).title;
|
||||
option.innerText = title + " (" + entry.name.slice(0, -5) + ") ["
|
||||
+ new Date(timestamp) + "]";
|
||||
option.timestamp = timestamp;
|
||||
option.file = entry;
|
||||
options.push(option);
|
||||
}
|
||||
}
|
||||
|
||||
options.sort((a, b) => b.timestamp - a.timestamp).forEach(opt =>
|
||||
schedulesList.appendChild(opt));
|
||||
|
||||
await loadFile(newest, false);
|
||||
}
|
||||
|
||||
if (window.showDirectoryPicker) {
|
||||
document.getElementById('browserVersionWarning').style.display = 'none';
|
||||
|
||||
idb_get('dirHandle').then(async dir => {
|
||||
if (dir) {
|
||||
if (await dir.queryPermission({mode: 'readwrite'}) === 'granted') {
|
||||
await loadDir(dir);
|
||||
} else {
|
||||
reopenDirButton.style.display = '';
|
||||
reopenDirButton.addEventListener('click', async e => {
|
||||
if (await dir.requestPermission({mode: 'readwrite'}) === 'granted') {
|
||||
await loadDir(dir);
|
||||
}
|
||||
reopenDirButton.style.display = 'none';
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
closeDirButton.addEventListener('click', e => closeDir());
|
||||
|
||||
openDirButton.style.display = '';
|
||||
openDirButton.addEventListener('click', async e => {
|
||||
const dir = await window.showDirectoryPicker({mode: 'readwrite'});
|
||||
if (dir) {
|
||||
await idb_set('dirHandle', dir);
|
||||
await loadDir(dir);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function displayFullScheduleAllDays() {
|
||||
utils.clearChildren(schedulesDiv);
|
||||
schedule.all_days
|
||||
.map(day => schedule.fullGridFor(day))
|
||||
.forEach(grid => {
|
||||
const title = document.createElement('h1');
|
||||
title.innerText = grid.title;
|
||||
schedulesDiv.appendChild(title);
|
||||
schedulesDiv.appendChild(grid.toHtml());
|
||||
})
|
||||
}
|
||||
|
||||
function refreshDisplay() {
|
||||
// TODO support different displays.
|
||||
updateScheduleSettings();
|
||||
displayFullScheduleAllDays();
|
||||
}
|
||||
|
||||
document.getElementById('changeTitle').addEventListener('click', async e => {
|
||||
schedule.base_title = document.getElementById('title').value;
|
||||
saveSchedule();
|
||||
});
|
||||
|
||||
const personInput = document.getElementById('person');
|
||||
|
||||
document.getElementById('addStudent').addEventListener('click', async e => {
|
||||
schedule.addStudent(personInput.value);
|
||||
personInput.value = '';
|
||||
saveSchedule();
|
||||
});
|
||||
|
||||
document.getElementById('addStaff').addEventListener('click', async e => {
|
||||
schedule.addStaff(personInput.value);
|
||||
personInput.value = '';
|
||||
saveSchedule();
|
||||
});
|
||||
|
||||
document.getElementById('addTeacher').addEventListener('click', async e => {
|
||||
schedule.addTeacher(personInput.value);
|
||||
personInput.value = '';
|
||||
saveSchedule();
|
||||
});
|
||||
|
||||
document.getElementById('delStudent').addEventListener('click', async e => {
|
||||
schedule.delStudent(document.getElementById('removeStudent').value);
|
||||
saveSchedule();
|
||||
});
|
||||
|
||||
document.getElementById('delStaff').addEventListener('click', async e => {
|
||||
schedule.delStaff(document.getElementById('removeStaff').value);
|
||||
saveSchedule();
|
||||
});
|
||||
|
@ -0,0 +1 @@
|
||||
../external/idb-keyval/index.js
|
Loading…
Reference in New Issue