Files
wp-fedistream/assets/js/library.js

555 lines
14 KiB
JavaScript
Raw Permalink Normal View History

/**
* FediStream Library JavaScript
*
* @package WP_FediStream
*/
( function( $ ) {
'use strict';
const Library = {
currentTab: 'favorites',
currentPage: { favorites: 1, artists: 1, history: 1 },
currentFilter: 'all',
isLoading: false,
init: function() {
this.bindEvents();
this.loadInitialTab();
},
bindEvents: function() {
const self = this;
// Tab navigation.
$( '.fedistream-library-nav .tab-btn' ).on( 'click', function() {
const tab = $( this ).data( 'tab' );
self.switchTab( tab );
} );
// Filter change.
$( '.fedistream-library-filters .filter-type' ).on( 'change', function() {
self.currentFilter = $( this ).val();
self.currentPage.favorites = 1;
self.loadFavorites();
} );
// Clear history.
$( '.btn-clear-history' ).on( 'click', function() {
self.clearHistory();
} );
// Pagination clicks.
$( document ).on( 'click', '.library-pagination .page-btn', function() {
const page = $( this ).data( 'page' );
const tab = $( this ).closest( '.library-pagination' ).data( 'tab' );
self.goToPage( tab, page );
} );
// Unfavorite button.
$( document ).on( 'click', '.unfavorite-btn', function() {
const btn = $( this );
const contentType = btn.data( 'content-type' );
const contentId = btn.data( 'content-id' );
self.toggleFavorite( contentType, contentId, btn.closest( '.library-item' ) );
} );
// Unfollow button.
$( document ).on( 'click', '.unfollow-btn', function() {
const btn = $( this );
const artistId = btn.data( 'artist-id' );
self.toggleFollow( artistId, btn.closest( '.library-item' ) );
} );
// Play button.
$( document ).on( 'click', '.play-btn', function( e ) {
e.preventDefault();
const trackId = $( this ).data( 'track-id' );
self.playTrack( trackId );
} );
},
loadInitialTab: function() {
const initialTab = $( '.fedistream-library' ).data( 'initial-tab' ) || 'favorites';
this.switchTab( initialTab );
},
switchTab: function( tab ) {
this.currentTab = tab;
// Update nav.
$( '.fedistream-library-nav .tab-btn' ).removeClass( 'active' );
$( '.fedistream-library-nav .tab-btn[data-tab="' + tab + '"]' ).addClass( 'active' );
// Update content.
$( '.tab-content' ).removeClass( 'active' );
$( '#tab-' + tab ).addClass( 'active' );
// Show/hide filters.
if ( tab === 'favorites' ) {
$( '.fedistream-library-filters' ).show();
} else {
$( '.fedistream-library-filters' ).hide();
}
// Load content.
this.loadTabContent( tab );
},
loadTabContent: function( tab ) {
switch ( tab ) {
case 'favorites':
this.loadFavorites();
break;
case 'artists':
this.loadArtists();
break;
case 'history':
this.loadHistory();
break;
}
},
loadFavorites: function() {
const self = this;
if ( this.isLoading ) {
return;
}
this.showLoading();
$.ajax( {
url: fedistreamLibrary.ajaxUrl,
type: 'POST',
data: {
action: 'fedistream_get_library',
nonce: fedistreamLibrary.nonce,
type: this.currentFilter,
page: this.currentPage.favorites
},
success: function( response ) {
self.hideLoading();
if ( response.success ) {
self.renderFavorites( response.data );
} else {
self.showError( response.data.message );
}
},
error: function() {
self.hideLoading();
self.showError( fedistreamLibrary.i18n.error );
}
} );
},
loadArtists: function() {
const self = this;
if ( this.isLoading ) {
return;
}
this.showLoading();
$.ajax( {
url: fedistreamLibrary.ajaxUrl,
type: 'POST',
data: {
action: 'fedistream_get_followed_artists',
nonce: fedistreamLibrary.nonce,
page: this.currentPage.artists
},
success: function( response ) {
self.hideLoading();
if ( response.success ) {
self.renderArtists( response.data );
} else {
self.showError( response.data.message );
}
},
error: function() {
self.hideLoading();
self.showError( fedistreamLibrary.i18n.error );
}
} );
},
loadHistory: function() {
const self = this;
if ( this.isLoading ) {
return;
}
this.showLoading();
$.ajax( {
url: fedistreamLibrary.ajaxUrl,
type: 'POST',
data: {
action: 'fedistream_get_history',
nonce: fedistreamLibrary.nonce,
page: this.currentPage.history
},
success: function( response ) {
self.hideLoading();
if ( response.success ) {
self.renderHistory( response.data );
} else {
self.showError( response.data.message );
}
},
error: function() {
self.hideLoading();
self.showError( fedistreamLibrary.i18n.error );
}
} );
},
renderFavorites: function( data ) {
const container = $( '.favorites-grid' );
container.empty();
if ( ! data.items || data.items.length === 0 ) {
container.html( '<p class="empty-message">' + fedistreamLibrary.i18n.noFavorites + '</p>' );
$( '.library-pagination[data-tab="favorites"]' ).empty();
return;
}
data.items.forEach( function( item ) {
container.append( this.createFavoriteItem( item ) );
}, this );
this.renderPagination( 'favorites', data );
},
renderArtists: function( data ) {
const container = $( '.artists-grid' );
container.empty();
if ( ! data.artists || data.artists.length === 0 ) {
container.html( '<p class="empty-message">' + fedistreamLibrary.i18n.noArtists + '</p>' );
$( '.library-pagination[data-tab="artists"]' ).empty();
return;
}
data.artists.forEach( function( artist ) {
container.append( this.createArtistItem( artist ) );
}, this );
this.renderPagination( 'artists', data );
},
renderHistory: function( data ) {
const container = $( '.history-list' );
container.empty();
if ( ! data.tracks || data.tracks.length === 0 ) {
container.html( '<p class="empty-message">' + fedistreamLibrary.i18n.noHistory + '</p>' );
$( '.library-pagination[data-tab="history"]' ).empty();
return;
}
data.tracks.forEach( function( track ) {
container.append( this.createHistoryItem( track ) );
}, this );
this.renderPagination( 'history', data );
},
createFavoriteItem: function( item ) {
const thumbnail = item.thumbnail
? '<img src="' + item.thumbnail + '" alt="' + this.escapeHtml( item.title ) + '">'
: '<div class="placeholder-thumbnail"><span class="dashicons dashicons-format-audio"></span></div>';
const playBtn = item.type === 'track'
? '<button class="play-btn" data-track-id="' + item.id + '"><span class="dashicons dashicons-controls-play"></span></button>'
: '';
const artistInfo = item.artist
? '<p class="item-artist">' + this.escapeHtml( item.artist ) + '</p>'
: '';
let metaInfo = '';
if ( item.type === 'track' && item.duration ) {
metaInfo = '<p class="item-duration">' + this.formatDuration( item.duration ) + '</p>';
} else if ( item.type === 'album' && item.track_count ) {
metaInfo = '<p class="item-tracks">' + item.track_count + ' tracks</p>';
}
return `
<div class="library-item favorite-item" data-type="${item.type}" data-id="${item.id}">
<div class="item-thumbnail">
${thumbnail}
${playBtn}
</div>
<div class="item-info">
<h4 class="item-title">
<a href="${item.permalink}">${this.escapeHtml( item.title )}</a>
</h4>
${artistInfo}
${metaInfo}
</div>
<div class="item-actions">
<button class="unfavorite-btn" data-content-type="${item.type}" data-content-id="${item.id}" title="Remove from library">
<span class="dashicons dashicons-heart"></span>
</button>
</div>
</div>
`;
},
createArtistItem: function( artist ) {
const thumbnail = artist.thumbnail
? '<img src="' + artist.thumbnail + '" alt="' + this.escapeHtml( artist.name ) + '">'
: '<div class="placeholder-thumbnail"><span class="dashicons dashicons-admin-users"></span></div>';
const typeLabel = artist.type === 'band' ? 'Band' : 'Artist';
return `
<div class="library-item artist-item" data-id="${artist.id}">
<div class="item-thumbnail artist-avatar">
${thumbnail}
</div>
<div class="item-info">
<h4 class="item-title">
<a href="${artist.permalink}">${this.escapeHtml( artist.name )}</a>
</h4>
<p class="item-type">${typeLabel}</p>
</div>
<div class="item-actions">
<button class="unfollow-btn" data-artist-id="${artist.id}" title="Unfollow">
<span class="dashicons dashicons-minus"></span>
<span class="button-text">Unfollow</span>
</button>
</div>
</div>
`;
},
createHistoryItem: function( track ) {
const thumbnail = track.thumbnail
? '<img src="' + track.thumbnail + '" alt="' + this.escapeHtml( track.title ) + '">'
: '<div class="placeholder-thumbnail"><span class="dashicons dashicons-format-audio"></span></div>';
const artistInfo = track.artist
? '<p class="item-artist">' + this.escapeHtml( track.artist ) + '</p>'
: '';
const duration = track.duration
? '<span class="item-duration">' + this.formatDuration( track.duration ) + '</span>'
: '';
return `
<div class="library-item history-item" data-id="${track.id}">
<div class="item-thumbnail">
${thumbnail}
<button class="play-btn" data-track-id="${track.id}">
<span class="dashicons dashicons-controls-play"></span>
</button>
</div>
<div class="item-info">
<h4 class="item-title">
<a href="${track.permalink}">${this.escapeHtml( track.title )}</a>
</h4>
${artistInfo}
<p class="item-played">${this.formatPlayedTime( track.played_at )}</p>
</div>
<div class="item-meta">
${duration}
</div>
</div>
`;
},
renderPagination: function( tab, data ) {
const container = $( '.library-pagination[data-tab="' + tab + '"]' );
container.empty();
if ( data.total_pages <= 1 ) {
return;
}
let html = '<div class="pagination-controls">';
// Previous button.
if ( data.page > 1 ) {
html += '<button class="page-btn prev" data-page="' + ( data.page - 1 ) + '">&laquo; Previous</button>';
}
// Page numbers.
html += '<span class="page-info">Page ' + data.page + ' of ' + data.total_pages + '</span>';
// Next button.
if ( data.page < data.total_pages ) {
html += '<button class="page-btn next" data-page="' + ( data.page + 1 ) + '">Next &raquo;</button>';
}
html += '</div>';
container.html( html );
},
goToPage: function( tab, page ) {
this.currentPage[ tab ] = page;
this.loadTabContent( tab );
},
toggleFavorite: function( contentType, contentId, element ) {
const self = this;
$.ajax( {
url: fedistreamLibrary.ajaxUrl,
type: 'POST',
data: {
action: 'fedistream_toggle_favorite',
nonce: fedistreamLibrary.nonce,
content_type: contentType,
content_id: contentId
},
success: function( response ) {
if ( response.success && response.data.action === 'removed' ) {
element.fadeOut( 300, function() {
$( this ).remove();
self.updateFavoriteCount();
} );
}
},
error: function() {
self.showError( fedistreamLibrary.i18n.error );
}
} );
},
toggleFollow: function( artistId, element ) {
const self = this;
$.ajax( {
url: fedistreamLibrary.ajaxUrl,
type: 'POST',
data: {
action: 'fedistream_toggle_follow',
nonce: fedistreamLibrary.nonce,
artist_id: artistId
},
success: function( response ) {
if ( response.success && response.data.action === 'unfollowed' ) {
element.fadeOut( 300, function() {
$( this ).remove();
self.updateFollowingCount();
} );
}
},
error: function() {
self.showError( fedistreamLibrary.i18n.error );
}
} );
},
clearHistory: function() {
const self = this;
if ( ! confirm( fedistreamLibrary.i18n.confirmClear ) ) {
return;
}
$.ajax( {
url: fedistreamLibrary.ajaxUrl,
type: 'POST',
data: {
action: 'fedistream_clear_history',
nonce: fedistreamLibrary.nonce
},
success: function( response ) {
if ( response.success ) {
$( '.history-list' ).html( '<p class="empty-message">' + fedistreamLibrary.i18n.noHistory + '</p>' );
$( '.library-pagination[data-tab="history"]' ).empty();
} else {
self.showError( response.data.message );
}
},
error: function() {
self.showError( fedistreamLibrary.i18n.error );
}
} );
},
playTrack: function( trackId ) {
// Trigger global player if available.
if ( window.FediStreamPlayer && typeof window.FediStreamPlayer.playTrack === 'function' ) {
window.FediStreamPlayer.playTrack( trackId );
} else {
// Fallback: redirect to track page.
window.location.href = '?p=' + trackId;
}
},
updateFavoriteCount: function() {
const count = $( '.favorites-grid .library-item' ).length;
$( '.tab-btn[data-tab="favorites"] .count' ).text( count );
},
updateFollowingCount: function() {
const count = $( '.artists-grid .library-item' ).length;
$( '.tab-btn[data-tab="artists"] .count' ).text( count );
},
showLoading: function() {
this.isLoading = true;
$( '.fedistream-library-loading' ).show();
},
hideLoading: function() {
this.isLoading = false;
$( '.fedistream-library-loading' ).hide();
},
showError: function( message ) {
alert( message );
},
formatDuration: function( seconds ) {
const mins = Math.floor( seconds / 60 );
const secs = seconds % 60;
return mins + ':' + ( secs < 10 ? '0' : '' ) + secs;
},
formatPlayedTime: function( dateString ) {
const date = new Date( dateString );
const now = new Date();
const diff = Math.floor( ( now - date ) / 1000 );
if ( diff < 60 ) {
return 'Just now';
} else if ( diff < 3600 ) {
const mins = Math.floor( diff / 60 );
return mins + ' minute' + ( mins !== 1 ? 's' : '' ) + ' ago';
} else if ( diff < 86400 ) {
const hours = Math.floor( diff / 3600 );
return hours + ' hour' + ( hours !== 1 ? 's' : '' ) + ' ago';
} else {
const days = Math.floor( diff / 86400 );
return days + ' day' + ( days !== 1 ? 's' : '' ) + ' ago';
}
},
escapeHtml: function( text ) {
const div = document.createElement( 'div' );
div.textContent = text;
return div.innerHTML;
}
};
// Initialize on document ready.
$( document ).ready( function() {
if ( $( '.fedistream-library' ).length ) {
Library.init();
}
} );
} )( jQuery );