From 308baf0b167c4fb38a7cbcd46238f1c54b3cdbe0 Mon Sep 17 00:00:00 2001 From: Thayol Date: Sat, 2 Dec 2023 13:27:37 +0100 Subject: [PATCH] refactor to ES modules --- index.html | 2 +- js/championship.js | 44 +++++++++++++++ js/data.js | 21 +++++++ js/day.js | 26 +++++++++ js/sso-timer.js | 3 + js/transform.js | 21 +++++++ js/ui.js | 90 +++++++++++++++++++++++++++++ script.js | 138 --------------------------------------------- 8 files changed, 206 insertions(+), 139 deletions(-) create mode 100644 js/championship.js create mode 100644 js/data.js create mode 100644 js/day.js create mode 100644 js/sso-timer.js create mode 100644 js/transform.js create mode 100644 js/ui.js delete mode 100644 script.js diff --git a/index.html b/index.html index e82fbaf..e7c691c 100755 --- a/index.html +++ b/index.html @@ -22,6 +22,6 @@
- + diff --git a/js/championship.js b/js/championship.js new file mode 100644 index 0000000..c923b05 --- /dev/null +++ b/js/championship.js @@ -0,0 +1,44 @@ +import Data from './data.js' +import Day from './day.js' +import Transform from './transform.js' + +export default class Championship { + static last = null + + static get next() { + let remainingOffset = 0 + let day = Day.today + let time = Championship.nextTime + + if (time == null) { + remainingOffset += 1440 + day = Day.tomorrow + time = Championship.firstTimeNextDay + } + + return { + day: day, + time: time, + location: Data.championships[day][time], + remaining: (remainingOffset + time) - Day.minutesPassedSinceMidnight, + } + } + + static firstTimeOnDay(day) { + return Math.min(...Object.keys(Data.championships[day])) + } + + static get nextTime() { + let nextTimes = Transform.dropChampionshipsBefore(Data.championshipsToday, Day.minutesPassedSinceMidnight) + + if (nextTimes.length < 1) { + return null + } + + return Math.min(...nextTimes) + } + + static get firstTimeTomorrow() { + return Championship.firstTimeOnDay(Day.tomorrow) + } +} diff --git a/js/data.js b/js/data.js new file mode 100644 index 0000000..f691f44 --- /dev/null +++ b/js/data.js @@ -0,0 +1,21 @@ +import Transform from './transform.js' +import Day from './day.js' + +export default class Data { + static championships = {} + static championshipsURL = 'data/championships.json' + static locations = [] + + static get championshipsToday() { + return Data.championships[Day.today] + } + + static get isLoaded() { + return Object.keys(Data.championships).length > 0 + } + + static init(json) { + Data.championships = json + Data.locations = Transform.championshipsToLocations(json) + } +} diff --git a/js/day.js b/js/day.js new file mode 100644 index 0000000..58c6dd3 --- /dev/null +++ b/js/day.js @@ -0,0 +1,26 @@ +export default class Day { + static date = new Date() // maybe easier to offset time zone later + + static update() { + Day.date = new Date() + } + + static get today() { + return Day.date.getDay() + } + + static get tomorrow() { + let day = Day.today + 1 + if (day > 6) day -= 6 + + return day + } + + static get secondsUntilFullMinute() { + return (60 - Day.date.getSeconds()).toString().padStart(2, '0') + } + + static get minutesPassedSinceMidnight() { + return Day.date.getHours() * 60 + Day.date.getMinutes() + } +} diff --git a/js/sso-timer.js b/js/sso-timer.js new file mode 100644 index 0000000..ed32b2e --- /dev/null +++ b/js/sso-timer.js @@ -0,0 +1,3 @@ +import UI from './ui.js' + +window.addEventListener('load', UI.init) diff --git a/js/transform.js b/js/transform.js new file mode 100644 index 0000000..0d61341 --- /dev/null +++ b/js/transform.js @@ -0,0 +1,21 @@ +export default class Transform { + static championshipsToLocations(championships) { + return Transform.unique(Transform.flatten(championships)) + } + + static flatten(object) { + return Object.values(object).reduce((arr, t) => arr.concat(Object.values(t)), []) + } + + static unique(array) { + return [...new Set(array)] + } + + static dropChampionshipsBefore(championshipsToday, time) { + return Object.keys(championshipsToday).filter((t) => parseInt(t) >= time) + } + + static time(time) { + return Math.floor(time/60).toString().padStart(2, '0') + ":" + (time % 60).toString().padStart(2, '0') + } +} diff --git a/js/ui.js b/js/ui.js new file mode 100644 index 0000000..5a9885a --- /dev/null +++ b/js/ui.js @@ -0,0 +1,90 @@ +import Transform from './transform.js' +import Championship from './championship.js' +import Data from './data.js' +import Day from './day.js' + +export default class UI { + static instance = null + static updateFrequency = 500 // in ms + + constructor(rootObject) { + this.rootObject = rootObject + this.loadChampionships() + this.start() // auto-start, might need some more thought + } + + static init() { + UI.instance = new UI(window.document) + } + + loadResponseIntoData(response) { + response.json().then(Data.init) + } + + loadChampionships() { + fetch(Data.championshipsURL).then(this.loadResponseIntoData) + } + + start() { + this.intervalId = setInterval(() => { UI.instance.update() }, UI.updateFrequency) + } + + stop() { + clearInterval(this.intervalId) + } + + update() { + if (!Data.isLoaded) { return } + + Day.update() + + let next = Championship.next + + if (Championship.last != next) { + Championship.last = next + this.updateChampionshipDetails(next) + this.updateChampionshipTable(next) + } + + this.updateTimer(next) + } + + updateChampionshipDetails(next) { + this.rootObject.getElementById('time').innerHTML = Transform.time(next.time) + this.rootObject.getElementById('location').innerHTML = next.location + } + + updateTimer(next) { + let remaining = Transform.time(next.remaining) + ":" + Day.secondsUntilFullMinute + this.rootObject.getElementById('timer').innerHTML = remaining + this.rootObject.title = `${remaining} - ${next.location} - SSO Timer` + } + + // TODO: refactor to builder + updateChampionshipTable(next) { + const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] + let table = `` + for (const [day, details] of Object.entries(Data.championships)) { + let dayContainer = `

${weekdays[day]}

` + let dayContainerClasses = 'day-container' + + if (Day.today == day) { + dayContainerClasses += ' today' + } + + for (const [time, location] of Object.entries(details)) { + let formattedTime = Transform.time(time) + let classes = 'time-container' + if (next.day == day && next.time == time) { + classes += ' next-time' + } + + dayContainer += `
  • ${formattedTime}: ${location}
  • ` + } + + table += `
    ` + } + + document.getElementById('championships_table').innerHTML = table + } +} diff --git a/script.js b/script.js deleted file mode 100644 index 8ffc2c7..0000000 --- a/script.js +++ /dev/null @@ -1,138 +0,0 @@ -var championships = {} -var lastChampionship = null -var locations = [] -var updateFrequency = 500; // in ms - -function flattenObjectIntoSingleUniqueArray(object) { - return [...new Set(Object.values(object).reduce((arr, t) => arr.concat(Object.values(t)), []))] -} - -function getDateObject() { - return new Date() // maybe easier to offset time zone later -} - -function currentDay() { - return getDateObject().getDay() -} - -function currentTime() { - let date = getDateObject() - return date.getHours() * 60 + date.getMinutes() -} - -function nextDay() { - let day = currentDay() + 1 - if (day > 6) day -= 6 - - return day -} - -function firstTimeOnDay(day) { - return Math.min(...Object.keys(championships[day])) -} - -function firstTimeNextDay() { - return firstTimeOnDay(nextDay()) -} - -function nextTime() { - let time = currentTime() - - let nextTimes = Object.keys(championships[currentDay()]).filter((t) => parseInt(t) >= time) - - if (nextTimes.length < 1) { - return null - } - - return Math.min(...nextTimes) -} - -function formatTime(time) { - return Math.floor(time/60).toString().padStart(2, '0') + ":" + (time % 60).toString().padStart(2, '0') -} - -function formatSeconds() { - return (60 - getDateObject().getSeconds()).toString().padStart(2, '0') -} - -function nextChampionship() { - let remainingOffset = 0 - let day = currentDay() - let time = nextTime() - - if (time == null) { - remainingOffset = 1440 - day = nextDay() - time = firstTimeNextDay() - } - - return { - day: day, - time: time, - location: championships[day][time], - remaining: (remainingOffset + time) - currentTime(), - } -} - -function updateChampionshipDetails(next) { - document.getElementById('time').innerHTML = formatTime(next.time) - document.getElementById('location').innerHTML = next.location -} - -function updateTimer(next) { - let remaining = formatTime(next.remaining) + ":" + formatSeconds() - document.getElementById('timer').innerHTML = remaining - document.title = `${remaining} - ${next.location} - SSO Timer` -} - -function updateChampionshipTable(next) { - const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] - let table = `` - for (const [day, details] of Object.entries(championships)) { - let dayContainer = `

    ${weekdays[day]}

    ` - let dayContainerClasses = 'day-container' - - if (currentDay() == day) { - dayContainerClasses += ' today' - } - - for (const [time, location] of Object.entries(details)) { - let formattedTime = formatTime(time) - let classes = 'time-container' - if (next.day == day && next.time == time) { - classes += ' next-time' - } - - dayContainer += `
  • ${formattedTime}: ${location}
  • ` - } - - table += `
    ` - } - - document.getElementById('championships_table').innerHTML = table -} - -window.addEventListener('load', () => { - fetch('data/championships.json').then((response) => { - response.json().then((json) => { - championships = json - locations = flattenObjectIntoSingleUniqueArray(championships) - - }) - }) - - setInterval(() => { - if (championships.length < 1) { - return - } - - let next = nextChampionship() - - if (lastChampionship != nextChampionship()) { - updateChampionshipDetails(next) - updateChampionshipTable(next) - } - - updateTimer(next) - }, updateFrequency) -})