Facebook
From Anorexic Matamata, 3 Years ago, written in JavaScript.
Embed
Download Paste or View Raw
Hits: 422
  1. // Added info
  2. let collection = "932809"
  3. let widgetInputRAW = args.widgetParameter
  4. if (widgetInputRAW) {
  5.   try {
  6.     widgetInputRAW.toString()
  7.     if (widgetInputRAW.toString() !== "") {
  8.       collection = widgetInputRAW.toString()
  9.     }
  10.   } catch (e) {
  11.     throw new Error("Please long press the widget and add a parameter.")
  12.   }
  13. }
  14.  
  15. /*
  16.  * SETUP
  17.  * Use this section to set up the widget.
  18.  * ======================================
  19.  */
  20.  
  21. // Get a free API key here: openweathermap.org/appid
  22. const apiKey = ""
  23.  
  24. // Set to true for fixed location, false to update location as you move around
  25. const lockLocation = true
  26.  
  27. // Set to imperial for Fahrenheit, or metric for Celsius
  28. const units = "imperial"
  29.  
  30. // The size of the widget preview in the app.
  31. const widgetPreview = "large"
  32.  
  33. // Set to true for an image background, false for no image.
  34. const imageBackground = true
  35.  
  36. // Set to true and run the script once to update the image manually.
  37. const forceImageUpdate = false
  38.  
  39. /*
  40.  * LAYOUT
  41.  * Decide what elements to show on the widget.
  42.  * ===========================================
  43.  */
  44.  
  45. // Set the width of the column, or set to 0 for an automatic width.
  46.  
  47. // You can add items to the column:
  48. // date, greeting, events, current, future, text("Your text here")
  49. // You can also add a left, center, or right to the list. Everything after it will be aligned that way.
  50.  
  51. // Make sure to always put a comma after each item.
  52.  
  53. const columns = [{
  54.  
  55.   // Settings for the left column.
  56.   width: 0,
  57.   items: [
  58.    
  59.     left,
  60.     date,
  61.     events,
  62.    
  63. end]}, {
  64.  
  65.   // Settings for the right column.
  66.   width: 100,
  67.   items: [
  68.    
  69.     left,
  70.     current,
  71.     space,
  72.     future,
  73.  
  74. end]}]
  75.  
  76. /*
  77.  * FORMATTING
  78.  * Choose how each element is displayed.
  79.  * =====================================
  80.  */  
  81.  
  82. // How many events to show.
  83. const numberOfEvents = 3
  84.  
  85. // Show today's high and low temperatures.
  86. const showHighLow = true
  87.  
  88. // Set the hour (in 24-hour time) to switch to tomorrow's weather. Set to 24 to never show it.
  89. const tomorrowShownAtHour = 20
  90.  
  91. // If set to true, date will become smaller when events are displayed.
  92. const dynamicDateSize = true
  93.  
  94. // If the date is not dynamic, should it be large or small?
  95. const staticDateSize = "large"
  96.  
  97. // Determine the date format for each element. See docs.scriptable.app/dateformatter
  98. const smallDateFormat = "EEEE, MMMM d"
  99. const largeDateLineOne = "EEEE,"
  100. const largeDateLineTwo = "MMMM d"
  101.  
  102. // In this section, set the font, size, and color. Use iosfonts.com to find fonts to use. If you want to use the default iOS font, set the font name to one of the following: ultralight, light, regular, medium, semibold, bold, heavy, black, or italic.
  103. const textFormat = {
  104.  
  105.   // Set the default font and color.
  106.   defaultText: { size: 14, color: "ffffff", font: "regular" },
  107.  
  108.   // Any blank values will use the default.
  109.   smallDate:   { size: 17, color: "", font: "semibold" },
  110.   largeDate1:  { size: 30, color: "", font: "light" },
  111.   largeDate2:  { size: 30, color: "", font: "light" },
  112.  
  113.   greeting:    { size: 30, color: "", font: "semibold" },
  114.   eventTitle:  { size: 14, color: "", font: "semibold" },
  115.   eventTime:   { size: 14, color: "ffffffcc", font: "" },
  116.  
  117.   largeTemp:   { size: 34, color: "", font: "light" },
  118.   smallTemp:   { size: 14, color: "", font: "" },
  119.   tinyTemp:    { size: 12, color: "", font: "" },
  120.  
  121.   customText:  { size: 14, color: "", font: "" }
  122. }
  123.  
  124. /*
  125.  * WIDGET CODE
  126.  * Be more careful editing this section.
  127.  * =====================================
  128.  */
  129.  
  130. // Set up the date and event information.
  131. const currentDate = new Date()
  132. const allEvents = await CalendarEvent.today([])
  133. const futureEvents = enumerateEvents()
  134. const eventsAreVisible = (futureEvents.length > 0) && (numberOfEvents > 0)
  135.  
  136. // Set up the file manager.
  137. const files = FileManager.local()
  138.  
  139. // Set up the location logic.
  140. const locationPath = files.joinPath(files.documentsDirectory(), "weather-cal-location")
  141. var latitude, longitude
  142.  
  143. // If we're locking our location and it's saved already, read from the file.
  144. if (lockLocation && files.fileExists(locationPath)) {
  145.   const locationStr = files.readString(locationPath).split(",")
  146.   latitude = locationStr[0]
  147.   longitude = locationStr[1]
  148.  
  149. // Otherwise, get the location from the system.
  150. } else {
  151.   const location = await Location.current()
  152.   latitude = location.latitude
  153.   longitude = location.longitude
  154.   files.writeString(locationPath, latitude + "," + longitude)
  155. }
  156.  
  157. // Set up the cache.
  158. const cachePath = files.joinPath(files.documentsDirectory(), "weather-cal-cache")
  159. const cacheExists = files.fileExists(cachePath)
  160. const cacheDate = cacheExists ? files.modificationDate(cachePath) : 0
  161. var data
  162.  
  163. // If cache exists and it's been less than 60 seconds since last request, use cached data.
  164. if (cacheExists && (currentDate.getTime() - cacheDate.getTime()) < 60000) {
  165.   const cache = files.readString(cachePath)
  166.   data = JSON.parse(cache)
  167.  
  168. // Otherwise, use the API to get new weather data.
  169. } else {
  170.   const weatherReq = "https://api.openweathermap.org/data/2.5/onecall?lat=" + latitude + "&lon=" + longitude + "&exclude=minutely,alerts&units=" + units + "&lang=en&appid=" + apiKey
  171.   data = await new Request(weatherReq).loadJSON()
  172.   files.writeString(cachePath, JSON.stringify(data))
  173. }
  174.  
  175. // Store the weather values.
  176. const currentTemp = data.current.temp
  177. const currentCondition = data.current.weather[0].id
  178. const todayHigh = data.daily[0].temp.max
  179. const todayLow = data.daily[0].temp.min
  180.  
  181. const nextHourTemp = data.hourly[1].temp
  182. const nextHourCondition = data.hourly[1].weather[0].id
  183.  
  184. const tomorrowHigh = data.daily[1].temp.max
  185. const tomorrowLow = data.daily[1].temp.min
  186. const tomorrowCondition = data.daily[1].weather[0].id
  187.  
  188. // Set up the sunrise/sunset cache.
  189. const sunCachePath = files.joinPath(files.documentsDirectory(), "weather-cal-sun")
  190. const sunCacheExists = files.fileExists(sunCachePath)
  191. const sunCacheDate = sunCacheExists ? files.modificationDate(sunCachePath) : 0
  192. var sunData
  193.  
  194. // If cache exists and it was created today, use cached data.
  195. if (sunCacheExists && sameDay(currentDate, sunCacheDate)) {
  196.   const sunCache = files.readString(sunCachePath)
  197.   sunData = JSON.parse(sunCache)
  198.  
  199. // Otherwise, use the API to get sunrise and sunset times.
  200. } else {
  201.   const sunReq = "https://api.sunrise-sunset.org/json?lat=" + latitude + "&lng=" + longitude + "&formatted=0&date=" + currentDate.getFullYear() + "-" + (currentDate.getMonth()+1) + "-" + currentDate.getDate()
  202.   sunData = await new Request(sunReq).loadJSON()
  203.   files.writeString(sunCachePath, JSON.stringify(sunData))
  204. }
  205.  
  206. // Store the timing values.
  207. const sunrise = new Date(sunData.results.sunrise).getTime()
  208. const sunset = new Date(sunData.results.sunset).getTime()
  209. const utcTime = currentDate.getTime()
  210.  
  211. /*
  212.  * COLUMNS AND PADDING
  213.  * ===================
  214.  */
  215.  
  216. // Set up the widget and the main stack.
  217. let widget = new ListWidget()
  218. widget.setPadding(0, 0, 0, 0)
  219.  
  220. let mainStack = widget.addStack()
  221. mainStack.layoutHorizontally()
  222. mainStack.setPadding(0, 0, 0, 0)
  223.  
  224. // Set up alignment
  225. var currentAlignment = left
  226.  
  227. // Set up our columns.
  228. for (var x = 0; x < columns.length; x++) {
  229.  
  230.   let column = columns[x]
  231.   let columnStack = mainStack.addStack()
  232.   columnStack.layoutVertically()
  233.  
  234.   // Only add padding on the first or last column.
  235.   columnStack.setPadding(0, x == 0 ? 5 : 0, 0, x == columns.length-1 ? 5 : 0)
  236.   columnStack.size = new Size(column.width,0)
  237.  
  238.   // Add the items to the column.
  239.   for (var i = 0; i < column.items.length; i++) {
  240.     column.items[i](columnStack)
  241.   }
  242. }
  243.  
  244. /*
  245.  * BACKGROUND DISPLAY
  246.  * ==================
  247.  */
  248.  
  249. // If it's an image background, display it.
  250. if (imageBackground) {
  251.  
  252.   // Determine if our image exists and when it was saved.
  253.   const path = files.joinPath(files.documentsDirectory(), "weather-cal-image")
  254.   const exists = files.fileExists(path)
  255.   const createdToday = exists ? sameDay(files.modificationDate(path),currentDate) : false
  256.  
  257.   // If it exists and updates aren't being forced, use the cache.
  258.   if (exists && !forceImageUpdate) {
  259.     widget.backgroundImage = files.readImage(path)
  260.  
  261.   // If it's missing, forced to update, or not created today, download it.
  262.   } else if (!exists || forceImageUpdate) {
  263.    
  264.     try {
  265.       let img = await new Request("https://source.unsplash.com/collection/" + collection).loadImage()
  266.       files.writeImage(path, img)
  267.       widget.backgroundImage = img
  268.     } catch {
  269.       widget.backgroundImage = files.readImage(path)
  270.     }
  271.    
  272.   }
  273.    
  274. // If it's not an image background, show the gradient.
  275. } else {
  276.   let gradient = new LinearGradient()
  277.   let gradientSettings = getGradientSettings()
  278.  
  279.   gradient.colors = gradientSettings.color()
  280.   gradient.locations = gradientSettings.position()
  281.  
  282.   widget.backgroundGradient = gradient
  283. }
  284.  
  285. Script.setWidget(widget)
  286. if (widgetPreview == "small") { widget.presentSmall() }
  287. else if (widgetPreview == "medium") { widget.presentMedium() }
  288. else if (widgetPreview == "large") { widget.presentLarge() }
  289. Script.complete()
  290.  
  291. /*
  292.  * IMAGES AND FORMATTING
  293.  * =====================
  294.  */
  295.  
  296. // Get the gradient settings for each time of day.
  297. function getGradientSettings() {
  298.  
  299.   let gradient = {
  300.                 "dawn": {
  301.                         "color": function() { return [new Color("142C52"), new Color("1B416F"), new Color("62668B")] },
  302.                         "position": function() { return [0, 0.5, 1] }
  303.                 },
  304.        
  305.                 "sunrise": {
  306.                         "color": function() { return [new Color("274875"), new Color("766f8d"), new Color("f0b35e")] },
  307.                         "position": function() { return [0, 0.8, 1.5] }
  308.                 },
  309.        
  310.                 "midday": {
  311.                         "color": function() { return [new Color("3a8cc1"), new Color("90c0df")] },
  312.                         "position": function() { return [0, 1] }
  313.                 },
  314.        
  315.                 "noon": {
  316.                         "color": function() { return [new Color("b2d0e1"), new Color("80B5DB"), new Color("3a8cc1")] },
  317.                         "position": function() { return [-0.2, 0.2, 1.5] }
  318.                 },
  319.        
  320.                 "sunset": {
  321.                         "color": function() { return [new Color("32327A"), new Color("662E55"), new Color("7C2F43")] },
  322.                         "position": function() { return [0.1, 0.9, 1.2] }
  323.                 },
  324.        
  325.                 "twilight": {
  326.                         "color": function() { return [new Color("021033"), new Color("16296b"), new Color("414791")] },
  327.                         "position": function() { return [0, 0.5, 1] }
  328.                 },
  329.        
  330.                 "night": {
  331.                         "color": function() { return [new Color("16296b"), new Color("021033"), new Color("021033"), new Color("113245")] },
  332.                         "position": function() { return [-0.5, 0.2, 0.5, 1] }
  333.                 }
  334.         }
  335.  
  336.   function closeTo(time,mins) {
  337.     return Math.abs(utcTime - time) < (mins * 60000)
  338.   }
  339.  
  340.   // Use sunrise or sunset if we're within 30min of it.
  341.         if (closeTo(sunrise,15)) { return gradient.sunrise }
  342.         if (closeTo(sunset,15)) { return gradient.sunset }
  343.  
  344.         // In the 30min before/after, use dawn/twilight.
  345.         if (closeTo(sunrise,45) && utcTime < sunrise) { return gradient.dawn }
  346.         if (closeTo(sunset,45) && utcTime > sunset) { return gradient.twilight }
  347.  
  348.     // Otherwise, if it's night, return night.
  349.         if (isNight(currentDate)) { return gradient.night }
  350.  
  351.         // If it's around noon, the sun is high in the sky.
  352.         if (currentDate.getHours() == 12) { return gradient.noon }
  353.  
  354.         // Otherwise, return the "typical" theme.
  355.         return gradient.midday
  356. }
  357.  
  358. // Provide a symbol based on the condition.
  359. function provideSymbol(cond,night) {
  360.  
  361.   // Define our symbol equivalencies.
  362.   let symbols = {
  363.  
  364.     // Thunderstorm
  365.     "2": function() { return "cloud.bolt.rain.fill" },
  366.    
  367.     // Drizzle
  368.     "3": function() { return "cloud.drizzle.fill" },
  369.    
  370.     // Rain
  371.     "5": function() { return (cond == 511) ? "cloud.sleet.fill" : "cloud.rain.fill" },
  372.    
  373.     // Snow
  374.     "6": function() { return (cond >= 611 && cond <= 613) ? "cloud.snow.fill" : "snow" },
  375.    
  376.     // Atmosphere
  377.     "7": function() {
  378.       if (cond == 781) { return "tornado" }
  379.       if (cond == 701 || cond == 741) { return "cloud.fog.fill" }
  380.       return night ? "cloud.fog.fill" : "sun.haze.fill"
  381.     },
  382.    
  383.     // Clear and clouds
  384.     "8": function() {
  385.       if (cond == 800 || cond == 801) { return night ? "moon.stars.fill" : "sun.max.fill" }
  386.       if (cond == 802 || cond == 803) { return night ? "cloud.moon.fill" : "cloud.sun.fill" }
  387.       return "cloud.fill"
  388.     }
  389.   }
  390.  
  391.   // Find out the first digit.
  392.   let conditionDigit = Math.floor(cond / 100)
  393.  
  394.   // Get the symbol.
  395.   return SFSymbol.named(symbols[conditionDigit]()).image
  396. }
  397.  
  398. // Provide a font based on the input.
  399. function provideFont(fontName, fontSize) {
  400.   const fontGenerator = {
  401.     "ultralight": function() { return Font.ultraLightSystemFont(fontSize) },
  402.     "light": function() { return Font.lightSystemFont(fontSize) },
  403.     "regular": function() { return Font.regularSystemFont(fontSize) },
  404.     "medium": function() { return Font.mediumSystemFont(fontSize) },
  405.     "semibold": function() { return Font.semiboldSystemFont(fontSize) },
  406.     "bold": function() { return Font.boldSystemFont(fontSize) },
  407.     "heavy": function() { return Font.heavySystemFont(fontSize) },
  408.     "black": function() { return Font.blackSystemFont(fontSize) },
  409.     "italic": function() { return Font.italicSystemFont(fontSize) }
  410.   }
  411.  
  412.   const systemFont = fontGenerator[fontName]
  413.   if (systemFont) { return systemFont() }
  414.   return new Font(fontName, fontSize)
  415. }
  416.  
  417. // Format text based on the settings.
  418. function formatText(textItem, format) {
  419.   const textFont = format.font || textFormat.defaultText.font
  420.   const textSize = format.size || textFormat.defaultText.size
  421.   const textColor = format.color || textFormat.defaultText.color
  422.  
  423.   textItem.font = provideFont(textFont, textSize)
  424.   textItem.textColor = new Color(textColor)
  425. }
  426.  
  427. /*
  428.  * HELPER FUNCTIONS
  429.  * ================
  430.  */
  431.  
  432. // Find future events that aren't all day and aren't canceled
  433. function enumerateEvents() {
  434.   let futureEvents = []
  435.   for (const event of allEvents) {
  436.     if (event.startDate.getTime() > currentDate.getTime() && !event.isAllDay && !event.title.startsWith("Canceled:")) {
  437.       futureEvents.push(event)
  438.     }
  439.   }
  440.   return futureEvents
  441. }
  442.  
  443. // Determines if the provided date is at night.
  444. function isNight(dateInput) {
  445.   const timeValue = dateInput.getTime()
  446.   return (timeValue < sunrise) || (timeValue > sunset)
  447. }
  448.  
  449. // Determines if two dates occur on the same day
  450. function sameDay(d1, d2) {
  451.   return d1.getFullYear() === d2.getFullYear() &&
  452.     d1.getMonth() === d2.getMonth() &&
  453.     d1.getDate() === d2.getDate()
  454. }
  455.  
  456. /*
  457.  * DRAWING FUNCTIONS
  458.  * =================
  459.  */
  460.  
  461. // Draw the vertical line in the tomorrow view.
  462. function drawVerticalLine() {
  463.  
  464.   const w = 2
  465.   const h = 20
  466.  
  467.   let draw = new DrawContext()
  468.   draw.opaque = false
  469.   draw.respectScreenScale = true
  470.   draw.size = new Size(w,h)
  471.  
  472.   let barPath = new Path()
  473.   const barHeight = h
  474.   barPath.addRoundedRect(new Rect(0, 0, w, h), w/2, w/2)
  475.   draw.addPath(barPath)
  476.   draw.setFillColor(new Color("ffffff", 0.5))
  477.   draw.fillPath()
  478.  
  479.   return draw.getImage()
  480. }
  481.  
  482. // Draw the temp bar.
  483. function drawTempBar() {
  484.  
  485.   // Set the size of the temp bar.
  486.   const tempBarWidth = 200
  487.   const tempBarHeight = 20
  488.  
  489.   // Calculate the current percentage of the high-low range.
  490.   let percent = (currentTemp - todayLow) / (todayHigh - todayLow)
  491.  
  492.   // If we're out of bounds, clip it.
  493.   if (percent < 0) {
  494.     percent = 0
  495.   } else if (percent > 1) {
  496.     percent = 1
  497.   }
  498.  
  499.   // Determine the scaled x-value for the current temp.
  500.   const currPosition = (tempBarWidth - tempBarHeight) * percent
  501.  
  502.   // Start our draw context.
  503.   let draw = new DrawContext()
  504.   draw.opaque = false
  505.   draw.respectScreenScale = true
  506.   draw.size = new Size(tempBarWidth, tempBarHeight)
  507.  
  508.   // Make the path for the bar.
  509.   let barPath = new Path()
  510.   const barHeight = tempBarHeight - 10
  511.   barPath.addRoundedRect(new Rect(0, 5, tempBarWidth, barHeight), barHeight / 2, barHeight / 2)
  512.   draw.addPath(barPath)
  513.   draw.setFillColor(new Color("ffffff", 0.5))
  514.   draw.fillPath()
  515.  
  516.   // Make the path for the current temp indicator.
  517.   let currPath = new Path()
  518.   currPath.addEllipse(new Rect(currPosition, 0, tempBarHeight, tempBarHeight))
  519.   draw.addPath(currPath)
  520.   draw.setFillColor(new Color("ffffff", 1))
  521.   draw.fillPath()
  522.  
  523.   return draw.getImage()
  524. }
  525.  
  526. /*
  527.  * ELEMENTS AND ALIGNMENT
  528.  * ======================
  529.  */
  530.  
  531. // Create an aligned stack to add content to.
  532. function align(column) {
  533.  
  534.   // Add the containing stack to the column.
  535.   let alignmentStack = column.addStack()
  536.   alignmentStack.layoutHorizontally()
  537.  
  538.   // Get the correct stack from the alignment function.
  539.   let returnStack = currentAlignment(alignmentStack)
  540.   returnStack.layoutVertically()
  541.   return returnStack
  542. }
  543.  
  544. // Create a right-aligned stack.
  545. function alignRight(alignmentStack) {
  546.   alignmentStack.addSpacer()
  547.   let returnStack = alignmentStack.addStack()
  548.   return returnStack
  549. }
  550.  
  551. // Create a left-aligned stack.
  552. function alignLeft(alignmentStack) {
  553.   let returnStack = alignmentStack.addStack()
  554.   alignmentStack.addSpacer()
  555.   return returnStack
  556. }
  557.  
  558. // Create a center-aligned stack.
  559. function alignCenter(alignmentStack) {
  560.   alignmentStack.addSpacer()
  561.   let returnStack = alignmentStack.addStack()
  562.   alignmentStack.addSpacer()
  563.   return returnStack
  564. }
  565.  
  566. // Display the date on the widget.
  567. function date(column) {
  568.  
  569.   // Set up the date formatter.
  570.   let df = new DateFormatter()
  571.  
  572.   // Show small if it's hard coded, or if it's dynamic and events are visible.
  573.   if ((dynamicDateSize && eventsAreVisible) || staticDateSize == "small") {
  574.     let dateStack = align(column)
  575.     dateStack.setPadding(10, 10, 10, 10)
  576.  
  577.     df.dateFormat = smallDateFormat
  578.     let dateText = dateStack.addText(df.string(currentDate))
  579.     formatText(dateText, textFormat.smallDate)
  580.    
  581.   // Otherwise, show the large date.
  582.   } else {
  583.     let dateOneStack = align(column)
  584.     df.dateFormat = largeDateLineOne
  585.     let dateOne = dateOneStack.addText(df.string(currentDate))
  586.     formatText(dateOne, textFormat.largeDate1)
  587.     dateOneStack.setPadding(10, 10, 0, 10)
  588.    
  589.     let dateTwoStack = align(column)
  590.     df.dateFormat = largeDateLineTwo
  591.     let dateTwo = dateTwoStack.addText(df.string(currentDate))
  592.     formatText(dateTwo, textFormat.largeDate2)
  593.     dateTwoStack.setPadding(0, 10, 10, 10)
  594.   }
  595. }
  596.  
  597. function greeting(column) {
  598.  
  599.   // This function makes a greeting based on the time of day.
  600.   function makeGreeting() {
  601.     const hour = currentDate.getHours()
  602.     if (hour    < 5)  { return "Good night." }
  603.     if (hour    < 12) { return "Good morning." }
  604.     if (hour-12 < 5)  { return "Good afternoon." }
  605.     if (hour-12 < 10) { return "Good evening." }
  606.     return "Good night."
  607.   }
  608.  
  609.   // Set up the greeting.
  610.   let greetingStack = align(column)
  611.   let greeting = greetingStack.addText(makeGreeting())
  612.   formatText(greeting, textFormat.greeting)
  613.   greetingStack.setPadding(10, 10, 10, 10)
  614. }
  615.  
  616. // Display events on the widget.
  617. function events(column) {
  618.  
  619.   // If no events should be displayed, just exit this function
  620.   if (numberOfEvents == 0) { return }
  621.  
  622.   for (let i = 0; i < numberOfEvents; i++) {
  623.     // Determine if the event exists, otherwise end.
  624.     const event = futureEvents[i]
  625.     if (!event) { break }
  626.    
  627.     const titleStack = align(column)
  628.     const title = titleStack.addText(event.title)
  629.     formatText(title, textFormat.eventTitle)
  630.     titleStack.setPadding(i==0 ? 10 : 5, 10, 0, 10)
  631.    
  632.     // If there are too many events, limit the line height.
  633.     if (futureEvents.length >= 3) { title.lineLimit = 1 }
  634.  
  635.     // If it's an all-day event, we don't need a time.
  636.     if (event.isAllDay) { return }
  637.  
  638.     // Format the time information.
  639.     let df = new DateFormatter()
  640.     df.useNoDateStyle()
  641.     df.useShortTimeStyle()
  642.  
  643.     const timeText = df.string(event.startDate)
  644.     const timeStack = align(column)
  645.     const time = timeStack.addText(timeText)
  646.     formatText(time, textFormat.eventTime)
  647.     timeStack.setPadding(0, 10, i==numberOfEvents-1 ? 10 : 0, 10)
  648.   }
  649. }
  650.  
  651. // Display the current weather.
  652. function current(column) {
  653.  
  654.   // Show the current condition symbol.
  655.   let mainConditionStack = align(column)
  656.   let mainCondition = mainConditionStack.addImage(provideSymbol(currentCondition,isNight(currentDate)))
  657.   mainCondition.imageSize = new Size(22,22)
  658.   mainConditionStack.setPadding(10, 10, 0, 10)
  659.  
  660.   // Show the current temperature.
  661.   let tempStack = align(column)
  662.   let temp = tempStack.addText(Math.round(currentTemp) + "°")
  663.   tempStack.setPadding(0, 10, 0, 10)
  664.   formatText(temp, textFormat.largeTemp)
  665.  
  666.   // If we're not showing the high and low, end it here.
  667.   if (!showHighLow) { return }
  668.  
  669.   // Show the temp bar and high/low values.
  670.   let tempBarStack = align(column)
  671.   tempBarStack.layoutVertically()
  672.   tempBarStack.setPadding(0, 10, 5, 10)
  673.  
  674.   let tempBar = drawTempBar()
  675.   let tempBarImage = tempBarStack.addImage(tempBar)
  676.   tempBarImage.size = new Size(50,0)
  677.  
  678.   tempBarStack.addSpacer(1)
  679.  
  680.   let highLowStack = tempBarStack.addStack()
  681.   highLowStack.layoutHorizontally()
  682.  
  683.   let mainLow = highLowStack.addText(Math.round(todayLow).toString())
  684.   highLowStack.addSpacer()
  685.   let mainHigh = highLowStack.addText(Math.round(todayHigh).toString())
  686.  
  687.   formatText(mainHigh, textFormat.tinyTemp)
  688.   formatText(mainLow, textFormat.tinyTemp)
  689.  
  690.   tempBarStack.size = new Size(70,30)
  691. }
  692.  
  693. // Display upcoming weather.
  694. function future(column) {
  695.  
  696.   // Determine if we should show the next hour.
  697.   const showNextHour = (currentDate.getHours() < tomorrowShownAtHour)
  698.  
  699.   // Set the label value.
  700.   const subLabelText = showNextHour ? "Next hour" : "Tomorrow"
  701.   let subLabelStack = align(column)
  702.   let subLabel = subLabelStack.addText(subLabelText)
  703.   formatText(subLabel, textFormat.smallTemp)
  704.   subLabelStack.setPadding(0, 10, 2, 10)
  705.  
  706.   // Set up the sub condition stack.
  707.   let subConditionStack = align(column)
  708.   subConditionStack.layoutHorizontally()
  709.   subConditionStack.centerAlignContent()
  710.   subConditionStack.setPadding(0, 10, 10, 10)
  711.  
  712.   // Determine what condition to show.
  713.   var nightCondition
  714.   if (showNextHour) {
  715.     const addHour = currentDate.getTime() + (60*60*1000)
  716.     const newDate = new Date(addHour)
  717.     nightCondition = isNight(newDate)
  718.   } else {
  719.     nightCondition = false
  720.   }
  721.  
  722.   let subCondition = subConditionStack.addImage(provideSymbol(showNextHour ? nextHourCondition : tomorrowCondition,nightCondition))
  723.   const subConditionSize = showNextHour ? 14 : 18
  724.   subCondition.imageSize = new Size(subConditionSize, subConditionSize)
  725.   subConditionStack.addSpacer(5)
  726.  
  727.   // The next part of the display changes significantly for next hour vs tomorrow.
  728.   if (showNextHour) {
  729.     let subTemp = subConditionStack.addText(Math.round(nextHourTemp) + "°")
  730.     formatText(subTemp, textFormat.smallTemp)
  731.    
  732.   } else {
  733.     let tomorrowLine = subConditionStack.addImage(drawVerticalLine())
  734.     tomorrowLine.imageSize = new Size(3,28)
  735.     subConditionStack.addSpacer(5)
  736.     let tomorrowStack = subConditionStack.addStack()
  737.     tomorrowStack.layoutVertically()
  738.    
  739.     let tomorrowHighText = tomorrowStack.addText(Math.round(tomorrowHigh) + "")
  740.     tomorrowStack.addSpacer(4)
  741.     let tomorrowLowText = tomorrowStack.addText(Math.round(tomorrowLow) + "")
  742.    
  743.     formatText(tomorrowHighText, textFormat.tinyTemp)
  744.     formatText(tomorrowLowText, textFormat.tinyTemp)
  745.   }
  746. }
  747.  
  748. // Return a text-creation function.
  749. function text(inputText) {
  750.  
  751.   function displayText(column) {
  752.     let textStack = align(column)
  753.     textStack.setPadding(10, 10, 10, 10)
  754.    
  755.     let textDisplay = textStack.addText(inputText)
  756.     formatText(textDisplay, textFormat.customText)
  757.   }
  758.   return displayText
  759. }
  760.  
  761. /*
  762.  * MINI FUNCTIONS
  763.  * ==============
  764.  */
  765.  
  766. // This function adds a space.
  767. function space(column) { column.addSpacer() }
  768.  
  769. // Change the current alignment to right.
  770. function right(x) { currentAlignment = alignRight }
  771.  
  772. // Change the current alignment to left.
  773. function left(x) { currentAlignment = alignLeft }
  774.  
  775. // Change the current alignment to center.
  776. function center(x) { currentAlignment = alignCenter }
  777.  
  778. // This function doesn't need to do anything.
  779. function end(x) { return }
  780.  
  781. Script.complete()