mirror of
https://github.com/iptv-org/epg.git
synced 2025-05-10 09:00:07 -04:00
Update website
- faster site loading - added more channels
This commit is contained in:
parent
7fb8250025
commit
3f5ff5b671
2 changed files with 208 additions and 193 deletions
186
.gh-pages/app.js
186
.gh-pages/app.js
|
@ -1,54 +1,158 @@
|
||||||
document.addEventListener('alpine:init', () => {
|
const ChannelItem = {
|
||||||
Alpine.data('list', () => ({
|
props: ['channel'],
|
||||||
|
template: `
|
||||||
|
<tr>
|
||||||
|
<td class="is-vcentered" style="min-width: 150px; text-align: center">
|
||||||
|
<img
|
||||||
|
loading="lazy"
|
||||||
|
v-show="channel.logo"
|
||||||
|
:src="channel.logo"
|
||||||
|
style="max-width: 100px; max-height: 50px; vertical-align: middle"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td class="is-vcentered" nowrap>
|
||||||
|
<p v-text="channel.name"></p>
|
||||||
|
</td>
|
||||||
|
<td class="is-vcentered" nowrap>
|
||||||
|
<code v-text="channel.id"></code>
|
||||||
|
</td>
|
||||||
|
<td class="is-vcentered">
|
||||||
|
<p v-for="guide in channel.guides"><code style="white-space: nowrap" v-text="guide"></code></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const CountryItem = {
|
||||||
|
components: {
|
||||||
|
ChannelItem
|
||||||
|
},
|
||||||
|
props: ['item', 'query'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
count: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
countryChannels() {
|
||||||
|
if (!this.query) return this.item.channels
|
||||||
|
|
||||||
|
return (
|
||||||
|
this.item.channels.filter(c => {
|
||||||
|
return c.key.includes(this.query)
|
||||||
|
}) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
countryChannels: function (value) {
|
||||||
|
this.count = value.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div
|
||||||
|
class="card mb-3 is-shadowless"
|
||||||
|
style="border: 1px solid #dbdbdb"
|
||||||
|
v-show="countryChannels && countryChannels.length > 0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="card-header is-shadowless is-clickable"
|
||||||
|
@click="item.expanded = !item.expanded"
|
||||||
|
>
|
||||||
|
<span class="card-header-title">{{item.flag}} {{item.name}}</span>
|
||||||
|
<button class="card-header-icon" aria-label="more options">
|
||||||
|
<span class="icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512">
|
||||||
|
<path
|
||||||
|
v-show="!item.expanded"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="48"
|
||||||
|
d="M112 184l144 144 144-144"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
v-show="item.expanded"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="48"
|
||||||
|
d="M112 328l144-144 144 144"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-content" v-show="item.expanded || (count > 0 && query.length)">
|
||||||
|
<div class="table-container">
|
||||||
|
<table class="table" style="min-width: 100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>TVG-ID</th>
|
||||||
|
<th>EPG</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<channel-item v-for="channel in countryChannels" :channel="channel"></channel-item>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
components: {
|
||||||
|
CountryItem
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
query: '',
|
query: '',
|
||||||
_query: '',
|
normQuery: '',
|
||||||
items: [],
|
items: []
|
||||||
|
}
|
||||||
search() {
|
|
||||||
this._query = this.query.toLowerCase()
|
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
async init() {
|
search() {
|
||||||
const countries = await fetch('api/countries.json')
|
this.normQuery = this.query.replace(/\s/g, '').toLowerCase()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
const guides = await fetch('https://iptv-org.github.io/epg/api/channels.json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
const channels = await fetch('api/channels.json')
|
const channels = await fetch('https://iptv-org.github.io/api/channels.json')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(arr =>
|
||||||
|
arr.map(c => {
|
||||||
|
const found = guides.find(g => g.id === c.id)
|
||||||
|
c.key = `${c.id}_${c.name}`.replace(/\s/g, '').toLowerCase()
|
||||||
|
c.guides = found ? found.guides : []
|
||||||
|
return c
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.then(arr => groupBy(arr, 'country'))
|
||||||
|
.catch(console.log)
|
||||||
|
|
||||||
|
const countries = await fetch('https://iptv-org.github.io/api/countries.json')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.catch(console.log)
|
.catch(console.log)
|
||||||
|
|
||||||
let items = {}
|
this.items = countries.map(i => {
|
||||||
for (let channel of channels) {
|
i.expanded = false
|
||||||
if (!items[channel.country]) {
|
i.channels = channels[i.code] || []
|
||||||
if (!countries[channel.country]) {
|
return i
|
||||||
console.log('Warning: Wrong country code', channel)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const country = countries[channel.country]
|
|
||||||
|
|
||||||
items[channel.country] = {
|
|
||||||
flag: country.flag,
|
|
||||||
name: country.name,
|
|
||||||
expanded: false,
|
|
||||||
channels: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.hash = `${channel.id}_${channel.name}`.toLowerCase()
|
|
||||||
|
|
||||||
items[channel.country].channels.push(channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
items = Object.values(items).sort((a, b) => {
|
|
||||||
if (a.name > b.name) return 1
|
|
||||||
if (a.name < b.name) return -1
|
|
||||||
return 0
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.items = items
|
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
})
|
|
||||||
|
Vue.createApp(App).mount('#app')
|
||||||
|
|
|
@ -5,9 +5,11 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>iptv-org/epg</title>
|
<title>iptv-org/epg</title>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css" />
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css" />
|
||||||
<script defer src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
<script src="https://unpkg.com/vue@next"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/lodash.groupby@4.6.0/index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color: #f6f8fa; min-height: 100vh">
|
<body style="background-color: #f6f8fa; min-height: 100vh">
|
||||||
|
<div id="app">
|
||||||
<div class="navbar" style="background-color: transparent">
|
<div class="navbar" style="background-color: transparent">
|
||||||
<div class="navbar-end">
|
<div class="navbar-end">
|
||||||
<div class="navbar-item">
|
<div class="navbar-item">
|
||||||
|
@ -24,7 +26,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="container" x-data="list">
|
<div class="container">
|
||||||
<div class="columns is-centered">
|
<div class="columns is-centered">
|
||||||
<div class="column is-9">
|
<div class="column is-9">
|
||||||
<form class="mb-5" @submit.prevent="search()">
|
<form class="mb-5" @submit.prevent="search()">
|
||||||
|
@ -33,7 +35,7 @@
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
type="search"
|
type="search"
|
||||||
x-model="query"
|
v-model="query"
|
||||||
placeholder="Search by channel name..."
|
placeholder="Search by channel name..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,103 +58,12 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div x-show="isLoading" class="level">
|
<div v-show="isLoading" class="level">
|
||||||
<div class="level-item">Loading...</div>
|
<div class="level-item">Loading...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template x-for="item in items">
|
<country-item v-for="item in items" :item="item" :query="normQuery"></country-item>
|
||||||
<div
|
|
||||||
class="card mb-3 is-shadowless"
|
|
||||||
style="border: 1px solid #dbdbdb"
|
|
||||||
x-data="{
|
|
||||||
count: 0,
|
|
||||||
get countryChannels() {
|
|
||||||
if (!_query) return item.channels
|
|
||||||
const normQuery = _query.toLowerCase()
|
|
||||||
|
|
||||||
return item.channels.filter(c => {
|
|
||||||
return c.hash.includes(normQuery)
|
|
||||||
}) || []
|
|
||||||
}
|
|
||||||
}"
|
|
||||||
x-show="countryChannels.length > 0"
|
|
||||||
x-init="$watch('countryChannels', value => {
|
|
||||||
count = value.length
|
|
||||||
})"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="card-header is-shadowless is-clickable"
|
|
||||||
@click="item.expanded = !item.expanded"
|
|
||||||
>
|
|
||||||
<span class="card-header-title" x-text="`${item.flag} ${item.name}`"></span>
|
|
||||||
<button class="card-header-icon" aria-label="more options">
|
|
||||||
<span class="icon">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="ionicon" viewBox="0 0 512 512">
|
|
||||||
<path
|
|
||||||
x-show="!item.expanded"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="48"
|
|
||||||
d="M112 184l144 144 144-144"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
x-show="item.expanded"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="48"
|
|
||||||
d="M112 328l144-144 144 144"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-content" x-show="item.expanded || (count > 0 && _query.length)">
|
|
||||||
<div class="table-container">
|
|
||||||
<table class="table" style="min-width: 100%">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th></th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>TVG-ID</th>
|
|
||||||
<th>EPG</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<template x-for="channel in countryChannels">
|
|
||||||
<tr>
|
|
||||||
<td class="is-vcentered" style="min-width: 150px; text-align: center">
|
|
||||||
<img
|
|
||||||
loading="lazy"
|
|
||||||
x-show="channel.logo"
|
|
||||||
:src="channel.logo"
|
|
||||||
style="max-width: 100px; max-height: 50px; vertical-align: middle"
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
<td class="is-vcentered" nowrap>
|
|
||||||
<template x-for="name in channel.name">
|
|
||||||
<p x-text="name"></p>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td class="is-vcentered" nowrap>
|
|
||||||
<code x-text="channel.id"></code>
|
|
||||||
</td>
|
|
||||||
<td class="is-vcentered">
|
|
||||||
<template x-for="guide in channel.guides">
|
|
||||||
<p><code style="white-space: nowrap" x-text="guide"></code></p>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue