@vixoniccom/footbal-score 1.4.6-dev.1 → 1.4.6-dev.10
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.
- package/CHANGELOG.md +18 -0
- package/build/main.js +1 -1
- package/build/main.js.LICENSE.txt +0 -7
- package/build/test/parameters.json +1 -2
- package/build/test/services.json +109 -0
- package/build.zip +0 -0
- package/configuration/dataGroup/dataInputs.ts +5 -51
- package/configuration.json +4 -290
- package/package.json +2 -2
- package/src/App.tsx +28 -152
- package/src/components/Table/components/Results.tsx +2 -2
- package/src/components/Table/components/Row.tsx +2 -1
- package/src/components/Table/components/Time.tsx +4 -3
- package/src/components/Table/index.tsx +7 -1
- package/src/football-loader.ts +27 -0
- package/src/globals.d.ts +28 -0
- package/src/helpers/getFixture.ts +1 -46
- package/src/main.ts +39 -10
- package/src/parameters.d.ts +1 -1
- package/src/test/parameters.json +1 -2
- package/src/test/services.json +109 -0
- package/src/helpers/mockFixtures.ts +0 -74
package/src/App.tsx
CHANGED
|
@@ -1,154 +1,49 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import moment from 'moment'
|
|
3
3
|
import { FontLoader, FormattedText, Table } from './components'
|
|
4
|
-
import { getFixture } from './helpers/getFixture'
|
|
5
4
|
|
|
6
5
|
export type Props = {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
appData?: VixonicData
|
|
7
|
+
fixtures: FootballFixture[]
|
|
8
|
+
serviceError?: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
const
|
|
12
|
-
|
|
11
|
+
export const App: React.FunctionComponent<Props> = ({ appData, fixtures }) => {
|
|
12
|
+
if (!appData) return null
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
const { parameters, downloadsPath } = appData
|
|
15
|
+
const { backgroundImage, padding, msj0 = '', matchesYesterday, matchesTomorrow } = parameters
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
console.log(`[⚽ football-score ${new Date().toLocaleTimeString()}]`, msg, ...args)
|
|
17
|
+
console.log('[App] fixtures received:', fixtures.length, fixtures)
|
|
18
18
|
|
|
19
|
-
// Returns the timestamp (ms) when the next fetch should happen based on fixture states.
|
|
20
|
-
// Persisted in localStorage so re-mounts don't reset the schedule.
|
|
21
|
-
function computeNextFetchTime(fixtures: any[], pollIntervalMs: number): number {
|
|
22
|
-
const now = Date.now()
|
|
23
|
-
|
|
24
|
-
const liveMatches = fixtures.filter(f => LIVE_STATUSES.includes(f.fixture?.status?.short))
|
|
25
|
-
if (liveMatches.length > 0) {
|
|
26
|
-
const next = new Date(now + pollIntervalMs)
|
|
27
|
-
log(`🟢 ${liveMatches.length} partido(s) en vivo → próximo fetch en ${next.toLocaleTimeString()} (${pollIntervalMs / 1000}s)`)
|
|
28
|
-
return now + pollIntervalMs
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const nextMatchAt = fixtures
|
|
32
|
-
.filter(f => f.fixture?.status?.short === 'NS')
|
|
33
|
-
.map(f => new Date(f.fixture.date).getTime())
|
|
34
|
-
.filter(t => t > now)
|
|
35
|
-
.sort((a, b) => a - b)[0]
|
|
36
|
-
|
|
37
|
-
if (nextMatchAt !== undefined) {
|
|
38
|
-
const wakeUp = nextMatchAt - PRE_MATCH_MS
|
|
39
|
-
if (wakeUp <= now) {
|
|
40
|
-
const next = new Date(now + pollIntervalMs)
|
|
41
|
-
log(`🟡 Dentro de ventana pre-partido → próximo fetch en ${next.toLocaleTimeString()}`)
|
|
42
|
-
return now + pollIntervalMs
|
|
43
|
-
}
|
|
44
|
-
log(`⏰ Próximo partido: ${new Date(nextMatchAt).toLocaleTimeString()} → fetch a las ${new Date(wakeUp).toLocaleTimeString()} (10 min antes)`)
|
|
45
|
-
return wakeUp
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const tomorrow = new Date()
|
|
49
|
-
tomorrow.setDate(tomorrow.getDate() + 1)
|
|
50
|
-
tomorrow.setHours(0, 15, 0, 0)
|
|
51
|
-
log(`💤 Sin partidos pendientes → próximo check: ${tomorrow.toLocaleString()}`)
|
|
52
|
-
return tomorrow.getTime()
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export const App: React.FunctionComponent<Props> = ({ data, start }) => {
|
|
56
|
-
const [dataFixture, setDataFixture] = useState<any[]>([])
|
|
57
|
-
const [leagueState, setLeagueState] = useState<string>('')
|
|
58
|
-
const { parameters, downloadsPath } = data
|
|
59
|
-
const { backgroundImage, padding, msj0 = '', league = '9', debugTimezone = false } = parameters
|
|
60
19
|
const backgroundImageState = backgroundImage?.filename
|
|
61
20
|
? `url("${downloadsPath}/${backgroundImage.filename}")`
|
|
62
21
|
: ''
|
|
63
22
|
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const paramsRef = useRef(parameters)
|
|
68
|
-
useEffect(() => {
|
|
69
|
-
leagueRef.current = league
|
|
70
|
-
paramsRef.current = parameters
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
const clearSchedule = () => {
|
|
74
|
-
if (timeoutRef.current !== null) {
|
|
75
|
-
clearTimeout(timeoutRef.current)
|
|
76
|
-
timeoutRef.current = null
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const fetchAndSchedule = async () => {
|
|
81
|
-
const { matchesYesterday, matchesTomorrow, updateTime: ut = 300000 } = paramsRef.current
|
|
82
|
-
const cached: any = await localforage.getItem('football-score')
|
|
83
|
-
|
|
84
|
-
log(`🔄 Fetching fixtures (liga ${leagueRef.current})...`)
|
|
85
|
-
let fixtures: any[]
|
|
86
|
-
try {
|
|
87
|
-
fixtures = await getFixture(leagueRef.current, matchesYesterday, matchesTomorrow)
|
|
88
|
-
log(`✅ ${fixtures.length} fixture(s) recibidos`)
|
|
89
|
-
} catch (err) {
|
|
90
|
-
console.error('[football-score] API error:', err)
|
|
91
|
-
fixtures = cached?.data ?? []
|
|
92
|
-
log(`❌ API falló → usando cache (${fixtures.length} fixtures)`)
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const nextFetchTime = computeNextFetchTime(fixtures, ut)
|
|
23
|
+
const serviceUtcOffset = fixtures[0]
|
|
24
|
+
? moment.parseZone(fixtures[0].fixture.date).utcOffset()
|
|
25
|
+
: 0
|
|
96
26
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
console.error('[football-score] localStorage write error', err)
|
|
101
|
-
}
|
|
27
|
+
const today = moment().utcOffset(serviceUtcOffset).format('YYYY-MM-DD')
|
|
28
|
+
const yesterdayStr = moment().utcOffset(serviceUtcOffset).subtract(1, 'days').format('YYYY-MM-DD')
|
|
29
|
+
const tomorrowStr = moment().utcOffset(serviceUtcOffset).add(1, 'days').format('YYYY-MM-DD')
|
|
102
30
|
|
|
103
|
-
|
|
31
|
+
console.log('[App] date context — today:', today, 'yesterday:', yesterdayStr, 'tomorrow:', tomorrowStr, 'utcOffset:', serviceUtcOffset)
|
|
32
|
+
console.log('[App] filter config — matchesYesterday:', matchesYesterday, 'matchesTomorrow:', matchesTomorrow)
|
|
104
33
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (!cached?.data) {
|
|
114
|
-
log(`🆕 Sin cache → fetch inmediato`)
|
|
115
|
-
fetchAndSchedule()
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
setDataFixture(cached.data)
|
|
120
|
-
|
|
121
|
-
if (!cached.nextFetchTime || cached.nextFetchTime <= now) {
|
|
122
|
-
log(`🔁 Mount check: nextFetchTime ya pasó (${cached.nextFetchTime ? new Date(cached.nextFetchTime).toLocaleTimeString() : 'null'}) → fetch inmediato`)
|
|
123
|
-
fetchAndSchedule()
|
|
124
|
-
} else {
|
|
125
|
-
const mins = Math.round((cached.nextFetchTime - now) / 60000)
|
|
126
|
-
log(`💾 Mount check: usando cache → próximo fetch en ${new Date(cached.nextFetchTime).toLocaleTimeString()} (~${mins} min)`)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
useEffect(() => {
|
|
131
|
-
if (!start) return
|
|
132
|
-
setLeagueState(league)
|
|
133
|
-
init()
|
|
134
|
-
return clearSchedule
|
|
135
|
-
}, [data])
|
|
136
|
-
|
|
137
|
-
useEffect(() => {
|
|
138
|
-
if (leagueState === '' || leagueState === league) return
|
|
139
|
-
setLeagueState(league)
|
|
140
|
-
clearSchedule()
|
|
141
|
-
localforage.clear()
|
|
142
|
-
fetchAndSchedule()
|
|
143
|
-
}, [league])
|
|
34
|
+
const filteredFixtures = fixtures.filter(f => {
|
|
35
|
+
const fixtureDate = moment.parseZone(f.fixture.date).format('YYYY-MM-DD')
|
|
36
|
+
if (fixtureDate === today) return true
|
|
37
|
+
if (fixtureDate === yesterdayStr && matchesYesterday) return true
|
|
38
|
+
if (fixtureDate === tomorrowStr && matchesTomorrow) return true
|
|
39
|
+
return false
|
|
40
|
+
})
|
|
144
41
|
|
|
145
|
-
|
|
146
|
-
try { return Intl.DateTimeFormat().resolvedOptions().timeZone } catch { return 'UTC' }
|
|
147
|
-
})()
|
|
42
|
+
console.log('[App] filteredFixtures count:', filteredFixtures.length)
|
|
148
43
|
|
|
149
44
|
return (
|
|
150
45
|
<>
|
|
151
|
-
{
|
|
46
|
+
{filteredFixtures.length > 0 ?
|
|
152
47
|
<div style={{
|
|
153
48
|
position: 'absolute',
|
|
154
49
|
top: 0,
|
|
@@ -165,7 +60,7 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
|
|
|
165
60
|
<FormattedText text={parameters.title0 || 'Partidos Mundial'} format={parameters.titleFormat} />
|
|
166
61
|
</div>
|
|
167
62
|
}
|
|
168
|
-
<Table dataFixture={
|
|
63
|
+
<Table dataFixture={filteredFixtures} parameters={parameters} />
|
|
169
64
|
</div> :
|
|
170
65
|
<div style={{
|
|
171
66
|
position: 'absolute',
|
|
@@ -185,25 +80,6 @@ export const App: React.FunctionComponent<Props> = ({ data, start }) => {
|
|
|
185
80
|
</div>
|
|
186
81
|
</div>
|
|
187
82
|
}
|
|
188
|
-
{debugTimezone && (
|
|
189
|
-
<div style={{
|
|
190
|
-
position: 'fixed',
|
|
191
|
-
bottom: 8,
|
|
192
|
-
right: 8,
|
|
193
|
-
background: 'rgba(0,0,0,0.75)',
|
|
194
|
-
color: '#00ff00',
|
|
195
|
-
fontSize: 10,
|
|
196
|
-
fontFamily: 'monospace',
|
|
197
|
-
padding: '4px 8px',
|
|
198
|
-
borderRadius: 4,
|
|
199
|
-
zIndex: 9999,
|
|
200
|
-
lineHeight: 1.4,
|
|
201
|
-
pointerEvents: 'none',
|
|
202
|
-
whiteSpace: 'nowrap',
|
|
203
|
-
}}>
|
|
204
|
-
TZ: {timezone}
|
|
205
|
-
</div>
|
|
206
|
-
)}
|
|
207
83
|
</>
|
|
208
84
|
)
|
|
209
85
|
}
|
|
@@ -11,11 +11,11 @@ interface Props {
|
|
|
11
11
|
export const Results: React.FunctionComponent<Props> = ({ parameters, goals, score }) => {
|
|
12
12
|
return (
|
|
13
13
|
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
|
14
|
-
{isNumber(score.penalty
|
|
14
|
+
{isNumber(score.penalty?.home) ? <FormattedText text={`(${score.penalty.home})`} format={parameters.goalsFormat} /> : ''}
|
|
15
15
|
<FormattedText text={goals.home ? goals.home : 0} format={parameters.goalsFormat} />
|
|
16
16
|
<FormattedText text={'-'} format={parameters.goalsFormat} />
|
|
17
17
|
<FormattedText text={goals.away ? goals.away : 0} format={parameters.goalsFormat} />
|
|
18
|
-
{isNumber(score.penalty
|
|
18
|
+
{isNumber(score.penalty?.away) ? <FormattedText text={`(${score.penalty.away})`} format={parameters.goalsFormat} /> : ''}
|
|
19
19
|
</div>
|
|
20
20
|
)
|
|
21
21
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import moment from 'moment'
|
|
1
2
|
import React, { useEffect, useState } from 'react'
|
|
2
3
|
import { Results, Status, Time } from './'
|
|
3
4
|
import { FormattedText } from '../../'
|
|
@@ -71,7 +72,7 @@ export const Row: React.FunctionComponent<Props> = ({ item, parameters }) => {
|
|
|
71
72
|
justifyContent: 'center'
|
|
72
73
|
}}>
|
|
73
74
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
74
|
-
<Time time={
|
|
75
|
+
<Time time={moment.parseZone(fixture.date).unix()} utcOffset={moment.parseZone(fixture.date).utcOffset()} parameters={parameters} />
|
|
75
76
|
</div>
|
|
76
77
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
77
78
|
<Status status={fixture.status.short} parameters={parameters} />
|
|
@@ -4,14 +4,15 @@ import { FormattedText } from '../../'
|
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
6
6
|
time: number
|
|
7
|
+
utcOffset: number
|
|
7
8
|
parameters: VixonicParameters
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
export const Time: React.FunctionComponent<Props> = ({ time, parameters }) => {
|
|
11
|
+
export const Time: React.FunctionComponent<Props> = ({ time, utcOffset, parameters }) => {
|
|
11
12
|
return (
|
|
12
13
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }}>
|
|
13
|
-
<FormattedText text={moment.unix(time).format('DD-MM')} format={parameters.timeFormat} />
|
|
14
|
-
<FormattedText text={moment.unix(time).format('h:mm a')} format={parameters.timeFormat} />
|
|
14
|
+
<FormattedText text={moment.unix(time).utcOffset(utcOffset).format('DD-MM')} format={parameters.timeFormat} />
|
|
15
|
+
<FormattedText text={moment.unix(time).utcOffset(utcOffset).format('h:mm a')} format={parameters.timeFormat} />
|
|
15
16
|
</div>
|
|
16
17
|
)
|
|
17
18
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import moment from 'moment'
|
|
1
2
|
import React, { useEffect, useState } from 'react'
|
|
2
3
|
import { Row } from './components'
|
|
3
4
|
|
|
@@ -9,11 +10,16 @@ type Props = {
|
|
|
9
10
|
export const Table: React.FunctionComponent<Props> = ({ dataFixture, parameters }) => {
|
|
10
11
|
const { itemsPerPage, durationPerPage } = parameters
|
|
11
12
|
const rows = Number(itemsPerPage || 4)
|
|
12
|
-
const orderedData = dataFixture.sort((a, b) => a.fixture.
|
|
13
|
+
const orderedData = dataFixture.sort((a, b) => moment.parseZone(a.fixture.date).unix() - moment.parseZone(b.fixture.date).unix())
|
|
13
14
|
const [page, setPage] = useState<number>(1)
|
|
14
15
|
const [items, setItems] = useState<string[][]>(orderedData.slice(0, rows))
|
|
15
16
|
const total = orderedData.length
|
|
16
17
|
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
setPage(1)
|
|
20
|
+
setItems(orderedData.slice(0, rows))
|
|
21
|
+
}, [dataFixture])
|
|
22
|
+
|
|
17
23
|
useEffect(() => {
|
|
18
24
|
const timer = setTimeout(() => {
|
|
19
25
|
handlePagination()
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export class FootballLoader {
|
|
2
|
+
public onData?: (fixtures: FootballFixture[]) => void = undefined
|
|
3
|
+
public onError?: (error: string) => void = undefined
|
|
4
|
+
|
|
5
|
+
update(appData: VixonicData) {
|
|
6
|
+
const serviceId = appData.parameters.footballService?.id
|
|
7
|
+
console.log('[FootballLoader] serviceId:', serviceId)
|
|
8
|
+
|
|
9
|
+
if (!serviceId || !appData.services[serviceId]) {
|
|
10
|
+
console.log('[FootballLoader] service not found in appData.services. Available keys:', Object.keys(appData.services))
|
|
11
|
+
return
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const rawData = appData.services[serviceId].data as FootballServiceData | undefined
|
|
15
|
+
console.log('[FootballLoader] rawData:', JSON.stringify(rawData, null, 2))
|
|
16
|
+
|
|
17
|
+
const fixtures = rawData?.fixtures ?? []
|
|
18
|
+
console.log('[FootballLoader] fixtures count:', fixtures.length)
|
|
19
|
+
if (fixtures.length > 0) {
|
|
20
|
+
console.log('[FootballLoader] first fixture:', JSON.stringify(fixtures[0], null, 2))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
this.onData?.(fixtures)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
stop() {}
|
|
27
|
+
}
|
package/src/globals.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
declare interface FootballFixture {
|
|
2
|
+
fixture: {
|
|
3
|
+
id: number
|
|
4
|
+
date: string
|
|
5
|
+
status: { long: string; short: string; elapsed: number | null }
|
|
6
|
+
venue: { name: string; city: string }
|
|
7
|
+
}
|
|
8
|
+
league: {
|
|
9
|
+
id: number
|
|
10
|
+
name: string
|
|
11
|
+
country: string
|
|
12
|
+
logo: string
|
|
13
|
+
round: string
|
|
14
|
+
}
|
|
15
|
+
teams: {
|
|
16
|
+
home: { id: number; name: string; logo: string; winner: boolean | null }
|
|
17
|
+
away: { id: number; name: string; logo: string; winner: boolean | null }
|
|
18
|
+
}
|
|
19
|
+
goals: { home: number | null; away: number | null }
|
|
20
|
+
score: {
|
|
21
|
+
halftime: { home: number | null; away: number | null }
|
|
22
|
+
fulltime: { home: number | null; away: number | null }
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
declare interface FootballServiceData {
|
|
27
|
+
fixtures: FootballFixture[]
|
|
28
|
+
}
|
|
@@ -1,46 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import axios from 'axios'
|
|
3
|
-
import { mockFixtures } from './mockFixtures'
|
|
4
|
-
|
|
5
|
-
if (!process.env.FOOTBALL_API_KEY || !process.env.FOOTBALL_API_HOST || !process.env.FOOTBALL_API_URL)
|
|
6
|
-
console.warn('Missing API credentials')
|
|
7
|
-
const FOOTBALL_API_KEY = process.env.FOOTBALL_API_KEY
|
|
8
|
-
const FOOTBALL_API_HOST = process.env.FOOTBALL_API_HOST
|
|
9
|
-
const FOOTBALL_API_URL = process.env.FOOTBALL_API_URL
|
|
10
|
-
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000
|
|
11
|
-
|
|
12
|
-
const USE_MOCK = false
|
|
13
|
-
|
|
14
|
-
export const getFixture = async (league: string, yesterday?: boolean, tomorrow?: boolean) => {
|
|
15
|
-
if (USE_MOCK) return mockFixtures
|
|
16
|
-
|
|
17
|
-
const today = moment().format('YYYY-MM-DD')
|
|
18
|
-
const year = moment().format('YYYY')
|
|
19
|
-
const from = yesterday ? moment(Date.now() - MILLISECONDS_PER_DAY).format('YYYY-MM-DD') : today
|
|
20
|
-
const to = tomorrow ? moment(Date.now() + MILLISECONDS_PER_DAY).format('YYYY-MM-DD') : today
|
|
21
|
-
const timezone = (() => {
|
|
22
|
-
try {
|
|
23
|
-
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
24
|
-
console.log(`[football-score] Timezone resolved: ${tz}`)
|
|
25
|
-
return tz
|
|
26
|
-
} catch (error) {
|
|
27
|
-
console.warn('[football-score] Failed to resolve timezone, defaulting to UTC', error)
|
|
28
|
-
return 'UTC'
|
|
29
|
-
}
|
|
30
|
-
})()
|
|
31
|
-
|
|
32
|
-
const options = {
|
|
33
|
-
method: 'GET',
|
|
34
|
-
url: FOOTBALL_API_URL,
|
|
35
|
-
params: { from, to, league, season: year, timezone },
|
|
36
|
-
headers: { 'X-RapidAPI-Key': FOOTBALL_API_KEY, 'X-RapidAPI-Host': FOOTBALL_API_HOST },
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
const response = await axios.request(options)
|
|
41
|
-
return response.data.response
|
|
42
|
-
} catch (error) {
|
|
43
|
-
console.error(error)
|
|
44
|
-
return []
|
|
45
|
-
}
|
|
46
|
-
}
|
|
1
|
+
export const resolvedTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
|
package/src/main.ts
CHANGED
|
@@ -1,27 +1,56 @@
|
|
|
1
|
-
import ReactDOM from 'react-dom'
|
|
2
1
|
import React from 'react'
|
|
3
|
-
import
|
|
4
|
-
|
|
2
|
+
import ReactDOM from 'react-dom'
|
|
3
|
+
import { App } from './App'
|
|
4
|
+
import { FootballLoader } from './football-loader'
|
|
5
5
|
const { ipcRenderer } = require('electron')
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
let appData: VixonicData | undefined = undefined
|
|
8
|
+
let fixtures: FootballFixture[] = []
|
|
9
|
+
let serviceError: string | undefined = undefined
|
|
10
|
+
|
|
11
|
+
const footballLoader = new FootballLoader()
|
|
12
|
+
|
|
13
|
+
footballLoader.onData = (update) => {
|
|
14
|
+
fixtures = update
|
|
15
|
+
render()
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
footballLoader.onError = (error) => {
|
|
19
|
+
serviceError = error
|
|
20
|
+
render()
|
|
21
|
+
}
|
|
7
22
|
|
|
8
23
|
ipcRenderer.on('preload', (_event: any, data: VixonicData) => {
|
|
9
|
-
|
|
24
|
+
console.log('[main] preload received. parameters:', JSON.stringify(data.parameters, null, 2))
|
|
25
|
+
console.log('[main] preload services keys:', Object.keys(data.services))
|
|
26
|
+
appData = data
|
|
27
|
+
footballLoader.update(data)
|
|
28
|
+
render()
|
|
10
29
|
})
|
|
11
30
|
|
|
12
31
|
ipcRenderer.on('start', (_event: any, data: VixonicData) => {
|
|
13
|
-
start
|
|
14
|
-
|
|
32
|
+
console.log('[main] start received. parameters:', JSON.stringify(data.parameters, null, 2))
|
|
33
|
+
console.log('[main] start services keys:', Object.keys(data.services))
|
|
34
|
+
appData = data
|
|
35
|
+
footballLoader.update(data)
|
|
36
|
+
render()
|
|
15
37
|
})
|
|
16
38
|
|
|
17
39
|
ipcRenderer.on('update', (_event: any, data: VixonicData) => {
|
|
18
|
-
|
|
40
|
+
console.log('[main] update received. services keys:', Object.keys(data.services))
|
|
41
|
+
appData = data
|
|
42
|
+
footballLoader.update(data)
|
|
43
|
+
render()
|
|
19
44
|
})
|
|
20
45
|
|
|
21
46
|
ipcRenderer.on('finish', (_event: any) => {
|
|
47
|
+
footballLoader.stop()
|
|
22
48
|
console.log('finish')
|
|
23
49
|
})
|
|
24
50
|
|
|
25
|
-
function render(
|
|
26
|
-
ReactDOM.render(
|
|
51
|
+
function render() {
|
|
52
|
+
ReactDOM.render(
|
|
53
|
+
React.createElement(App, { appData, fixtures, serviceError }),
|
|
54
|
+
document.getElementById('root')
|
|
55
|
+
)
|
|
27
56
|
}
|
package/src/parameters.d.ts
CHANGED
|
@@ -5,5 +5,5 @@ declare type VixonicData = {
|
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
declare type VixonicParameters = Partial<{
|
|
8
|
-
|
|
8
|
+
footballService: { id: string }, matchesYesterday: boolean, matchesTomorrow: boolean, titleEnabled: boolean, title0: string, titleFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, msj0: string, formatMjs: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, alignVerticalMsj: 'flex-start' | 'center' | 'flex-end', itemsPerPage: number, durationPerPage: number, backgroundImage: {id?: string, filename?: string, extension?: string}, padding: string, columnGap: string, rowGap: string, timeFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthTime: string, statusFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthStatus: string, goalsFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthScore: string, descriptionEnabled: boolean, descriptionFormat: { fontSize?: number, fontColor?: string, alignment?: { horizontal?: 'left' | 'right' | 'center' }, font?: { filename: string, id: string, __isAsset: true } }, widthDescription: string, imageSize: number, widthImage: string
|
|
9
9
|
}>
|
package/src/test/parameters.json
CHANGED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
{
|
|
2
|
+
"services": {
|
|
3
|
+
"f7e3a1b2-c4d5-6789-abcd-ef0123456789": {
|
|
4
|
+
"id": "f7e3a1b2-c4d5-6789-abcd-ef0123456789",
|
|
5
|
+
"data": {
|
|
6
|
+
"fixtures": [
|
|
7
|
+
{
|
|
8
|
+
"fixture": {
|
|
9
|
+
"id": 1001,
|
|
10
|
+
"date": "2026-06-22T18:00:00+00:00",
|
|
11
|
+
"status": { "long": "Match Finished", "short": "FT", "elapsed": 90 },
|
|
12
|
+
"venue": { "name": "Camp Nou", "city": "Barcelona" }
|
|
13
|
+
},
|
|
14
|
+
"league": {
|
|
15
|
+
"id": 140,
|
|
16
|
+
"name": "La Liga",
|
|
17
|
+
"country": "Spain",
|
|
18
|
+
"logo": "https://media.api-sports.io/football/leagues/140.png",
|
|
19
|
+
"round": "Regular Season - 38"
|
|
20
|
+
},
|
|
21
|
+
"teams": {
|
|
22
|
+
"home": { "id": 529, "name": "Barcelona", "logo": "https://media.api-sports.io/football/teams/529.png", "winner": true },
|
|
23
|
+
"away": { "id": 541, "name": "Real Madrid", "logo": "https://media.api-sports.io/football/teams/541.png", "winner": false }
|
|
24
|
+
},
|
|
25
|
+
"goals": { "home": 3, "away": 1 },
|
|
26
|
+
"score": {
|
|
27
|
+
"halftime": { "home": 1, "away": 0 },
|
|
28
|
+
"fulltime": { "home": 3, "away": 1 }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"fixture": {
|
|
33
|
+
"id": 1002,
|
|
34
|
+
"date": "2026-06-22T20:45:00+00:00",
|
|
35
|
+
"status": { "long": "Second Half", "short": "2H", "elapsed": 67 },
|
|
36
|
+
"venue": { "name": "Anfield", "city": "Liverpool" }
|
|
37
|
+
},
|
|
38
|
+
"league": {
|
|
39
|
+
"id": 39,
|
|
40
|
+
"name": "Premier League",
|
|
41
|
+
"country": "England",
|
|
42
|
+
"logo": "https://media.api-sports.io/football/leagues/39.png",
|
|
43
|
+
"round": "Regular Season - 38"
|
|
44
|
+
},
|
|
45
|
+
"teams": {
|
|
46
|
+
"home": { "id": 40, "name": "Liverpool", "logo": "https://media.api-sports.io/football/teams/40.png", "winner": null },
|
|
47
|
+
"away": { "id": 42, "name": "Arsenal", "logo": "https://media.api-sports.io/football/teams/42.png", "winner": null }
|
|
48
|
+
},
|
|
49
|
+
"goals": { "home": 2, "away": 2 },
|
|
50
|
+
"score": {
|
|
51
|
+
"halftime": { "home": 1, "away": 1 },
|
|
52
|
+
"fulltime": { "home": null, "away": null }
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"fixture": {
|
|
57
|
+
"id": 1003,
|
|
58
|
+
"date": "2026-06-23T19:00:00+00:00",
|
|
59
|
+
"status": { "long": "Not Started", "short": "NS", "elapsed": null },
|
|
60
|
+
"venue": { "name": "Allianz Arena", "city": "Munich" }
|
|
61
|
+
},
|
|
62
|
+
"league": {
|
|
63
|
+
"id": 78,
|
|
64
|
+
"name": "Bundesliga",
|
|
65
|
+
"country": "Germany",
|
|
66
|
+
"logo": "https://media.api-sports.io/football/leagues/78.png",
|
|
67
|
+
"round": "Regular Season - 34"
|
|
68
|
+
},
|
|
69
|
+
"teams": {
|
|
70
|
+
"home": { "id": 157, "name": "Bayern Munich", "logo": "https://media.api-sports.io/football/teams/157.png", "winner": null },
|
|
71
|
+
"away": { "id": 165, "name": "Borussia Dortmund", "logo": "https://media.api-sports.io/football/teams/165.png", "winner": null }
|
|
72
|
+
},
|
|
73
|
+
"goals": { "home": null, "away": null },
|
|
74
|
+
"score": {
|
|
75
|
+
"halftime": { "home": null, "away": null },
|
|
76
|
+
"fulltime": { "home": null, "away": null }
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"fixture": {
|
|
81
|
+
"id": 1004,
|
|
82
|
+
"date": "2026-06-22T17:30:00+00:00",
|
|
83
|
+
"status": { "long": "Match Finished", "short": "FT", "elapsed": 90 },
|
|
84
|
+
"venue": { "name": "San Siro", "city": "Milan" }
|
|
85
|
+
},
|
|
86
|
+
"league": {
|
|
87
|
+
"id": 135,
|
|
88
|
+
"name": "Serie A",
|
|
89
|
+
"country": "Italy",
|
|
90
|
+
"logo": "https://media.api-sports.io/football/leagues/135.png",
|
|
91
|
+
"round": "Regular Season - 38"
|
|
92
|
+
},
|
|
93
|
+
"teams": {
|
|
94
|
+
"home": { "id": 489, "name": "AC Milan", "logo": "https://media.api-sports.io/football/teams/489.png", "winner": false },
|
|
95
|
+
"away": { "id": 505, "name": "Inter", "logo": "https://media.api-sports.io/football/teams/505.png", "winner": true }
|
|
96
|
+
},
|
|
97
|
+
"goals": { "home": 0, "away": 2 },
|
|
98
|
+
"score": {
|
|
99
|
+
"halftime": { "home": 0, "away": 1 },
|
|
100
|
+
"fulltime": { "home": 0, "away": 2 }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"updatedAt": 1750608000000,
|
|
106
|
+
"__typename": "FootballAppService"
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|