@vixoniccom/aqi 0.0.1 → 0.0.2-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,46 @@
1
+ /*!
2
+ localForage -- Offline Storage, Improved
3
+ Version 1.10.0
4
+ https://localforage.github.io/localForage
5
+ (c) 2013-2017 Mozilla, Apache License 2.0
6
+ */
7
+
8
+ /**
9
+ * @license React
10
+ * react-dom.production.min.js
11
+ *
12
+ * Copyright (c) Facebook, Inc. and its affiliates.
13
+ *
14
+ * This source code is licensed under the MIT license found in the
15
+ * LICENSE file in the root directory of this source tree.
16
+ */
17
+
18
+ /**
19
+ * @license React
20
+ * react-jsx-runtime.production.min.js
21
+ *
22
+ * Copyright (c) Facebook, Inc. and its affiliates.
23
+ *
24
+ * This source code is licensed under the MIT license found in the
25
+ * LICENSE file in the root directory of this source tree.
26
+ */
27
+
28
+ /**
29
+ * @license React
30
+ * react.production.min.js
31
+ *
32
+ * Copyright (c) Facebook, Inc. and its affiliates.
33
+ *
34
+ * This source code is licensed under the MIT license found in the
35
+ * LICENSE file in the root directory of this source tree.
36
+ */
37
+
38
+ /**
39
+ * @license React
40
+ * scheduler.production.min.js
41
+ *
42
+ * Copyright (c) Facebook, Inc. and its affiliates.
43
+ *
44
+ * This source code is licensed under the MIT license found in the
45
+ * LICENSE file in the root directory of this source tree.
46
+ */
Binary file
@@ -0,0 +1,36 @@
1
+ {
2
+ "parameters": {
3
+ "backgroundImage": {
4
+ "id": "earthquakes",
5
+ "filename": "Earthquakes.png",
6
+ "__isAsset": true
7
+ },
8
+ "cityInput": "Santiago",
9
+ "topSeparationCard": 200,
10
+ "leftSeparationCard": 600,
11
+ "cardWidth": 700,
12
+ "cardHeight": 600,
13
+ "cardGap": 2,
14
+ "aqiFormat": {
15
+ "fontSize": 18,
16
+ "fontColor": "#000000",
17
+ "alignment": {
18
+ "horizontal": "center"
19
+ }
20
+ },
21
+ "stationFormat": {
22
+ "fontSize": 8,
23
+ "fontColor": "#000000",
24
+ "alignment": {
25
+ "horizontal": "center"
26
+ }
27
+ },
28
+ "qualityFormat": {
29
+ "fontSize": 4,
30
+ "fontColor": "#000000",
31
+ "alignment": {
32
+ "horizontal": "center"
33
+ }
34
+ }
35
+ }
36
+ }
package/build.zip CHANGED
Binary file
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "author": {
9
9
  "name": "Daniel Alvayay"
10
10
  },
11
- "version": "0.0.1",
11
+ "version": "0.0.2-dev.1",
12
12
  "scripts": {
13
13
  "prepublishOnly": "vixonic-module-packager --mode=build",
14
14
  "watch": "vixonic-module-packager --mode=watch",
@@ -22,14 +22,14 @@
22
22
  "dependencies": {
23
23
  "axios": "^1.6.0",
24
24
  "localforage": "^1.10.0",
25
- "react": "^17.0.2",
26
- "react-dom": "^17.0.2"
25
+ "react": "^18.3.1",
26
+ "react-dom": "^18.3.1"
27
27
  },
28
28
  "devDependencies": {
29
- "@types/react": "^17.0.35",
30
- "@types/react-dom": "^17.0.11",
31
- "@vixoniccom/module-packager": "^2.10.0",
32
- "@vixoniccom/modules": "^2.19.0",
33
- "standard-version": "^9.3.1"
29
+ "@types/react": "^18.3.23",
30
+ "@types/react-dom": "^18.3.7",
31
+ "@vixoniccom/module-packager": "^2.13.0-dev.1",
32
+ "@vixoniccom/modules": "^2.20.5-dev.1",
33
+ "standard-version": "^9.5.0"
34
34
  }
35
35
  }
