|
const CONFIG = { |
|
dataURL: 'https://api.socialspill.com/covid', |
|
sourceURL: 'https://covidtracker.fr/vaccintracker/', |
|
alsoDrawState: undefined, |
|
debug: false |
|
}; |
|
|
|
const widgetBackgroundColor = Color.dynamic(new Color("#f2f2f2"), new Color("#1c1c1e")); |
|
|
|
const fetchJSON = async (url) => { |
|
const request = new Request(url); |
|
const data = await request.loadJSON() |
|
return data; |
|
}; |
|
|
|
const drawError = (message) => { |
|
const widget = new ListWidget(); |
|
|
|
let text = widget.addText(message); |
|
text.font = Font.mediumSystemFont(16); |
|
|
|
return widget; |
|
}; |
|
|
|
async function drawRegion(view, title, { difference_to_the_previous_day: delta, vaccinated, percent_vaccinated: percent_vaccinated, total: population, update: update, update_hour: update_hour, source: source, url: url }) { |
|
|
|
const stackTitle = view.addStack(); |
|
stackTitle.layoutHorizontally(); |
|
stackTitle.centerAlignContent(); |
|
stackTitle.spacing = 1; |
|
let stackTitleText = stackTitle.addText(`🇫🇷 France`) |
|
stackTitleText.font = Font.boldSystemFont(14); |
|
stackTitleText.minimumScaleFactor = 1; |
|
|
|
stackTitle.addSpacer(10) |
|
|
|
//let logoImage = stackTitle.addImage(await getImage("vac-logo2.png")); |
|
//logoImage.imageSize = new Size(25,25); |
|
|
|
view.addSpacer(5) |
|
|
|
const stack = view.addStack(); |
|
stack.layoutVertically(); |
|
stack.centerAlignContent(); |
|
stack.spacing = 1; |
|
let stackText = stack.addText(`💉 +${new Intl.NumberFormat('fr-FR', { style: 'decimal' }).format(delta.toString())} en 24h`) |
|
stackText.font = Font.mediumSystemFont(12); |
|
stackText.lineLimit = 1 |
|
stackText.minimumScaleFactor = 0.5; |
|
stack.addSpacer(4) |
|
|
|
const stackTotalVacc = view.addStack(); |
|
stackTotalVacc.layoutVertically(); |
|
stackTotalVacc.centerAlignContent(); |
|
|
|
let stackTotalVaccText = stackTotalVacc.addText(`👪 ${new Intl.NumberFormat('fr-FR', { style: 'decimal' }).format(vaccinated.toString())} vaccinés`) |
|
stackTotalVaccText.font = Font.mediumSystemFont(11) |
|
stackTotalVaccText.lineLimit = 1 |
|
stackTotalVaccText.minimumScaleFactor = 0.5; |
|
stackTotalVacc.addSpacer(8) |
|
|
|
const stack2 = view.addStack(); |
|
stack2.layoutVertically(); |
|
stack2.centerAlignContent(); |
|
stack2.spacing = 1; |
|
let stackText2 = stack2.addText(percent_vaccinated.toString() + "% pop. vaccinée") |
|
stackText2.textColor = Color.dynamic(Color.gray(), Color.gray()) |
|
stackText2.font = Font.regularSystemFont(10); |
|
stackText2.minimumScaleFactor = 1; |
|
|
|
stack2.addSpacer(4); |
|
|
|
const stack3 = view.addStack(); |
|
stack3.layoutVertically(); |
|
stack3.centerAlignContent(); |
|
stack3.spacing = 1; |
|
let stackText3 = stack3.addText("Source : " + (source.toString() == "Estimation" ? "Éstimation" : source.toString().replace("Ministère de la Santé", "Ministère Santé"))) |
|
stackText3.textColor = Color.dynamic(Color.gray(), Color.gray()) |
|
stackText3.font = Font.regularSystemFont(9); |
|
stackText3.minimumScaleFactor = 1; |
|
|
|
stack3.addSpacer(4) |
|
|
|
const stackUpdate = view.addStack(); |
|
stackUpdate.layoutVertically(); |
|
stackUpdate.centerAlignContent(); |
|
stackUpdate.spacing = 1; |
|
let date = new Intl.DateTimeFormat('fr-FR', { month: 'long', day: "numeric" }).format(Date.parse(update)) |
|
let stackUpdateText = // stackUpdate.addText("MàJ : " + date + " à " + update_hour) |
|
stackUpdate.addText("MàJ : " + date) |
|
stackUpdateText.textColor = Color.dynamic(Color.gray(), Color.gray()) |
|
stackUpdateText.font = Font.regularSystemFont(9); |
|
stackUpdateText.minimumScaleFactor = 1; |
|
|
|
|
|
const ctx = new DrawContext(); |
|
const size = new Size(58, 4); |
|
ctx.size = size; |
|
ctx.opaque = false; |
|
ctx.respectScreenScale = true; |
|
|
|
const backgroundColour = Dynamic.color(new Color('#f2f2f2'), new Color('#1c1c1e')) |
|
|
|
return stack; |
|
} |
|
|
|
const extractData = (node, state) => { |
|
|
|
// Quick validation |
|
const fields = [ |
|
'difference_to_the_previous_day', |
|
'vaccinated', |
|
'total', |
|
]; |
|
|
|
if (node == null) { |
|
throw new Error(`missing node${state != null ? ' for state: ' + state : ''}`); |
|
} |
|
|
|
for (const field of fields) { |
|
if (node[field] == null) { |
|
throw new Error(`missing data: ${field}`); |
|
} |
|
} |
|
|
|
return node; |
|
}; |
|
|
|
const drawData = (data) => { |
|
const widget = new ListWidget(); |
|
const padding = 16; |
|
widget.setPadding(padding, padding, padding, padding); |
|
widget.backgroundColor = widgetBackgroundColor; |
|
|
|
drawRegion(widget, '🇫🇷 France', extractData(data)); |
|
|
|
return widget; |
|
} |
|
|
|
// Get images from iCloud or download them once |
|
async function getImage(image) { |
|
let fm = FileManager.local(); |
|
let dir = fm.documentsDirectory(); |
|
let path = fm.joinPath(dir, image); |
|
if (fm.fileExists(path)) { |
|
console.log("Image exist locally") |
|
return fm.readImage(path); |
|
} else { |
|
// Download once |
|
let imageUrl; |
|
switch (image) { |
|
case "vac-logo2.png": |
|
imageUrl = "https://api.socialspill.com/vac-logo.png"; |
|
break; |
|
case "calendar-icon.png": |
|
imageUrl = "https://api.socialspill.com/calendar-icon.png"; |
|
break; |
|
default: |
|
console.log(`Sorry, couldn't find ${image}.`); |
|
} |
|
let req = new Request(imageUrl); |
|
let loadedImage = await req.loadImage(); |
|
console.log("Downloading image...") |
|
fm.writeImage(path, loadedImage); |
|
return loadedImage; |
|
} |
|
} |
|
|
|
const run = async () => { |
|
try { |
|
const url = `${CONFIG.dataURL}?nocache=${(new Date()).getTime()}`; |
|
const data = await fetchJSON(url); |
|
|
|
return drawData(data); |
|
} catch (error) { |
|
return drawError(error.message || 'Gasp! An unknown error!') |
|
} |
|
}; |
|
|
|
if (config.runsInWidget) { |
|
let widget = await run() |
|
Script.setWidget(widget) |
|
Script.complete() |
|
} |
|
else if (CONFIG.debug) { |
|
Script.complete(); |
|
const widget = await run() |
|
await widget.presentSmall() |
|
} |
|
else { |
|
const callback = new CallbackURL(CONFIG.sourceURL) |
|
callback.open() |
|
Script.complete() |
|
} |