-
-
Save Chaos53925/5617c8a70ddbaec918b2d6804755c92f to your computer and use it in GitHub Desktop.
A Scriptable widget that shows the amount of people who have received the corona vaccination in Germany
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
// Version 1.4.7 | |
// 03.02.2023 | |
// | |
// Original von marco79cgn | |
// | |
// Mit Caching und Fallback | |
// Neue API mit TSV-Parsing | |
// Dark-Mode Unterstützung | |
// Dark-Mode kann nun deaktiviert werden | |
// Bei deaktivierung kann man auch auswählen ob man dauerhaft den Dark- oder Lightmode möchte | |
// Setup Assistent für unversierte Nutzer hinzugefügt (erweiterbar auf neue Funktionen) | |
// Das öffnen der Url ist nun abschaltbar | |
// Mediumsize Ansicht hinzugefügt | |
// Neue standart Ansicht für die Medium Size Ansicht hinzugefügt | |
// Alte Ansicht als Alternative Ansicht beibehalten | |
let darkModeSupport = 2 | |
let urlSupport = 1 | |
let mediumDesign = 1 | |
let widgetInputRAW = args.widgetParameter | |
let dark | |
let url | |
let design | |
let textColors = new Color("000000") | |
if (!config.runsInWidget && config.runsInApp) { | |
const prompt = new Alert() | |
prompt.message = 'Möchtest du den Setup Assistant starten?' | |
prompt.addAction('Ja') | |
prompt.addCancelAction('Nein') | |
if (await prompt.presentAlert() === 0) { | |
await setupAssistant() | |
} | |
} | |
// Setup Assistent für unversierte Nutzer um die nötgen Parameter zu erhalten | |
async function setupAssistant() { | |
let para = '' | |
let m | |
let u | |
let d | |
const darkModePrompt = new Alert() | |
darkModePrompt.message = 'Welche Darkmode Einstellung möchtest du verwenden?' | |
darkModePrompt.addAction('Automatisch') | |
darkModePrompt.addAction('Festgelegt') | |
if (await darkModePrompt.presentAlert() === 0) { | |
d = "2" | |
} else { | |
const darkModeSetPrompt = new Alert() | |
darkModeSetPrompt.message = 'Bitte leg deine Darkmode Einstellung fest.' | |
darkModeSetPrompt.addAction('Light') | |
darkModeSetPrompt.addAction('Dark') | |
if (await darkModeSetPrompt.present() === 0) { | |
d = "1" | |
} else { | |
d = "0" | |
} | |
} | |
if (d !== null) { | |
const urlSupportPrompt = new Alert() | |
urlSupportPrompt.message = 'Soll die Website beim klicken auf das Widget geöffnet werden?' | |
urlSupportPrompt.addAction('Ja') | |
urlSupportPrompt.addAction('Nein') | |
if (await urlSupportPrompt.presentAlert() === 0) { | |
u = "1" | |
} else { | |
u = "0" | |
} | |
} | |
if (m !== null) { | |
const mediumDesignPrompt = new Alert() | |
mediumDesignPrompt.message = 'Soll die Medium Ansicht ein alternatives Aussehen verwenden?' | |
mediumDesignPrompt.addAction('Ja') | |
mediumDesignPrompt.addAction('Nein') | |
if (await mediumDesignPrompt.presentAlert() === 0) { | |
m = "1" | |
} else { | |
m = "0" | |
} | |
} | |
para = `${d}${u}${m}` | |
console.log('Config: ' + para) | |
Pasteboard.copy(para) | |
const promptSuccess = new Alert() | |
promptSuccess.title = 'Setup abgeschlossen' | |
promptSuccess.message = 'Die für dich passende Konfiguration wurde generiert und in die Zwischenablage kopiert.\nFüge diese nun in das Feld "Parameter" in den Widget Einstellungen ein.' | |
promptSuccess.addAction('Schließen') | |
await promptSuccess.present() | |
} | |
// convert paraeters | |
if (widgetInputRAW !== null) { | |
const parameter = widgetInputRAW.toString() | |
dark = parameter.charAt(0) | |
url = parameter.charAt(1) | |
design = parameter.charAt(2) | |
if (dark === "0") { | |
darkModeSupport = 0 | |
} else if (dark === "1") { | |
darkModeSupport = 1 | |
} else if (dark === "2" || dark === null) { | |
darkModeSupport = 2 | |
} | |
if (url === "0") { | |
urlSupport = 0 | |
} else if (url === "1" || url === null) { | |
urlSupport = 1 | |
} | |
if (design === "0") { | |
mediumDesign = 0 | |
} else if (design === "1" || design === null) { | |
mediumDesign = 1 | |
} | |
} | |
const resetCache = false; | |
const cacheMinutes = 3600; | |
const today = new Date(); | |
const neededTotalVaccinations = 83200000; | |
let result; | |
let resultDe; | |
let width | |
const h = 5; | |
const colorLightGreen = new Color("#47b881") | |
const colorDarkGreen = new Color("#00783e") | |
const colorGreen0 = new Color("#084D20") | |
const colorGreen1 = new Color("#208045") | |
const colorGreen2 = new Color("#39AD64") | |
const colorGreen3 = new Color("#6FC98E") | |
let widget = new ListWidget(); | |
widget.setPadding(6, 12, 8, 12); | |
await getNumbers(); | |
await createWidget(); | |
Script.setWidget(widget); | |
Script.complete(); | |
if (config.runsInApp) { | |
widget.presentSmall(); | |
} | |
async function createWidget() { | |
if (config.widgetFamily === "medium") { | |
width = 150 | |
} else { | |
width = 100 | |
} | |
// Add background color | |
if (darkModeSupport === 2) { | |
// dynamisch | |
textColors = Color.dynamic(textColors, new Color("ffffff")) | |
widget.backgroundColor = Color.dynamic(new Color("ffffff"), new Color("1c1c1c")); | |
} else if (darkModeSupport === 1) { | |
// light | |
textColors = Color.dynamic(textColors, textColors) | |
widget.backgroundColor = Color.dynamic(new Color("ffffff"), new Color("ffffff")); | |
} else if (darkModeSupport === 0) { | |
// dark | |
textColors = Color.dynamic(new Color("ffffff"), new Color("ffffff")) | |
widget.backgroundColor = Color.dynamic(new Color("1c1c1c"), new Color("1c1c1c")); | |
} | |
if (urlSupport === 1) { | |
widget.url = "https://interaktiv.morgenpost.de/corona-impfungen-deutschland-bundeslaender-weltweit/"; | |
} else { | |
widget.url = null | |
} | |
if (config.widgetFamily === 'medium') { | |
if (mediumDesign === 1) { | |
widget.addSpacer(); | |
const dataFirst = { | |
text: "Erstimpfung", | |
quota: resultDe.vaccinations1_quota, | |
vaccination: resultDe.vaccinations1, | |
progress: createProgressMedium(resultDe.vaccinations1), | |
}; | |
createProgressStackMediumText(dataFirst); | |
const dataSecond = { | |
text: "Zweitimpfung", | |
quota: resultDe.vaccinations2_quota, | |
vaccination: resultDe.vaccinations2, | |
progress: createProgressMedium(resultDe.vaccinations2), | |
}; | |
createProgressStackMediumText(dataSecond); | |
const dataThird = { | |
text: "Erster Booster", | |
quota: resultDe.vaccinations3_quota, | |
vaccination: resultDe.vaccinations3, | |
progress: createProgressMedium(resultDe.vaccinations3), | |
}; | |
createProgressStackMediumText(dataThird); | |
const dataFourth = { | |
text: "Zweiter Booster", | |
quota: resultDe.vaccinations4_quota, | |
vaccination: resultDe.vaccinations4, | |
progress: createProgressMedium(resultDe.vaccinations3), | |
}; | |
createProgressStackMediumText(dataFourth); | |
const dataBar = { | |
progress: createProgressMedium(resultDe.vaccinations1, resultDe.vaccinations2, resultDe.vaccinations3, resultDe.vaccinations4), | |
}; | |
createProgressStackMediumBar(dataBar); | |
widget.addSpacer(3); | |
let calendarStack = widget.addStack(); | |
const date = resultDe.date.split('-'); // destructure date | |
const lastUpdateDate = new Date(date[0], date[1] - 1, date[2]); // fixes -1 day | |
const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; | |
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString('de-DE', options)); | |
description.centerAlignText(); | |
description.font = Font.mediumSystemFont(9); | |
description.textColor = textColors | |
} else { | |
width = 100 | |
widget.addSpacer(); | |
const dataFirst = { | |
text: "Erstimpfung", | |
quota: resultDe.vaccinations1_quota, | |
vaccination: resultDe.vaccinations1, | |
progress: createProgressShort(resultDe.vaccinations1), | |
}; | |
createProgressStackMedium(dataFirst); | |
const dataSecond = { | |
text: "Zweitimpfung", | |
quota: resultDe.vaccinations2_quota, | |
vaccination: resultDe.vaccinations2, | |
progress: createProgressShort(resultDe.vaccinations2), | |
}; | |
createProgressStackMedium(dataSecond); | |
const dataThird = { | |
text: "Erster Booster", | |
quota: resultDe.vaccinations3_quota, | |
vaccination: resultDe.vaccinations3, | |
progress: createProgressShort(resultDe.vaccinations3), | |
}; | |
createProgressStackMedium(dataThird); | |
const dataFourth = { | |
text: "Zweiter Booster", | |
quota: resultDe.vaccinations4_quota, | |
vaccination: resultDe.vaccinations4, | |
progress: createProgressShort(resultDe.vaccinations4), | |
}; | |
createProgressStackMedium(dataFourth); | |
widget.addSpacer(3); | |
let calendarStack = widget.addStack(); | |
const date = resultDe.date.split('-'); // destructure date | |
const lastUpdateDate = new Date(date[0], date[1] - 1, date[2]); // fixes -1 day | |
const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; | |
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString('de-DE', options)); | |
description.centerAlignText(); | |
description.font = Font.mediumSystemFont(9); | |
description.textColor = textColors | |
} | |
} else { | |
widget.addSpacer(); | |
const dataFirst = { | |
text: "Erstimpfung", | |
quota: resultDe.vaccinations1_quota, | |
vaccination: resultDe.vaccinations1, | |
progress: createProgress(resultDe.vaccinations1), | |
}; | |
createProgressStack(dataFirst); | |
const dataSecond = { | |
text: "Zweitimpfung", | |
quota: resultDe.vaccinations2_quota, | |
vaccination: resultDe.vaccinations2, | |
progress: createProgress(resultDe.vaccinations2), | |
}; | |
createProgressStack(dataSecond); | |
const dataThird = { | |
text: "Booster", | |
quota: resultDe.vaccinations4_quota + " | " + resultDe.vaccinations3_quota, | |
vaccination: resultDe.vaccinations4 + " | " + resultDe.vaccinations3, | |
progress: createProgress(resultDe.vaccinations3, resultDe.vaccinations4), | |
}; | |
createProgressStack(dataThird); | |
widget.addSpacer(3); | |
let calendarStack = widget.addStack(); | |
const date = resultDe.date.split('-'); // destructure date | |
const lastUpdateDate = new Date(date[0], date[1] - 1, date[2]); // fixes -1 day | |
const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; | |
let description = widget.addText("Stand: " + lastUpdateDate.toLocaleDateString('de-DE', options)); | |
description.centerAlignText(); | |
description.font = Font.mediumSystemFont(9); | |
description.textColor = textColors | |
} | |
} | |
function createProgressStack(data) { | |
let firstDoseStack = widget.addStack(); | |
firstDoseStack.layoutHorizontally(); | |
let amountFirstText = firstDoseStack.addText(data.text); | |
amountFirstText.font = Font.boldSystemFont(12); | |
amountFirstText.textColor = textColors | |
firstDoseStack.addSpacer(); | |
let percentFirstText = firstDoseStack.addText(data.quota + "%"); | |
percentFirstText.font = Font.boldSystemFont(12); | |
percentFirstText.textColor = textColors | |
widget.addSpacer(2); | |
let progressStack = widget.addStack(); | |
progressStack.layoutVertically(); | |
let progressNumberStack = widget.addStack(); | |
progressNumberStack.layoutHorizontally(); | |
const progressText = progressNumberStack.addText(numberWithDots(data.vaccination)); | |
progressText.font = Font.mediumSystemFont(10); | |
progressText.textColor = textColors | |
progressStack.addImage(data.progress); | |
widget.addSpacer(3); | |
} | |
function createProgressStackMedium(data) { | |
let firstDoseStack = widget.addStack(); | |
firstDoseStack.layoutHorizontally(); | |
let amountFirstText = firstDoseStack.addText(data.text); | |
amountFirstText.font = Font.boldSystemFont(12); | |
amountFirstText.textColor = textColors | |
firstDoseStack.addSpacer(); | |
let percentFirstText = firstDoseStack.addText(data.quota + "%"); | |
percentFirstText.font = Font.boldSystemFont(12); | |
percentFirstText.textColor = textColors | |
widget.addSpacer(2); | |
let progressText = widget.addStack(); | |
progressText.layoutHorizontally(); | |
let progressNumberText = progressText.addText(numberWithDots(data.vaccination)); | |
progressNumberText.font = Font.mediumSystemFont(10); | |
progressNumberText.textColor = textColors | |
progressText.addSpacer(30); | |
let progressStack = progressText.addImage(data.progress); | |
widget.addSpacer(3); | |
} | |
function createProgressStackMediumText(data) { | |
let firstDoseStack = widget.addStack(); | |
firstDoseStack.layoutHorizontally(); | |
let amountFirstText = firstDoseStack.addText(data.text); | |
amountFirstText.font = Font.boldSystemFont(12); | |
amountFirstText.textColor = textColors | |
firstDoseStack.addSpacer(); | |
let percentFirstText = firstDoseStack.addText(data.quota + "%"); | |
percentFirstText.font = Font.boldSystemFont(12); | |
percentFirstText.textColor = textColors | |
let progressNumberStack = widget.addStack(); | |
progressNumberStack.layoutHorizontally(); | |
const progressText = progressNumberStack.addText(numberWithDots(data.vaccination)); | |
progressText.font = Font.mediumSystemFont(10); | |
progressText.textColor = textColors | |
widget.addSpacer(1); | |
} | |
function createProgressStackMediumBar(data) { | |
let progressStack = widget.addStack(); | |
progressStack.layoutVertically(); | |
progressStack.addImage(data.progress); | |
widget.addSpacer(1); | |
} | |
// get images from local storage or download them once (currently not used) | |
//async function getImage(image) { | |
// let fm = FileManager.local(); | |
// let dir = fm.documentsDirectory(); | |
// let path = fm.joinPath(dir, image); | |
// if (fm.fileExists(path)) { | |
// return fm.readImage(path); | |
// } else { | |
// // download once | |
// let imageUrl; | |
// switch (image) { | |
// case "vac-logo.png": | |
// imageUrl = "https://i.imgur.com/ZsBNT8E.png"; | |
// break; | |
// case "calendar-icon.png": | |
// imageUrl = "https://i.imgur.com/Qp8CEFf.png"; | |
// break; | |
// default: | |
// console.log(`Sorry, couldn't find ${image}.`); | |
// } | |
// let req = new Request(imageUrl); | |
// let loadedImage = await req.loadImage(); | |
// fm.writeImage(path, loadedImage); | |
// return loadedImage; | |
// } | |
//} | |
function numberWithDots(x) { | |
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, "."); | |
} | |
async function getNumbers() { | |
// Set up the file manager. | |
const files = FileManager.local(); | |
// Set up cache | |
const cachePath = files.joinPath( | |
files.cacheDirectory(), | |
"api-cache-covid-vaccination-numbers-mopo-tsv" | |
); | |
const cacheExists = files.fileExists(cachePath); | |
const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0; | |
// Get Data | |
try { | |
// If cache exists and it's been less than 60 minutes since last request, use cached data. | |
if ( | |
cacheExists && !resetCache && | |
today.getTime() - cacheDate.getTime() < cacheMinutes * 60 * 1000 | |
) { | |
console.log("Get from Cache"); | |
result = files.readString(cachePath); | |
} else { | |
console.log("Get from API"); | |
const req2 = new Request( | |
"https://cdn.fnki.de/corona-data/vaccinations.rki.tsv" | |
); | |
result = await req2.loadString(); | |
console.log("Write Data to Cache"); | |
try { | |
files.writeString(cachePath, result); | |
} catch (e) { | |
console.log("Creating Cache failed!"); | |
console.log(e); | |
} | |
} | |
} catch (e) { | |
console.error(e); | |
if (cacheExists) { | |
console.log("Get from Cache"); | |
result = files.readString(cachePath); | |
} else { | |
console.log("No fallback to cache possible. Due to missing cache."); | |
} | |
} | |
await setTotalVacNoForGermany(result); | |
} | |
async function setTotalVacNoForGermany(result) { | |
const cols = result.split(/\r?\n/); | |
const data = cols.map(col => col.split('\t')); | |
data.pop(); // remove last (empty) element | |
const name = data[0]; | |
const vals = data[data.length - 1]; | |
const date = name.indexOf('date'); | |
const vac1 = name.indexOf('vaccinations1'); | |
const vac2 = name.indexOf('vaccinations2'); | |
const vac3 = name.indexOf('vaccinations3'); | |
const vac4 = name.indexOf('vaccinations4'); | |
const vac1Quota = name.indexOf('vaccinations1_quota'); | |
const vac2Quota = name.indexOf('vaccinations2_quota'); | |
const vac3Quota = name.indexOf('vaccinations3_quota'); | |
const vac4Quota = name.indexOf('vaccinations4_quota'); | |
resultDe = { | |
date: vals[date], | |
vaccinations1: vals[vac1], | |
vaccinations1_quota: vals[vac1Quota], | |
vaccinations2: vals[vac2], | |
vaccinations2_quota: vals[vac2Quota], | |
vaccinations3: vals[vac3], | |
vaccinations3_quota: vals[vac3Quota], | |
vaccinations4: vals[vac4], | |
vaccinations4_quota: vals[vac4Quota], | |
}; | |
} | |
function createProgress(firstVacNo, secondVacNo) { | |
const context = new DrawContext(); | |
context.size = new Size(width, h); | |
context.opaque = false; | |
context.respectScreenScale = true; | |
context.setFillColor(new Color("#d2d2d7")); | |
const path = new Path(); | |
path.addRoundedRect(new Rect(0, 0, width, h), 3, 2); | |
context.addPath(path); | |
context.fillPath(); | |
context.setFillColor(colorLightGreen); | |
const path1 = new Path(); | |
const path1width = | |
(width * firstVacNo) / neededTotalVaccinations > width | |
? width | |
: (width * firstVacNo) / neededTotalVaccinations; | |
path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2); | |
context.addPath(path1); | |
context.fillPath(); | |
if (secondVacNo) { | |
const path2 = new Path(); | |
const path2width = | |
(width * secondVacNo) / neededTotalVaccinations > width | |
? width | |
: (width * secondVacNo) / neededTotalVaccinations; | |
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2); | |
context.addPath(path2); | |
context.setFillColor(colorDarkGreen) | |
context.fillPath(); | |
} | |
return context.getImage(); | |
} | |
function createProgressMedium(firstVacNo, secondVacNo, thirdVacNo, fourthVacNo) { | |
const context = new DrawContext(); | |
context.size = new Size(width, h); | |
context.opaque = false; | |
context.respectScreenScale = true; | |
context.setFillColor(new Color("#d2d2d7")); | |
const path = new Path(); | |
path.addRoundedRect(new Rect(0, 0, width, h), 3, 2); | |
context.addPath(path); | |
context.fillPath(); | |
const path1 = new Path(); | |
const path1width = | |
(width * firstVacNo) / neededTotalVaccinations > width | |
? width | |
: (width * firstVacNo) / neededTotalVaccinations; | |
path1.addRoundedRect(new Rect(0, 0, path1width, h), 3, 2); | |
context.addPath(path1); | |
context.fillPath(); | |
context.setFillColor(colorGreen3); | |
if (secondVacNo) { | |
const path2 = new Path(); | |
const path2width = | |
(width * secondVacNo) / neededTotalVaccinations > width | |
? width | |
: (width * secondVacNo) / neededTotalVaccinations; | |
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2); | |
context.addPath(path2); | |
context.setFillColor(colorGreen2) | |
context.fillPath(); | |
} | |
if (thirdVacNo) { | |
const path2 = new Path(); | |
const path2width = | |
(width * thirdVacNo) / neededTotalVaccinations > width | |
? width | |
: (width * thirdVacNo) / neededTotalVaccinations; | |
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2); | |
context.addPath(path2); | |
context.setFillColor(colorGreen1) | |
context.fillPath(); | |
} | |
if (fourthVacNo) { | |
const path2 = new Path(); | |
const path2width = | |
(width * fourthVacNo) / neededTotalVaccinations > width | |
? width | |
: (width * fourthVacNo) / neededTotalVaccinations; | |
path2.addRoundedRect(new Rect(0, 0, path2width, h), 3, 2); | |
context.addPath(path2); | |
context.setFillColor(colorGreen0) | |
context.fillPath(); | |
} | |
return context.getImage(); | |
} | |
function createProgressShort(firstVacNo) { | |
const context = new DrawContext(); | |
context.size = new Size(width, h); | |
context.opaque = false; | |
context.respectScreenScale = true; | |
context.setFillColor(new Color("#d2d2d7")); | |
const path = new Path(); | |
path.addRoundedRect(new Rect(0, 0, width, 3), 2, 2); | |
context.addPath(path); | |
context.fillPath(); | |
context.setFillColor(colorLightGreen); | |
const path1 = new Path(); | |
const path1width = | |
(width * firstVacNo) / neededTotalVaccinations > width | |
? width | |
: (width * firstVacNo) / neededTotalVaccinations; | |
path1.addRoundedRect(new Rect(0, 0, path1width, 3), 2, 2); | |
context.addPath(path1); | |
context.fillPath(); | |
return context.getImage(); | |
} | |
// | |
// Bitte bis zum Ende kopieren | |
// |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Standart Ansichten für Small und Medium.
Stand: 03.02.2023