@@ -0,0 +1 @@
1
+ sonar.projectKey=Vixonic_store-air-quality-index_de6b8e55-8f87-4d53-b9d5-7e514a2702d9
package/src/App.tsx CHANGED
@@ -1,11 +1,9 @@
1
- import axios from 'axios'
2
- import localforage from 'localforage'
3
1
  import React, { useEffect, useState } from 'react'
4
2
  import { Card } from './components/Card'
5
3
  import { FontLoader } from './components/FontLoader'
6
4
  import { FormattedText } from './components/FormattedText'
7
- import { AirQuality, AqiData, StorageData } from './types'
8
- import { API_RESPONSE_STATUS, TOKEN } from './utils'
5
+ import { AqiData } from './types'
6
+ import { getData } from './services'
9
7
 
10
8
  interface Props {
11
9
  data: VixonicData
@@ -17,67 +15,28 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
17
15
  const backgroundImageState = parameters.backgroundImage ? `url('${downloadsPath}/${parameters.backgroundImage.filename}')` : ''
18
16
  const [formattedData, setFormattedData] = useState<AqiData>()
19
17
  const cityInput = parameters?.cityInput || "santiago"
18
+ const msj0 = parameters?.msj0 || 'No hay datos para mostrar'
20
19
  const updateTime = 600000
21
20
 
22
- const refetchData = (referenceDate: Date): boolean => {
23
- const now = new Date()
24
-
25
- const sameYear = referenceDate.getFullYear() === now.getFullYear()
26
- const sameMonth = referenceDate.getMonth() === now.getMonth()
27
- const sameDay = referenceDate.getDate() === now.getDate()
28
-
29
- if (sameYear && sameMonth && sameDay) {
30
- const differenceMinutes = Math.abs(now.getTime() - referenceDate.getTime())
31
- return differenceMinutes >= updateTime
32
- }
33
- return true
34
- }
35
-
36
- const requestData = async (): Promise<AirQuality | null> => {
37
- const URL = `https://api.waqi.info/feed/${cityInput}/?token=${TOKEN}`
38
- try {
39
- const response = await axios.get<AirQuality>(URL)
40
- if (response?.status === 200) {
41
- return response.data
42
- }
43
- return null
44
- } catch (error) {
45
- return null
46
- }
47
- }
21
+ useEffect(() => {
22
+ if (!start) return
48
23
 
49
- const processData = (response: AirQuality | null): AqiData => {
50
- let data: AqiData = { city: cityInput, station: "Estación desconocida" }
51
- if (response && response.status === API_RESPONSE_STATUS.OK) {
52
- data = {
53
- aqi: response.data.aqi,
54
- city: cityInput,
55
- station: response.data.city.name
24
+ const fetchData = async () => {
25
+ try {
26
+ const data = await getData(cityInput, updateTime, msj0)
27
+ setFormattedData(data)
28
+ } catch (error) {
29
+ console.error('Error fetching data:', error)
30
+ setFormattedData(undefined)
56
31
  }
57
32
  }
58
- return data
59
- }
60
33
 
61
- const getData = async () => {
62
- const storageData: StorageData | null = await localforage.getItem('aqi')
63
- let data
64
- if (!storageData?.item || storageData?.item.city !== parameters.cityInput || !storageData?.date || refetchData(new Date(storageData.date))) {
65
- data = await requestData()
66
- const processedData = processData(data)
67
- await localforage.setItem('aqi', { item: processedData, date: new Date() })
68
- setFormattedData(processedData)
69
- } else {
70
- data = storageData.item
71
- setFormattedData(data)
72
- }
73
- }
34
+ fetchData()
74
35
 
75
- useEffect(() => {
76
- if (!start) return
77
- getData()
78
36
  const interval = setInterval(() => {
79
- getData()
37
+ fetchData()
80
38
  }, updateTime)
39
+
81
40
  return () => clearInterval(interval)
82
41
  }, [start, updateTime, parameters.cityInput])
83
42
 
@@ -92,11 +51,7 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
92
51
  backgroundImage: backgroundImageState,
93
52
  backgroundSize: '100% 100%',
94
53
  }}>
95
- <div style={{
96
- width: "100%",
97
- height: "100%",
98
- position: "relative"
99
- }}>
54
+ <div style={{ width: "100%", height: "100%", position: "relative" }}>
100
55
  {formattedData ? (
101
56
  <div style={{
102
57
  position: "absolute",
@@ -122,17 +77,12 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
122
77
  left: `${parameters.leftSeparationCard || 0}px`
123
78
  }}>
124
79
  <FontLoader paths={['formatMjs.font']} parameters={parameters} downloadsPath={downloadsPath} />
125
- <div style={{
126
- display: 'flex',
127
- position: 'relative',
128
- flex: '1 1 0%',
129
- flexDirection: 'column',
130
- }}>
131
- <FormattedText text={parameters?.msj0 || 'No hay datos para mostrar'} format={parameters?.formatMjs} />
80
+ <div style={{ display: 'flex', position: 'relative', flex: '1 1 0%', flexDirection: 'column' }}>
81
+ <FormattedText text={msj0} format={parameters?.formatMjs} />
132
82
  </div>
133
83
  </div>
134
84
  )}
135
85
  </div>
136
86
  </div>
137
87
  )
138
- }
88
+ }
@@ -52,4 +52,4 @@ export const Card: React.FunctionComponent<Props> = ({ data, format }) => {
52
52
  </div>
53
53
  </div>
54
54
  )
