Update website

- faster site loading
- added more channels
This commit is contained in:
Aleksandr Statciuk 2022-01-25 20:51:23 +03:00
parent 7fb8250025
commit 3f5ff5b671
2 changed files with 208 additions and 193 deletions

View file

@ -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}}&nbsp;{{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')

View file

@ -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>