11const API = 'https://raw.githubusercontent.com/AirwaveDB/api/main/v1' ;
2+ const PAGE_SIZE = 100 ;
23
34const country = document . querySelector ( '#country' ) ;
45const search = document . querySelector ( '#search' ) ;
56const type = document . querySelector ( '#type' ) ;
67const status = document . querySelector ( '#status' ) ;
78const stationsEl = document . querySelector ( '#stations' ) ;
89const summary = document . querySelector ( '#summary' ) ;
10+ const more = document . querySelector ( '#more' ) ;
911const template = document . querySelector ( '#station-template' ) ;
1012
1113let current = [ ] ;
14+ let visibleCount = PAGE_SIZE ;
1215let countryNames ;
1316
1417try {
@@ -41,19 +44,24 @@ function searchable(station) {
4144 ] . filter ( Boolean ) . join ( ' ' ) . toLocaleLowerCase ( ) ;
4245}
4346
44- function render ( ) {
47+ function filteredStations ( ) {
4548 const query = search . value . trim ( ) . toLocaleLowerCase ( ) ;
4649 const wantedType = type . value ;
47- const filtered = current . filter ( ( station ) => {
50+ return current . filter ( ( station ) => {
4851 if ( wantedType && station . type !== wantedType ) return false ;
4952 if ( query && ! searchable ( station ) . includes ( query ) ) return false ;
5053 return true ;
5154 } ) ;
55+ }
56+
57+ function render ( ) {
58+ const filtered = filteredStations ( ) ;
59+ const visible = filtered . slice ( 0 , visibleCount ) ;
5260
5361 stationsEl . replaceChildren ( ) ;
5462 const fragment = document . createDocumentFragment ( ) ;
5563
56- for ( const station of filtered ) {
64+ for ( const station of visible ) {
5765 const node = template . content . firstElementChild . cloneNode ( true ) ;
5866 node . querySelector ( '.station-name' ) . textContent = station . name ;
5967
@@ -68,7 +76,8 @@ function render() {
6876 node . querySelector ( '.station-meta' ) . textContent = meta . join ( ' · ' ) ;
6977
7078 const tags = node . querySelector ( '.station-tags' ) ;
71- for ( const value of [ ...( station . categories || [ ] ) , ...( station . tags || [ ] ) . slice ( 0 , 5 ) ] ) {
79+ const tagValues = [ ...new Set ( [ ...( station . categories || [ ] ) , ...( station . tags || [ ] ) ] ) ] . slice ( 0 , 8 ) ;
80+ for ( const value of tagValues ) {
7281 const span = document . createElement ( 'span' ) ;
7382 span . textContent = value ;
7483 tags . append ( span ) ;
@@ -95,6 +104,7 @@ function render() {
95104 option . textContent = fmtStream ( stream ) ;
96105 select . append ( option ) ;
97106 } ) ;
107+
98108 play . addEventListener ( 'click' , async ( ) => {
99109 const stream = streams [ Number ( select . value ) || 0 ] ;
100110 if ( ! stream ) return ;
@@ -107,13 +117,26 @@ function render() {
107117 }
108118 } ) ;
109119 player . addEventListener ( 'pause' , ( ) => { if ( ! player . ended ) play . textContent = 'Play' ; } ) ;
120+ player . addEventListener ( 'error' , ( ) => { play . textContent = 'Failed' ; } ) ;
110121 }
111122
112123 fragment . append ( node ) ;
113124 }
114125
115126 stationsEl . append ( fragment ) ;
116- status . textContent = `${ filtered . length . toLocaleString ( ) } station${ filtered . length === 1 ? '' : 's' } ` ;
127+
128+ const shown = visible . length ;
129+ status . textContent = filtered . length
130+ ? `Showing ${ shown . toLocaleString ( ) } of ${ filtered . length . toLocaleString ( ) } station${ filtered . length === 1 ? '' : 's' } `
131+ : 'No stations match these filters' ;
132+
133+ more . hidden = shown >= filtered . length ;
134+ more . textContent = `Show ${ Math . min ( PAGE_SIZE , filtered . length - shown ) . toLocaleString ( ) } more` ;
135+ }
136+
137+ function resetAndRender ( ) {
138+ visibleCount = PAGE_SIZE ;
139+ render ( ) ;
117140}
118141
119142async function loadCountry ( code ) {
@@ -123,13 +146,15 @@ async function loadCountry(code) {
123146 status . textContent = '' ;
124147 search . disabled = true ;
125148 type . disabled = true ;
149+ more . hidden = true ;
126150 return ;
127151 }
128152
129153 status . textContent = 'Loading…' ;
130154 stationsEl . replaceChildren ( ) ;
131155 search . disabled = true ;
132156 type . disabled = true ;
157+ more . hidden = true ;
133158
134159 try {
135160 const response = await fetch ( `${ API } /countries/${ code . toLowerCase ( ) } .json` ) ;
@@ -138,7 +163,7 @@ async function loadCountry(code) {
138163 current = data . stations || [ ] ;
139164 search . disabled = false ;
140165 type . disabled = false ;
141- render ( ) ;
166+ resetAndRender ( ) ;
142167 } catch ( error ) {
143168 current = [ ] ;
144169 status . textContent = `Failed to load ${ code } : ${ error . message } ` ;
@@ -170,7 +195,11 @@ async function init() {
170195}
171196
172197country . addEventListener ( 'change' , ( ) => loadCountry ( country . value ) ) ;
173- search . addEventListener ( 'input' , render ) ;
174- type . addEventListener ( 'change' , render ) ;
198+ search . addEventListener ( 'input' , resetAndRender ) ;
199+ type . addEventListener ( 'change' , resetAndRender ) ;
200+ more . addEventListener ( 'click' , ( ) => {
201+ visibleCount += PAGE_SIZE ;
202+ render ( ) ;
203+ } ) ;
175204
176205init ( ) ;
0 commit comments