Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Weather App with Brand Fetching Features #208

Open
wants to merge 15 commits into
base: master
Choose a base branch
from

Conversation

salmanfarisvp
Copy link
Collaborator

@salmanfarisvp salmanfarisvp commented Dec 20, 2024

User description

newWeatherApp

  • Applied new UI
  • Update the colours as per the brand's primary and secondary colours -
  • Update the in-app logo per the brand logo fetched -
  • Override time zone -
  • Override locale setting for region languages in dates -
  • Tracks/capture errors using Sentry
  • Added Signal Ready.
  • Added Theme option selection setting -
  • Support Standard Screen Sizes - As per [Supported Screen Resolutions] (https://github.com/Screenly/Playground/blob/master/docs/resolutions.md) -
  • ToDo
  • QA is done by at least two or more members -> Ongoing
  • Support locale for weather pallet time.

PR Type

Enhancement, Bug fix, Documentation


Description

  • Implemented a new weather app UI with grid layout.

  • Added locale and timezone override settings.

  • Integrated Sentry for error tracking and logging.

  • Enhanced responsiveness with extensive CSS media queries.


Changes walkthrough 📝

Relevant files
Enhancement
4 files
styles.css
Added responsive grid-based layout and styling.                   
+723/-0 
common.css
Defined global styles and theme variables.                             
+373/-0 
main.js
Added weather API integration and locale/timezone handling.
+191/-19
index.html
Updated HTML structure for new weather app UI.                     
+75/-112
Configuration changes
2 files
screenly_qc.yml
Added new settings for locale, timezone, and Sentry DSN. 
+18/-1   
screenly.yml
Updated settings with locale, timezone, and Sentry DSN options.
+18/-1   
Additional files
8 files
.ignore +0/-1     
country-locale-map.1.18.5.min.js +2/-0     
icons.js +22/-25 
moment-timezone-with-data.min.js +1/-0     
offline-geocode-city-1.0.2.min.js +3/-0     
tz.min.js +5/-0     
background.css +0/-67   
main.css +0/-813 

💡 PR-Agent usage: Comment /help "your question" on any pull request to receive relevant information

@salmanfarisvp salmanfarisvp changed the title New Simple Message App with Brand Fetching Features New Weather App with Brand Fetching Features Dec 20, 2024
@salmanfarisvp salmanfarisvp marked this pull request as ready for review January 22, 2025 17:37
Copy link

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 5 🔵🔵🔵🔵🔵
🧪 No relevant tests
🔒 Security concerns

Sensitive information exposure:
The sentry_dsn setting is marked as a secret, but there is no validation or masking mechanism in place to ensure it is securely handled. Additionally, the OpenWeatherMap API key is exposed in the settings, which could lead to unauthorized usage if not properly secured.

⚡ Recommended focus areas for review

Possible Issue

The fetchImage function in main.js does not handle cases where the imgElement is null or undefined, which could cause runtime errors if the DOM element is missing.

async function fetchImage (fileUrl) {
  try {
    const response = await fetch(fileUrl)
    if (!response.ok) {
      throw new Error(`Failed to fetch image from ${fileUrl}, status: ${response.status}`)
    }

    const blob = await response.blob()
    const buffer = await blob.arrayBuffer()
    const uintArray = new Uint8Array(buffer)

    // Get the first 4 bytes for magic number detection
    const hex = Array.from(uintArray.slice(0, 4))
      .map(b => b.toString(16).padStart(2, '0'))
      .join('').toUpperCase()

    // Convert the first few bytes to ASCII for text-based formats like SVG
    const ascii = String.fromCharCode.apply(null, uintArray.slice(0, 100)) // Check first 100 chars for XML/SVG tags

    // Determine file type based on MIME type, magic number, or ASCII text
    if (ascii.startsWith('<?xml') || ascii.startsWith('<svg')) {
      // Convert to Base64 and display if SVG
      const svgReader = new FileReader()
      svgReader.readAsText(blob)
      svgReader.onloadend = function () {
        const base64 = btoa(unescape(encodeURIComponent(svgReader.result)))
        imgElement.src = 'data:image/svg+xml;base64,' + base64
      }
    } else if (hex === '89504E47' || hex.startsWith('FFD8FF')) {
      // Checking PNG or JPEG/JPG magic number
      imgElement.src = fileUrl
    } else {
      throw new Error('Unknown image type')
    }
  } catch (error) {
    console.error('Error fetching image:', error)
  }
}

// First, try to fetch the image using the CORS proxy URL
try {
  await fetchImage(corsUrl)
} catch (error) {
  // If CORS fails, try the fallback URL
  try {
    await fetchImage(fallbackUrl)
  } catch (fallbackError) {
    // If fallback fails, use the default logo
    imgElement.src = defaultLogo
Performance Concern

The initDateTime function in main.js fetches timezone and locale data asynchronously but does not implement caching or throttling, which could lead to redundant API calls and performance issues.

const initDateTime = async () => {
  const timezone = await getTimezone()
  const locale = await getLocale()
  const momentObject = moment().tz(timezone)

  if (locale) {
    momentObject.locale(locale) // Set the locale if available
  }

  const dayOfMonth = momentObject.format('D') // Get the day of the month

  // Check if the locale prefers a 24-hour format by checking 'LT'
  const is24HourFormat = moment.localeData(locale).longDateFormat('LT').includes('H')
  // Format time based on the locale's preference
  const formattedTime = is24HourFormat
    ? momentObject.format('HH:mm') // 24-hour format
    : momentObject.format('hh:mm') // 12-hour format

  // Handle AM/PM for 12-hour format
  const periodElement = document.querySelector('.time-ampm')
  if (is24HourFormat) {
    periodElement.innerText = '' // Clear AM/PM value in 24-hour format
    periodElement.style.display = 'none' // Optionally hide the element
  } else {
    const period = momentObject.format('A') // Get AM/PM for 12-hour format
    periodElement.innerText = period
    periodElement.style.display = 'inline' // Ensure it's visible
  }

  // Set time in card
  document.querySelector('.time-text-hour').innerText = formattedTime.split(':')[0]
  document.querySelector('.time-text-minutes').innerText = formattedTime.split(':')[1]
  // document.querySelector('.time-text-separator').innerText = ':'

  document.querySelector('.date-text').innerText = momentObject.format('ddd').toUpperCase()
  document.querySelector('.date-number').innerText = dayOfMonth // Set the inner text to the numeric day of the month
}
initDateTime() // Initialize the app
Accessibility Issue

The CSS styles in styles.css do not include sufficient contrast checks for text colors (e.g., --theme-color-secondary on --theme-color-background), which may fail accessibility standards for readability.

.main-weather-card, .time-date-card, .weather-card, .info-card {
  border-radius: 1rem;
  gap: 1rem
}

/* Main Weather Card */

Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Score
Security
Sanitize base64-encoded SVG strings

Ensure that all base64-encoded SVG strings are properly sanitized and validated to
prevent potential injection attacks or rendering issues.

edge-apps/weather/static/js/icons.js [3]

-chancesleet: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTM4LjYyMDIgMjMuMjM4N...
+chancesleet: sanitizeSVG('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTM4LjYyMDIgMjMuMjM4N...');
Suggestion importance[1-10]: 9

Why: The suggestion addresses a critical security concern by proposing the sanitization of base64-encoded SVG strings, which could prevent potential injection attacks or rendering issues. This is highly relevant and impactful for ensuring the safety and integrity of the application.

9
Possible issue
Add null check for DOM element

Ensure that the periodElement is checked for null or undefined before attempting to
set its innerText or style.display properties to avoid runtime errors if the element
is not found in the DOM.

edge-apps/weather/static/js/main.js [405-407]

-periodElement.innerText = '' // Clear AM/PM value in 24-hour format
-periodElement.style.display = 'none' // Optionally hide the element
+if (periodElement) {
+  periodElement.innerText = '' // Clear AM/PM value in 24-hour format
+  periodElement.style.display = 'none' // Optionally hide the element
+}
Suggestion importance[1-10]: 9

Why: Adding a null check for the periodElement ensures that the code does not throw runtime errors if the element is not found in the DOM. This is a critical improvement for robustness and error prevention.

9
Validate logo path before URL construction

Validate the screenly.settings.screenly_logo_dark value before constructing the
corsUrl and fallbackUrl to ensure it is not null or undefined, preventing invalid
URL construction.

edge-apps/weather/static/js/main.js [443-444]

-const corsUrl = screenly.cors_proxy_url + '/' + screenly.settings.screenly_logo_dark
-const fallbackUrl = screenly.settings.screenly_logo_dark
+const logoPath = screenly.settings.screenly_logo_dark || '';
+const corsUrl = screenly.cors_proxy_url + '/' + logoPath;
+const fallbackUrl = logoPath;
Suggestion importance[1-10]: 7

Why: Validating the screenly.settings.screenly_logo_dark value before constructing URLs prevents potential issues with invalid or undefined paths, improving the robustness of the image fetching logic.

7
General
Use relative units for font size

Ensure the html font size is set using a relative unit like em or % instead of px to
improve accessibility and responsiveness.

edge-apps/weather/static/styles/common.css [15-16]

 html {
-  font-size: 14px;
+  font-size: 87.5%;
 }
Suggestion importance[1-10]: 8

Why: Using relative units like % or em for font size improves accessibility and responsiveness, making the design more adaptable to user preferences and device settings.

8
Add error handling for initialization

Add error handling for the initDateTime function to catch and log any potential
errors during asynchronous operations like fetching timezone or locale.

edge-apps/weather/static/js/main.js [422]

-initDateTime() // Initialize the app
+try {
+  await initDateTime() // Initialize the app
+} catch (error) {
+  console.error('Error initializing date and time:', error)
+}
Suggestion importance[1-10]: 8

Why: Adding error handling to the initDateTime function improves the reliability of the application by ensuring that any issues during initialization are logged and do not cause the application to crash.

8
Add fallback for default locale

Add a fallback mechanism for navigator.language in case it is undefined, ensuring
the application does not break in environments where this property is unavailable.

edge-apps/weather/static/js/main.js [351-353]

 const defaultLocale = navigator?.languages?.length
   ? navigator.languages[0]
-  : navigator.language
+  : (navigator.language || 'en')
Suggestion importance[1-10]: 8

Why: Adding a fallback for navigator.language ensures the application remains functional in environments where this property is unavailable, enhancing compatibility and reliability.

8

Copy link
Contributor

@nicomiguelino nicomiguelino left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@salmanfarisvp

@salmanfarisvp
Copy link
Collaborator Author

@nicomiguelino, given that some weather icons lack the -night variant, should we clarify expectations here?

For example, when it snows at night, the system tries to show this with snow-night.svg, but this icon is absent in both the old and new app icon lists, resulting in the snow icon failing to display correctly.

Same for

  • chancesleet.svg
  • cloudy.svg
  • drizzle.svg
  • fewdrops.svg
  • fog.svg
  • haze.svg
  • snow.svg
  • windy.svg
  return {
    icon: isNight ? `${icon}-night` : icon,
    bg: isNight && hasNightBg ? `${bg}-night` : bg
  }

And we can do something like

return {
  icon: isNight && hasNightPair(icon) ? `${icon}-night` : icon,
  bg: isNight && hasNightBg ? `${bg}-night` : bg
};

// Helper function to check if an icon has a night pair
function hasNightPair(icon) {
  const noNightPairIcons = [
    'chancesleet',
    'cloudy',
    'drizzle',
    'fewdrops',
    'fog',
    'haze',
    'snow',
    'windy'
  ];
  return !noNightPairIcons.includes(icon);
}

@salmanfarisvp
Copy link
Collaborator Author

Added helper function - 98efd72

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants