const scheduleDOM = document.getElementById('schedule'); const VERSION = "v1.0.0"; const days = [ 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', ]; let schedule = { version: VERSION, days: {}, }; const offlineTitle = 'OFFLINE'; const offlineSubtitle = 'takin a nap'; let copyClipboard; let saveImage; document.addEventListener('readystatechange', () => { document.getElementById('version').innerText = VERSION; loadSchedule(); createSchedule(); document.getElementById('import').addEventListener('click', () => { importJSON(); }); document.getElementById('save').addEventListener('click', () => { saveSchedule(); }); document.getElementById('alt-text').addEventListener('click', () => { altText(); }); document.getElementById('clipboard').addEventListener('click', () => { copyClipboard(); }); document.getElementById('save-image').addEventListener('click', () => { saveImage(); }); document.getElementById('export').addEventListener('click', () => { exportJSON(); }); }); function importJSON() { const filePicker = document.createElement('input'); filePicker.type = 'file'; filePicker.accept = 'application/json'; filePicker.addEventListener('input', () => { if (filePicker.files.length == 0) return; const file = filePicker.files[0]; file.text().then(text => { try { schedule = JSON.parse(text); for (const day in schedule.days) { const data = schedule.days[day]; const slot = document.getElementById(`slot-${day}`); if (!data.enabled) { slot.classList.add('disabled'); slot.querySelector('.timeslot-title').innerText = offlineTitle slot.querySelector('.timeslot-subtitle').innerText = offlineSubtitle continue; } slot.classList.remove('disabled'); slot.querySelector('.timeslot-time').innerText = data.time slot.querySelector('.timeslot-title').innerText = data.title slot.querySelector('.timeslot-subtitle').innerText = data.subtitle } } catch (err) { console.error(err); alert('Failed to import schedule. Check the browser console for details.'); } }).catch(err => { console.error(err); alert('Failed to import schedule. Check the browser console for details.'); }); }); filePicker.click(); } function saveSchedule() { localStorage.setItem('streamschedule', btoa(JSON.stringify(schedule))); } function loadSchedule() { const saved = localStorage.getItem('streamschedule'); if (!saved) return; schedule = JSON.parse(atob(saved)); } function altText() { let altText = "ari melody LIVE schedule: "; const activeDays = Object.keys(schedule.days) .filter(day => schedule.days[day].enabled) .reduce((days, day) => (days[day] = schedule.days[day], days), {}); const nDays = Object.keys(activeDays).length; for (let i = 0; i < nDays; i++) { const day = Object.keys(activeDays)[i]; const slot = schedule.days[day]; if (!slot.enabled) continue; if (i == nDays - 1) altText += `and `; altText += slot.title; if (slot.subtitle.length > 0) altText += ` (${slot.subtitle})`; altText += ` on ${day} @ ${slot.time}`; if (i < nDays - 1) altText += `, `; } alert(altText); } if ('webkit' in window && 'messageHandlers' in window.webkit) { let _promiseId = 0; let _promises = new Map(); window.addEventListener('message', (event) => { const [id] = event.detail; const [resolve,] = _promises.get(id) ?? []; if (!resolve) return; _promises.delete(id); resolve(); }); window.addEventListener('message', (event) => { const [id] = event.detail; const [,reject] = _promises.get(id) ?? []; if (!reject) return; _promises.delete(id); reject(); }); copyClipboard = () => new Promise((resolve, reject) => { const thisPromiseId = ++_promiseId; _promises.set(thisPromiseId, [resolve, reject]); const schedule = document.getElementById('schedule'); const boundingRect = schedule.getBoundingClientRect(); window.webkit.messageHandlers.copySnapshot.postMessage([ thisPromiseId, 'clipboard', boundingRect.x + 1, boundingRect.y + 1, boundingRect.width - 2, boundingRect.height - 2, ]); }); saveImage = () => new Promise((resolve, reject) => { const thisPromiseId = ++_promiseId; _promises.set(thisPromiseId, [resolve, reject]); const schedule = document.getElementById('schedule'); const boundingRect = schedule.getBoundingClientRect(); window.webkit.messageHandlers.copySnapshot.postMessage([ thisPromiseId, 'file', boundingRect.x + 1, boundingRect.y + 1, boundingRect.width - 2, boundingRect.height - 2, ]); }); document.getElementById('clipboard').removeAttribute('disabled'); document.getElementById('save-image').removeAttribute('disabled'); } function exportJSON() { const json = JSON.stringify(schedule, null, '\t'); const data = new Blob([json], { type: 'application/json', }); const link = document.createElement('a'); link.href = URL.createObjectURL(data); link.download = 'stream-schedule.json'; link.click(); } function createSchedule() { for (let i = 0; i < days.length; i++) { const day = days[i]; if (schedule.days[day] == undefined) schedule.days[day] = { enabled: false, time: '6pm UTC', title: 'Untitled Event', subtitle: "yep, that sure is an event if i've ever seen one", }; const container = document.createElement('div'); container.className = 'timeslot'; container.id = `slot-${day}`; if (!schedule.days[day].enabled) container.classList.add('disabled'); const timeslotDateContainer = document.createElement('div'); timeslotDateContainer.className = 'timeslot-date'; const timeslotDay = document.createElement('div'); timeslotDay.className = 'timeslot-day'; timeslotDay.innerText = day.substring(0, 3).toUpperCase(); timeslotDateContainer.appendChild(timeslotDay); const timeslotTime = document.createElement('div'); timeslotTime.className = 'timeslot-time'; timeslotTime.innerText = schedule.days[day].time; timeslotTime.contentEditable = true; timeslotDateContainer.appendChild(timeslotTime); container.appendChild(timeslotDateContainer); const timeslotContentContainer = document.createElement('div'); timeslotContentContainer.className = 'timeslot-content'; const timeslotTitle = document.createElement('div'); timeslotTitle.className = 'timeslot-title'; timeslotTitle.innerText = schedule.days[day].enabled ? schedule.days[day].title : offlineTitle; timeslotTitle.contentEditable = schedule.days[day].enabled; timeslotTitle.spellcheck = false; timeslotContentContainer.appendChild(timeslotTitle); const timeslotSubtitle = document.createElement('div'); timeslotSubtitle.className = 'timeslot-subtitle'; timeslotSubtitle.innerText = schedule.days[day].enabled ? schedule.days[day].subtitle : offlineSubtitle; timeslotSubtitle.contentEditable = schedule.days[day].enabled; timeslotSubtitle.spellcheck = false; timeslotContentContainer.appendChild(timeslotSubtitle); container.appendChild(timeslotContentContainer); scheduleDOM.appendChild(container); timeslotDay.addEventListener('click', () => { schedule.days[day].enabled = !schedule.days[day].enabled; timeslotTitle.contentEditable = schedule.days[day].enabled; timeslotSubtitle.contentEditable = schedule.days[day].enabled; if (schedule.days[day].enabled) { container.classList.remove('disabled'); timeslotTitle.innerText = schedule.days[day].title; timeslotSubtitle.innerText = schedule.days[day].subtitle; } else { container.classList.add('disabled'); timeslotTitle.innerText = offlineTitle; timeslotSubtitle.innerText = offlineSubtitle; } }); timeslotTitle.addEventListener('keyup', () => { schedule.days[day].title = timeslotTitle.innerText; }); timeslotSubtitle.addEventListener('keyup', () => { if (timeslotSubtitle.innerHTML.includes('
')) timeslotSubtitle.innerText = ''; schedule.days[day].subtitle = timeslotSubtitle.innerText; }); timeslotTime.addEventListener('keyup', () => { schedule.days[day].time = timeslotTime.innerText; }); } }