From 61f9d04750e8fb8ae5284d98dfae0277add85cab Mon Sep 17 00:00:00 2001 From: Qumarth Jash Date: Wed, 15 Jul 2026 18:59:02 +0100 Subject: [PATCH] Switch to grist plugin --- README.md | 2 +- index.html | 55 ++++++++++++++ main.py | 44 ------------ plugin-api.js | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 45 deletions(-) create mode 100644 index.html delete mode 100644 main.py create mode 100644 plugin-api.js diff --git a/README.md b/README.md index 3510489..f108c47 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # video-privacy-today -Tell us what videos on this day require no recording and/or no streaming \ No newline at end of file +Tell us what events at EMF require our attention. This is a grist plugin diff --git a/index.html b/index.html new file mode 100644 index 0000000..42a79df --- /dev/null +++ b/index.html @@ -0,0 +1,55 @@ + + + + + + + EMF Video + + + + + + + +
+

Next refresh in 60

+
+ +
+

Events Happening Now

+ + + + + + + + + + + + + +
NameVenueVideo Team NeededPrivacyCommentsEnds In
+
+ +
+

Events Happening Next

+ + + + + + + + + + + + + +
NameVenueVideo Team NeededPrivacyCommentsStarts In
+
+ + diff --git a/main.py b/main.py deleted file mode 100644 index f8138c1..0000000 --- a/main.py +++ /dev/null @@ -1,44 +0,0 @@ -from datetime import datetime - -import urllib.request -import json - -def extract_day(datetime_str): - dt = datetime.strptime(datetime_str.split()[0], '%Y-%m-%d') - return(dt.date()) - -all_stuff = None -with urllib.request.urlopen('https://www.emfcamp.org/schedule/2024.json') as url: - all_stuff = json.load(url) - -# All things happening today -today = datetime.today().date() -all_today = [] -for thing in all_stuff: - day = extract_day(thing['start_date']) - - if day == today: - all_today.append(thing) - -# Only stages -all_stages = [] -for thing in all_today: - if 'Stage' in thing['venue']: - all_stages.append(thing) - -# Everything on stages today that's not public -not_public = [] -for talk in all_stages: - if talk['video_privacy'] != 'public': - not_public.append(talk) - -# Ok, print it -print('TITLE', 'START_TIME', 'END_TIME', 'VENUE', 'PRIVACY', sep=' | ') -print('-'*50) -not_public = sorted(not_public, key = lambda x: x['start_date']) -for e in not_public: - print('\n') - start_time = e['start_date'].split()[1] - end_time = e['end_date'].split()[1] - privacy = 'REVIEW' if e['video_privacy'] == 'review' else 'PRIVATE' - print(e['title'], start_time, end_time, e['venue'], privacy, sep=' | ') diff --git a/plugin-api.js b/plugin-api.js new file mode 100644 index 0000000..db2238a --- /dev/null +++ b/plugin-api.js @@ -0,0 +1,193 @@ +const MAX_COUNTDOWN = 60; + +var countdown = MAX_COUNTDOWN; +var now = Date.now(); +var eventsOnNow = []; +var eventsOnNext = []; + +grist.ready({ + requiredAccess: 'read table' + }); + +grist.onRecords(function (records) { + eventsOnNow = []; + eventsOnNext = []; + now = Date.now(); + + var i = 0; + + console.info(records); + + for (const r of records) { + let privacy = r["video_privacy"]; + let videoNeeded = r["Video_team_attending"]; + + if (privacy == "none" || videoNeeded == true) { + let e = new Event( + i++, + r["title"], + r["venue"], + videoNeeded, + privacy, + r["Comments"], + Date.parse(r["start_date"]), + Date.parse(r["end_date"]) + ); + + if (e.hasStarted() && !e.hasEnded()) { + eventsOnNow.push(e); + } else if (!e.hasStarted() && !e.hasEnded()) { + eventsOnNext.push(e); + } + } + } + + eventsOnNow.sort((e1, e2) => { + return (e1.timeUntilEnd() > e2.timeUntilEnd()) ? 1 : ((e2.timeUntilEnd() > e1.timeUntilEnd()) ? -1 : 0) + }); + + eventsOnNext.sort((e1, e2) => { + return (e1.timeUntilStart() > e2.timeUntilStart()) ? 1 : ((e2.timeUntilStart() > e1.timeUntilStart()) ? -1 : 0) + }); + + console.log(eventsOnNext); + + populateTable("now", eventsOnNow); + populateTable("next", eventsOnNext); + + window.setInterval(() => handleCountdown(), 1000); +}); + + +grist.onOptions(function(options, interaction) { + console.info("Options reloaded"); +}); + +class Event { + constructor(id, name, venue, videoNeeded, privacy, comments, startTime, endTime) { + this.id = id; + this.name = name; + this.venue = venue; + this.videoNeeded = videoNeeded; + this.privacy = privacy; + this.comments = comments; + this.startTime = startTime; + this.endTime = endTime; + } + + hasStarted() { + return this.startTime < now; + } + + hasEnded() { + return this.endTime < now; + } + + timeUntilStart() { + return new Date(this.startTime - now); + } + + timeUntilEnd() { + return new Date(this.endTime - now); + } +} + +function makeTextBold(text) { + return "" + text + ""; +} + +function convertTimeToString(time) { + // Using getHours() is bullshit for this usage, it won't include any days in the hours + let hours = Math.floor(time / (1000 * 60 * 60)); + if (hours < 10) { + hours = "0" + hours + } + + let minutes = time.getMinutes(); + if (minutes < 10) { + minutes = "0" + minutes + } + return hours + ":" + minutes; +} + +function populateTable(elementName, items) { + for (const i of items) { + const tableRow = document.createElement("tr"); + + tableRow.setAttribute("id", i.id); + + var name = document.createElement("td"); + var venue = document.createElement("td"); + var videoNeeded = document.createElement("td"); + var privacy = document.createElement("td"); + var comments = document.createElement("td"); + var startsIn = document.createElement("td"); + + startsIn.setAttribute("id", i.id + "_time"); + + name.innerHTML = i.name; + venue.innerHTML = i.venue; + videoNeeded.innerHTML = i.videoNeeded; + privacy.innerHTML = i.privacy; + comments.innerHTML = i.comments; + startsIn.innerHTML = convertTimeToString(i.timeUntilStart()); + + tableRow.appendChild(name); + tableRow.appendChild(venue); + tableRow.appendChild(videoNeeded); + tableRow.appendChild(privacy); + tableRow.appendChild(comments); + tableRow.appendChild(startsIn); + + // Colour in any rows + if (i.privacy != "public") { + privacy.innerHTML = makeTextBold(privacy.innerHTML); + + if (i.privacy == "none") { + tableRow.setAttribute("class", "table-danger"); + } else if (i.privacy == "review") { + tableRow.setAttribute("class", "table-warning"); + } + } + + if (i.comments != "") { + comments.innerHTML = makeTextBold(comments.innerHTML); + tableRow.setAttribute("class", "table-danger"); + } + + if (i.videoNeeded == true) { + videoNeeded.innerHTML = makeTextBold(videoNeeded.innerHTML); + tableRow.setAttribute("class", "table-danger"); + } + + document.getElementById(elementName).getElementsByTagName("tbody")[0].append(tableRow); + } +} + +function refreshTables() { + var toDelete = []; + + now = Date.now(); + for (const nowItem of eventsOnNow) { + let timeUntilEnd = nowItem.timeUntilEnd(); + + let timeField = document.getElementById(nowItem.id + "_time"); + timeField.innerHTML = convertTimeToString(nowItem.timeUntilEnd()); + } + + for (const nextItem of eventsOnNext) { + let timeField = document.getElementById(nextItem.id + "_time"); + timeField.innerHTML = convertTimeToString(nextItem.timeUntilEnd()); + } +} + +function handleCountdown() { + countdown--; + if (countdown == 0) { + countdown = MAX_COUNTDOWN; + refreshTables(); + } + + let counterElement = document.getElementById("refresh_counter_text"); + counterElement.innerHTML = "Next refresh in " + countdown; +}