Last active
October 25, 2021 20:05
-
-
Save chrisonntag/645d782bd9e48a71c6c2b84b21cfb734 to your computer and use it in GitHub Desktop.
Universität Passau Lesesaalbelegung - Scriptable iOS Widget
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Variables used by Scriptable | |
// icon-color: deep-gray; icon-glyph: th | |
// ProgressCircle class is adapted from https://github.com/chaeimg/battCircle | |
// Licence: Christoph Sonntag (https://github.com/chrisonntag), dl-de/by-2-0 | |
class BibOccupationWidget { | |
constructor() { | |
this.source = "https://www.zim.uni-passau.de/dienstleistungen/lesesaalbelegungen/" | |
this.apiUrl = "https://ampel.zim.uni-passau.de/v2"; | |
this.abbr = { | |
"Zentralbibliothek": "ZB", | |
"Juridicum": "JUR", | |
"Wirtschaftswissenschaften": "WIWI", | |
"Nikolakloster": "NK", | |
"Informatik/Mathematik": "IM" | |
} | |
this.abbrReverse = { | |
"ZB": "Zentralbibliothek", | |
"JUR": "Juridicum", | |
"WIWI": "Wirtschaftswissenschaften", | |
"NK": "Nikolakloster", | |
"IM": "Informatik/Mathematik" | |
} | |
this.shorts = Object.keys(this.abbrReverse) | |
this.favourite = args.widgetParameter || this.shorts[Math.floor(Math.random() * this.shorts.length)] | |
this.bgColor = "FFFFFF" | |
this.fontColor = "121212" | |
} | |
async run() { | |
let widget = await this.createWidget() | |
if (!config.runsInWidget) { | |
await widget.presentSmall() | |
} | |
widget.url = this.source | |
widget.refreshAfterDate = new Date(Date.now() + 1000 * 60 * 10) // in ms: 1000*60*10 == 10min | |
Script.setWidget(widget) | |
Script.complete() | |
} | |
async createWidget(items) { | |
let data = await this.getData() | |
// Basic widget setup | |
let list = new ListWidget() | |
list.backgroundColor = new Color(this.bgColor) | |
list.setPadding(0, 0, 0, 0) | |
let textStack = list.addStack() | |
textStack.setPadding(14, 14, 14, 14) | |
textStack.layoutVertically() | |
textStack.topAlignContent() | |
if (data.error) { | |
// Error handling | |
let loadingIndicator = textStack.addText(data.error.toUpperCase()) | |
loadingIndicator.font = Font.mediumSystemFont(13) | |
loadingIndicator.textColor = new Color(this.fontColor) | |
loadingIndicator.textOpacity = 0.5 | |
let spacer = textStack.addStack() | |
spacer.addSpacer(); | |
} else { | |
// Extract data for the favourite room | |
let favouriteRoom = data[this.abbrReverse[this.favourite]] | |
let favouriteRoomName = textStack.addText(this.abbr[favouriteRoom.roomname]) | |
favouriteRoomName.font = Font.mediumSystemFont(15) | |
favouriteRoomName.textColor = new Color(this.fontColor) | |
try { | |
// Room Name | |
let favouriteFreeSpace = parseInt(favouriteRoom.freeSpace.slice(0, -1)) | |
let favouriteUsedSpace = 100 - favouriteFreeSpace | |
// Used Seats in percent | |
let favouriteDataStack = textStack.addStack() | |
favouriteDataStack.layoutHorizontally() | |
favouriteDataStack.bottomAlignContent() | |
let favouriteUsedSpaceLabel = favouriteDataStack.addText(favouriteUsedSpace + "%") | |
favouriteUsedSpaceLabel.font = Font.boldSystemFont(30) | |
favouriteUsedSpaceLabel.textColor = favouriteRoom.state == 'red' ? Color.red() : favouriteRoom.state == 'yellow' ? Color.yellow() : Color.green(); | |
// Used seats in total | |
let seatsLabel = textStack.addText(Math.floor(favouriteUsedSpace / 100 * favouriteRoom.seats) + "/" + favouriteRoom.seats + " Plätze") | |
seatsLabel.font = Font.mediumSystemFont(12) | |
seatsLabel.textColor = new Color(this.fontColor) | |
let favouriteQualifierLabel = textStack.addText("belegt") | |
favouriteQualifierLabel.font = Font.regularSystemFont(10) | |
favouriteQualifierLabel.textColor = new Color(this.fontColor) | |
} catch (e) { | |
console.log(e) | |
} | |
delete data[this.abbrReverse[this.favourite]] | |
textStack.addSpacer() | |
let tableStack = textStack.addStack() | |
tableStack.layoutHorizontally() | |
var count = 1 | |
for (const room in data) { | |
let roomData = data[room] | |
let dataStack = tableStack.addStack() | |
dataStack.layoutVertically() | |
dataStack.centerAlignContent() | |
// Remaning rooms | |
try { | |
let freeSpace = parseInt(roomData.freeSpace.slice(0, -1)) | |
let usedSpace = 100 - freeSpace | |
// Progress Circle | |
let progressCircle = new ProgressCircle(usedSpace, this.bgColor) | |
let progressStack = dataStack.addStack() | |
progressStack.layoutVertically() | |
progressStack.centerAlignContent() | |
progressStack.setPadding(0, 0, 0, 0) | |
let progressCircleImage = progressStack.addImage(progressCircle.getImage()) | |
progressCircleImage.applyFittingContentMode() | |
// Room name | |
let usedSpaceStack = dataStack.addStack() | |
usedSpaceStack.layoutHorizontally() | |
usedSpaceStack.size = new Size(24, 10) | |
usedSpaceStack.setPadding(2, 0, 0, 0) | |
let usedSpaceLabel = usedSpaceStack.addText(this.abbr[roomData.roomname]) | |
usedSpaceLabel.centerAlignText() | |
usedSpaceLabel.font = Font.mediumSystemFont(8) | |
usedSpaceLabel.textColor = new Color(this.fontColor) | |
} catch (e) { | |
console.log("Can't parse int.") | |
console.log(e) | |
} | |
if (count < Object.keys(data).length) { | |
// add Spacer except for the last element | |
tableStack.addSpacer() | |
} | |
count = count + 1 | |
} | |
} | |
return list | |
} | |
async getData() { | |
try { | |
let zimDoc = await new Request(this.apiUrl).loadString() | |
let webView = new WebView() | |
await webView.loadURL(this.apiUrl) | |
var rooms = ` | |
rooms = {} | |
document.querySelectorAll(".room").forEach((room) => { | |
let roomname = room.querySelector(".roomname").innerText; | |
let freeSpace = room.querySelector(".detail").querySelector("tbody").querySelector("tr").querySelectorAll("td")[0].innerText; | |
let seats = room.querySelector(".detail").querySelector("tbody").querySelector("tr").querySelectorAll("td")[1].innerText; | |
let state = "" | |
room.querySelector(".trafficlightbody").childNodes.forEach((light) => { | |
let className = light.className ? light.className : "" | |
if (className != "" && className.slice(-6, className.length) == "active") { | |
state = className.slice(0,-6) | |
} | |
}); | |
roomData = { | |
roomname: roomname, | |
freeSpace: freeSpace, | |
seats: seats, | |
state: state | |
} | |
rooms[roomData.roomname] = roomData | |
}); | |
rooms | |
` | |
let response = await webView.evaluateJavaScript(rooms, false) | |
return response; | |
return {error: "Lesesaal nicht verfügbar."} | |
} catch (e) { | |
return {error: "Fehler bei Datenabruf."}; | |
} | |
} | |
} | |
class ProgressCircle { | |
constructor(progress, bgColor) { | |
this.canvas = new DrawContext(); | |
this.canvSize = 110; | |
this.canvTextSize = 32; | |
this.canvFillColor = '121212'; //remaining color | |
this.canvStrokeColor = 'EDEDED'; //used space color | |
this.canvBackColor = bgColor; //Widget background color | |
this.canvTextColor = '121212'; //Widget text color (use same color as above to hide text) | |
this.canvWidth = 11; // circle width | |
this.canvRadius = 50; // circle radius | |
this.canvas.size = new Size(this.canvSize, this.canvSize); | |
this.canvas.respectScreenScale = true; | |
this.progress = progress; | |
this.drawArc( | |
new Point(this.canvSize / 2, this.canvSize / 2), | |
this.canvRadius, | |
this.canvWidth, | |
Math.floor(this.progress * 3.6) | |
); | |
this.canvTextRect = new Rect( | |
0, | |
100 - this.canvTextSize * 2, | |
this.canvSize, | |
this.canvTextSize | |
); | |
this.canvas.setTextAlignedCenter(); | |
this.canvas.setTextColor(new Color(this.canvTextColor)); | |
this.canvas.setFont(Font.mediumSystemFont(this.canvTextSize)); | |
this.canvas.drawTextInRect("" + this.progress + "%", this.canvTextRect); | |
} | |
getImage() { | |
return this.canvas.getImage(); | |
} | |
sinDeg(deg) { | |
return Math.sin((deg * Math.PI) / 180); | |
} | |
cosDeg(deg) { | |
return Math.cos((deg * Math.PI) / 180); | |
} | |
drawArc(ctr, rad, w, deg) { | |
let bgx = ctr.x - rad; | |
let bgy = ctr.y - rad; | |
let bgd = 2 * rad; | |
let bgr = new Rect(bgx, bgy, bgd, bgd); | |
let bgc = new Rect(0, 0, this.canvSize, this.canvSize); | |
this.canvas.setFillColor(new Color(this.canvBackColor)); | |
this.canvas.fill(bgc); | |
this.canvas.setFillColor(new Color(this.canvFillColor)); | |
this.canvas.setStrokeColor(new Color(this.canvStrokeColor)); | |
this.canvas.setLineWidth(w); | |
this.canvas.strokeEllipse(bgr); | |
for (let t = 0; t < deg; t++) { | |
let rect_x = ctr.x + rad * this.sinDeg(t) - w / 2; | |
let rect_y = ctr.y - rad * this.cosDeg(t) - w / 2; | |
let rect_r = new Rect(rect_x, rect_y, w, w); | |
this.canvas.fillEllipse(rect_r); | |
} | |
} | |
} | |
await new BibOccupationWidget().run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Lesesaalbelegung - Universität Passau
Auf Basis der Daten der Uni Passau werden die aktuell belegten Plätze in allen verfügbaren Bibliotheken angezeigt.
Installation