55
- }
55
+ }
@@ -38,4 +38,4 @@ export const FontLoader: React.FunctionComponent<Props> = ({ paths, parameters,
38
38
  return (
39
39
  <style>{fonts}</style>
40
40
  )
41
- }
41
+ }
@@ -15,7 +15,7 @@ interface Props {
15
15
  format?: any
16
16
  lineHeight?: number
17
17
  maxChar?: number
18
- style?: number
18
+ style?: React.CSSProperties
19
19
  text: string
20
20
  unit?: string
21
21
  paddingBottom?: string
@@ -23,18 +23,17 @@ interface Props {
23
23
  }
24
24
 
25
25
  export const FormattedText: React.FunctionComponent<Props> = ({ text, format, maxChar, lineHeight, style, unit, paddingBottom, paddingTop }) => {
26
- const trimText = (text: any, maxChar: any) => {
26
+ const trimText = (text: string, maxChar?: number) => {
27
27
  const isValid = maxChar && maxChar >= 3
28
- if (isValid && (text && text.length > maxChar) || false) {
29
- let returnText = text.substring(0, maxChar - 3)
30
- returnText.substr(-1, 3)
28
+ if (isValid && text.length > maxChar) {
29
+ const returnText = text.substring(0, maxChar - 3)
31
30
  return `${returnText.trim()}...`
32
31
  }
33
32
  return text
34
33
  }
35
34
 
36
- const checkNested = (obj: any, path: any): any => {
37
- let arr = path.split('.')
35
+ const checkNested = (obj: any, path: string): any => {
36
+ const arr = path.split('.')
38
37
  if (arr.length > 0) {
39
38
  if (obj.hasOwnProperty(arr[0])) {
40
39
  if (arr.length > 1) return checkNested(obj[arr[0]], arr.splice(1).join('.'))
@@ -43,19 +42,19 @@ export const FormattedText: React.FunctionComponent<Props> = ({ text, format, ma
43
42
  }
44
43
  }
45
44
 
46
- const getHorizontalAlignment = (alignment: Aligment) => {
47
- if (alignment) {
48
- let hA = alignment.horizontal
49
- return alignments.hasOwnProperty(hA) ? alignments[alignment.horizontal] : 'flex-start'
50
- }
51
- return 'flex-start'
45
+ const getHorizontalAlignment = (alignment?: Aligment) => {
46
+ if (!alignment) return 'flex-start'
47
+
48
+ const hA = alignment.horizontal
49
+ return alignments.hasOwnProperty(hA) ? alignments[hA] : 'flex-start'
52
50
  }
53
51
 
54
52
  const renderText = maxChar ? trimText(text, maxChar) : text
55
- let containerStyle = Object.assign({
53
+ let containerStyle = {
56
54
  display: 'inline-flex',
57
- justifyContent: getHorizontalAlignment(format.alignment)
58
- }, style)
55
+ justifyContent: getHorizontalAlignment(format.alignment),
56
+ ...style
57
+ }
59
58
 
60
59
  return (
61
60
  <div style={containerStyle}>
@@ -78,4 +77,4 @@ FormattedText.defaultProps = {
78
77
  lineHeight: 1,
79
78
  unit: 'vh',
80
79
  maxChar: undefined
81
- }
80
+ }
package/src/index.html CHANGED
@@ -1,11 +1,14 @@
1
1
  <!DOCTYPE html>
2
- <html style='position: absolute; height:100%; width: 100%; overflow: hidden;'>
3
- <head>
4
- <title></title>
5
- <meta charset='utf-8'>
6
- </head>
7
- <body style='margin:0; overflow: hidden;'>
8
- <div id='root' style='position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden;'>
9
- </div>
10
- </body>
11
- </html>
2
+ <html
3
+ lang="en"
4
+ style="position: absolute; height: 100%; width: 100%; overflow: hidden">
5
+ <head>
6
+ <title></title>
7
+ <meta charset="utf-8" />
8
+ </head>
9
+ <body style="margin: 0; overflow: hidden">
10
+ <div
11
+ id="root"
12
+ style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; overflow: hidden"></div>
13
+ </body>
14
+ </html>
package/src/main.ts CHANGED
@@ -1,6 +1,6 @@
1
- import React from 'react';
2
- import ReactDOM from 'react-dom';
3
- import { App } from './App';
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom'
3
+ import { App } from './App'
4
4
 
5
5
  const { ipcRenderer } = require('electron')
6
6
  let start: boolean = false
@@ -0,0 +1,61 @@
1
+ import axios from 'axios'
2
+ import localforage from 'localforage'
3
+ import { AirQuality, AqiData, StorageData } from '../types'
4
+ import { API_RESPONSE_STATUS } from '../utils'
5
+
6
+ if (!process.env.AQI_TOKEN) console.warn('Env variable not found. Skipping API call.')
7
+ const TOKEN = process.env.AQI_TOKEN
8
+
9
+ const refetchData = (referenceDate: Date, updateTime: number): boolean => {
10
+ const now = new Date()
11
+
12
+ const sameYear = referenceDate.getFullYear() === now.getFullYear()
13
+ const sameMonth = referenceDate.getMonth() === now.getMonth()
14
+ const sameDay = referenceDate.getDate() === now.getDate()
15
+
16
+ if (sameYear && sameMonth && sameDay) {
17
+ const differenceMilliseconds = Math.abs(now.getTime() - referenceDate.getTime())
18
+ return differenceMilliseconds >= updateTime
19
+ }
20
+ return true
21
+ }
22
+
23
+ const requestData = async (cityInput: string): Promise<AirQuality | null> => {
24
+ const URL = `https://api.waqi.info/feed/${cityInput}/?token=${TOKEN}`
25
+ try {
26
+ const response = await axios.get<AirQuality>(URL)
27
+ if (response?.status === 200) {
28
+ return response.data
29
+ }
30
+ return null
31
+ } catch (error) {
32
+ console.error('Error fetching AQI data:', error)
33
+ return null
34
+ }
35
+ }
36
+
37
+ const processData = (response: AirQuality | null, cityInput: string, defaultMsg: string): AqiData => {
38
+ let data: AqiData = { city: cityInput, station: defaultMsg }
39
+ if (response?.status === API_RESPONSE_STATUS.OK) {
40
+ data = {
41
+ aqi: response.data.aqi,
42
+ city: cityInput,
43
+ station: response.data.city.name
44
+ }
45
+ }
46
+ return data
47
+ }
48
+
49
+ export const getData = async (cityInput: string, updateTime: number, defaultMsg: string): Promise<AqiData> => {
50
+ const storageData: StorageData | null = await localforage.getItem('aqi')
51
+ let data
52
+ if (!storageData?.item || storageData?.item.city !== cityInput || !storageData?.date || refetchData(new Date(storageData.date), updateTime)) {
53
+ data = await requestData(cityInput)
54
+ const processedData = processData(data, cityInput, defaultMsg)
55
+ await localforage.setItem('aqi', { item: processedData, date: new Date() })
56
+ return processedData
57
+ } else {
58
+ data = storageData.item
59
+ return data
60
+ }
61
+ }
package/src/utils.ts CHANGED
@@ -1,11 +1,9 @@
1
- const TOKEN = '9fa7d8df1a621c9d518a351a13ff9b94093b9dac'
2
-
3
- const API_RESPONSE_STATUS = {
1
+ export const API_RESPONSE_STATUS = {
4
2
  OK: 'ok',
5
3
  ERROR: 'error',
6
4
  }
7
5
 
8
- const assingAirQuality = (magnitude: number): { color: string; quality: string } => {
6
+ export const assingAirQuality = (magnitude: number): { color: string; quality: string } => {
9
7
  let color = '#9D9F93'
10
8
  let quality = ''
11
9
  if (0 <= magnitude && magnitude <= 50) {
@@ -29,5 +27,3 @@ const assingAirQuality = (magnitude: number): { color: string; quality: string }
29
27
  }
30
28
  return { color, quality }
31
29
  }
32
-
33
- export { TOKEN, API_RESPONSE_STATUS, assingAirQuality }
package/tsconfig.json CHANGED
@@ -1,37 +1,28 @@
1
1
  {
2
- "compilerOptions": {
3
- "target": "es2015",
4
- "module": "es2015",
5
- "moduleResolution": "node",
6
- "jsx": "preserve",
7
- "allowJs": true,
8
- "checkJs": true,
9
- "allowSyntheticDefaultImports": true,
10
- "emitDecoratorMetadata": true,
11
- "experimentalDecorators": true,
12
- "downlevelIteration": true,
13
- "strict": true,
14
- "forceConsistentCasingInFileNames": true,
15
- "noFallthroughCasesInSwitch": true,
16
- "noImplicitReturns": true,
17
- "noImplicitAny": true,
18
- "noImplicitThis": true,
19
- "noUnusedLocals": true,
20
- "noUnusedParameters": true,
21
- "sourceMap": true,
22
- "types": [
23
- "node"
24
- ],
25
- "typeRoots": [
26
- "./node_modules/@types",
27
- "./src",
28
- "./src/global.d.ts"
29
- ]
30
- },
31
- "include": [
32
- "./src/**/*"
33
- ],
34
- "exclude": [
35
- "./node_modules/**/*"
36
- ]
37
- }
2
+ "compilerOptions": {
3
+ "target": "es6",
4
+ "module": "esnext",
5
+ "moduleResolution": "node",
6
+ "jsx": "react-jsx",
7
+ "allowJs": true,
8
+ "checkJs": true,
9
+ "allowSyntheticDefaultImports": true,
10
+ "emitDecoratorMetadata": true,
11
+ "experimentalDecorators": true,
12
+ "downlevelIteration": true,
13
+ "strict": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noFallthroughCasesInSwitch": true,
16
+ "noImplicitReturns": true,
17
+ "noImplicitAny": true,
18
+ "noImplicitThis": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "sourceMap": true,
22
+ "skipLibCheck": true,
23
+ "types": ["node"],
24
+ "typeRoots": ["./node_modules/@types", "./src"]
25
+ },
26
+ "include": ["./src/**/*"],
27
+ "exclude": ["./node_modules/**/*"]
28
+ }