Spaces:
Sleeping
Sleeping
AdityaAdaki
commited on
Commit
·
7bda8d1
1
Parent(s):
e1cef6b
css update mobile
Browse files- app.py +4 -1
- static/css/style.css +101 -0
- static/js/main.js +91 -31
- templates/index.html +11 -6
app.py
CHANGED
|
@@ -14,7 +14,7 @@ import mutagen.flac
|
|
| 14 |
import mutagen.oggvorbis
|
| 15 |
from werkzeug.utils import secure_filename
|
| 16 |
|
| 17 |
-
app = Flask(__name__)
|
| 18 |
# Create a temporary directory for uploads
|
| 19 |
TEMP_UPLOAD_DIR = tempfile.mkdtemp()
|
| 20 |
app.config['UPLOAD_FOLDER'] = TEMP_UPLOAD_DIR
|
|
@@ -206,6 +206,9 @@ def upload_file():
|
|
| 206 |
'error': 'Invalid file type'
|
| 207 |
})
|
| 208 |
|
|
|
|
|
|
|
|
|
|
| 209 |
return jsonify({
|
| 210 |
'success': True,
|
| 211 |
'files': results
|
|
|
|
| 14 |
import mutagen.oggvorbis
|
| 15 |
from werkzeug.utils import secure_filename
|
| 16 |
|
| 17 |
+
app = Flask(__name__, static_folder='static')
|
| 18 |
# Create a temporary directory for uploads
|
| 19 |
TEMP_UPLOAD_DIR = tempfile.mkdtemp()
|
| 20 |
app.config['UPLOAD_FOLDER'] = TEMP_UPLOAD_DIR
|
|
|
|
| 206 |
'error': 'Invalid file type'
|
| 207 |
})
|
| 208 |
|
| 209 |
+
# Debug log to check the response
|
| 210 |
+
app.logger.debug(f"Upload response: {results}")
|
| 211 |
+
|
| 212 |
return jsonify({
|
| 213 |
'success': True,
|
| 214 |
'files': results
|
static/css/style.css
CHANGED
|
@@ -886,4 +886,105 @@ input[type="file"] {
|
|
| 886 |
display: block;
|
| 887 |
opacity: 1;
|
| 888 |
transform: translateY(0);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 889 |
}
|
|
|
|
| 886 |
display: block;
|
| 887 |
opacity: 1;
|
| 888 |
transform: translateY(0);
|
| 889 |
+
}
|
| 890 |
+
|
| 891 |
+
/* Add these styles for the visualization type dropdown */
|
| 892 |
+
.viz-type-dropdown {
|
| 893 |
+
position: fixed;
|
| 894 |
+
top: 60px;
|
| 895 |
+
right: -200px;
|
| 896 |
+
background: var(--surface-color);
|
| 897 |
+
backdrop-filter: blur(12px);
|
| 898 |
+
border-radius: 8px;
|
| 899 |
+
padding: 8px;
|
| 900 |
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
| 901 |
+
transition: right 0.3s ease;
|
| 902 |
+
z-index: 99;
|
| 903 |
+
width: 200px;
|
| 904 |
+
}
|
| 905 |
+
|
| 906 |
+
.viz-type-dropdown.visible {
|
| 907 |
+
right: 16px;
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
.viz-type-options {
|
| 911 |
+
display: flex;
|
| 912 |
+
flex-direction: column;
|
| 913 |
+
gap: 8px;
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
.viz-type-options button {
|
| 917 |
+
background: transparent;
|
| 918 |
+
border: none;
|
| 919 |
+
color: var(--text-primary);
|
| 920 |
+
padding: 8px 16px;
|
| 921 |
+
text-align: left;
|
| 922 |
+
border-radius: 4px;
|
| 923 |
+
cursor: pointer;
|
| 924 |
+
transition: background-color 0.2s;
|
| 925 |
+
width: 100%;
|
| 926 |
+
}
|
| 927 |
+
|
| 928 |
+
.viz-type-options button:hover {
|
| 929 |
+
background: rgba(255, 255, 255, 0.1);
|
| 930 |
+
}
|
| 931 |
+
|
| 932 |
+
.viz-type-options button.active {
|
| 933 |
+
background: var(--primary-color);
|
| 934 |
+
color: white;
|
| 935 |
+
}
|
| 936 |
+
|
| 937 |
+
/* Update mobile styles */
|
| 938 |
+
@media screen and (max-width: 768px) {
|
| 939 |
+
.header-controls {
|
| 940 |
+
gap: 4px;
|
| 941 |
+
}
|
| 942 |
+
|
| 943 |
+
.header-btn {
|
| 944 |
+
width: 32px;
|
| 945 |
+
height: 32px;
|
| 946 |
+
}
|
| 947 |
+
|
| 948 |
+
.viz-type-dropdown {
|
| 949 |
+
top: 50px;
|
| 950 |
+
width: calc(100% - 32px);
|
| 951 |
+
right: -100%;
|
| 952 |
+
}
|
| 953 |
+
|
| 954 |
+
.viz-type-dropdown.visible {
|
| 955 |
+
right: 16px;
|
| 956 |
+
}
|
| 957 |
+
}
|
| 958 |
+
|
| 959 |
+
/* Fix the main content positioning */
|
| 960 |
+
.main-content {
|
| 961 |
+
position: fixed;
|
| 962 |
+
top: 60px; /* Adjust based on header height */
|
| 963 |
+
right: -420px;
|
| 964 |
+
width: 400px;
|
| 965 |
+
max-height: calc(100vh - 180px); /* Adjust for header and player */
|
| 966 |
+
z-index: 10;
|
| 967 |
+
overflow: visible;
|
| 968 |
+
transition: right 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
| 969 |
+
padding: 16px;
|
| 970 |
+
box-sizing: border-box;
|
| 971 |
+
}
|
| 972 |
+
|
| 973 |
+
/* Update the mobile layout */
|
| 974 |
+
@media screen and (max-width: 768px) {
|
| 975 |
+
.main-content {
|
| 976 |
+
width: 100%;
|
| 977 |
+
right: -100%;
|
| 978 |
+
padding: 8px;
|
| 979 |
+
}
|
| 980 |
+
|
| 981 |
+
.main-content.visible {
|
| 982 |
+
right: 0;
|
| 983 |
+
}
|
| 984 |
+
|
| 985 |
+
.playlist-container,
|
| 986 |
+
.upload-area {
|
| 987 |
+
margin: 0;
|
| 988 |
+
border-radius: 8px;
|
| 989 |
+
}
|
| 990 |
}
|
static/js/main.js
CHANGED
|
@@ -276,22 +276,30 @@ async function handleFiles(files) {
|
|
| 276 |
const data = await response.json();
|
| 277 |
|
| 278 |
if (data.success) {
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
| 280 |
name: file.filename,
|
| 281 |
url: file.filepath,
|
| 282 |
metadata: file.metadata
|
| 283 |
}));
|
| 284 |
|
|
|
|
|
|
|
|
|
|
| 285 |
createPlaylist();
|
| 286 |
-
if (playlist.length > 0) {
|
| 287 |
playTrack(0);
|
|
|
|
|
|
|
| 288 |
}
|
| 289 |
} else {
|
| 290 |
-
showError(data.error);
|
| 291 |
}
|
| 292 |
} catch (error) {
|
| 293 |
-
showError('Error uploading files');
|
| 294 |
console.error('Upload error:', error);
|
|
|
|
| 295 |
}
|
| 296 |
}
|
| 297 |
|
|
@@ -528,7 +536,7 @@ function updateNowPlayingInfo() {
|
|
| 528 |
|
| 529 |
// Play selected track
|
| 530 |
function playTrack(index) {
|
| 531 |
-
if (index < 0 || index >= playlist.length) return;
|
| 532 |
|
| 533 |
currentTrackIndex = index;
|
| 534 |
const track = playlist[index];
|
|
@@ -537,33 +545,39 @@ function playTrack(index) {
|
|
| 537 |
initAudio();
|
| 538 |
}
|
| 539 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 540 |
// Update playlist UI
|
| 541 |
document.querySelectorAll('.playlist-item').forEach((item, i) => {
|
| 542 |
item.classList.toggle('active', i === index);
|
| 543 |
});
|
| 544 |
|
| 545 |
// Enable controls
|
| 546 |
-
document.getElementById('play-pause')
|
| 547 |
-
document.querySelector('.previous-btn')
|
| 548 |
-
document.querySelector('.next-btn')
|
|
|
|
|
|
|
|
|
|
|
|
|
| 549 |
|
| 550 |
// Update audio source and play
|
| 551 |
audioElement.src = track.url;
|
| 552 |
audioElement.play()
|
| 553 |
.then(() => {
|
| 554 |
isPlaying = true;
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
// Add event listeners for time updates if not already added
|
| 559 |
-
if (!audioElement.onended) {
|
| 560 |
-
audioElement.addEventListener('timeupdate', updateTimeDisplay);
|
| 561 |
-
audioElement.addEventListener('ended', handleTrackEnd);
|
| 562 |
}
|
|
|
|
| 563 |
})
|
| 564 |
.catch(error => {
|
| 565 |
console.error('Error playing track:', error);
|
| 566 |
-
showError('Error playing track');
|
| 567 |
});
|
| 568 |
}
|
| 569 |
|
|
@@ -574,39 +588,85 @@ function setupToggleHandlers() {
|
|
| 574 |
const playlistContainer = document.querySelector('.playlist-container');
|
| 575 |
const uploadToggleBtn = document.querySelector('.upload-toggle-btn');
|
| 576 |
const playlistToggleBtn = document.querySelector('.playlist-toggle-btn');
|
|
|
|
|
|
|
|
|
|
| 577 |
|
| 578 |
// Show playlist by default
|
| 579 |
-
mainContent
|
| 580 |
-
playlistContainer
|
| 581 |
-
playlistToggleBtn
|
| 582 |
|
| 583 |
uploadToggleBtn?.addEventListener('click', () => {
|
| 584 |
-
const isVisible = uploadArea
|
| 585 |
|
| 586 |
// Hide playlist if it's visible
|
| 587 |
-
playlistContainer
|
| 588 |
-
playlistToggleBtn
|
| 589 |
|
| 590 |
// Toggle upload area
|
| 591 |
-
uploadArea
|
| 592 |
-
uploadToggleBtn
|
| 593 |
|
| 594 |
// Show/hide main content
|
| 595 |
-
mainContent
|
| 596 |
});
|
| 597 |
|
| 598 |
playlistToggleBtn?.addEventListener('click', () => {
|
| 599 |
-
const isVisible = playlistContainer
|
| 600 |
|
| 601 |
// Hide upload area if it's visible
|
| 602 |
-
uploadArea
|
| 603 |
-
uploadToggleBtn
|
| 604 |
|
| 605 |
// Toggle playlist
|
| 606 |
-
playlistContainer
|
| 607 |
-
playlistToggleBtn
|
| 608 |
|
| 609 |
// Show/hide main content
|
| 610 |
-
mainContent
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 611 |
});
|
| 612 |
}
|
|
|
|
| 276 |
const data = await response.json();
|
| 277 |
|
| 278 |
if (data.success) {
|
| 279 |
+
// Debug log to check the response
|
| 280 |
+
console.log('Upload response:', data);
|
| 281 |
+
|
| 282 |
+
playlist = data.files.filter(file => file.success).map(file => ({
|
| 283 |
name: file.filename,
|
| 284 |
url: file.filepath,
|
| 285 |
metadata: file.metadata
|
| 286 |
}));
|
| 287 |
|
| 288 |
+
// Debug log to check the playlist
|
| 289 |
+
console.log('Created playlist:', playlist);
|
| 290 |
+
|
| 291 |
createPlaylist();
|
| 292 |
+
if (playlist.length > 0 && playlist[0].url) {
|
| 293 |
playTrack(0);
|
| 294 |
+
} else {
|
| 295 |
+
showError('No valid tracks were uploaded');
|
| 296 |
}
|
| 297 |
} else {
|
| 298 |
+
showError(data.error || 'Upload failed');
|
| 299 |
}
|
| 300 |
} catch (error) {
|
|
|
|
| 301 |
console.error('Upload error:', error);
|
| 302 |
+
showError('Error uploading files');
|
| 303 |
}
|
| 304 |
}
|
| 305 |
|
|
|
|
| 536 |
|
| 537 |
// Play selected track
|
| 538 |
function playTrack(index) {
|
| 539 |
+
if (!playlist || index < 0 || index >= playlist.length) return;
|
| 540 |
|
| 541 |
currentTrackIndex = index;
|
| 542 |
const track = playlist[index];
|
|
|
|
| 545 |
initAudio();
|
| 546 |
}
|
| 547 |
|
| 548 |
+
if (!track || !track.url) {
|
| 549 |
+
console.error('Invalid track or track URL');
|
| 550 |
+
showError('Invalid track data');
|
| 551 |
+
return;
|
| 552 |
+
}
|
| 553 |
+
|
| 554 |
// Update playlist UI
|
| 555 |
document.querySelectorAll('.playlist-item').forEach((item, i) => {
|
| 556 |
item.classList.toggle('active', i === index);
|
| 557 |
});
|
| 558 |
|
| 559 |
// Enable controls
|
| 560 |
+
const playPauseBtn = document.getElementById('play-pause');
|
| 561 |
+
const prevBtn = document.querySelector('.previous-btn');
|
| 562 |
+
const nextBtn = document.querySelector('.next-btn');
|
| 563 |
+
|
| 564 |
+
if (playPauseBtn) playPauseBtn.disabled = false;
|
| 565 |
+
if (prevBtn) prevBtn.disabled = false;
|
| 566 |
+
if (nextBtn) nextBtn.disabled = false;
|
| 567 |
|
| 568 |
// Update audio source and play
|
| 569 |
audioElement.src = track.url;
|
| 570 |
audioElement.play()
|
| 571 |
.then(() => {
|
| 572 |
isPlaying = true;
|
| 573 |
+
if (playPauseBtn) {
|
| 574 |
+
playPauseBtn.innerHTML = '<i class="fas fa-pause"></i>';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
}
|
| 576 |
+
updateNowPlayingInfo();
|
| 577 |
})
|
| 578 |
.catch(error => {
|
| 579 |
console.error('Error playing track:', error);
|
| 580 |
+
showError('Error playing track: ' + error.message);
|
| 581 |
});
|
| 582 |
}
|
| 583 |
|
|
|
|
| 588 |
const playlistContainer = document.querySelector('.playlist-container');
|
| 589 |
const uploadToggleBtn = document.querySelector('.upload-toggle-btn');
|
| 590 |
const playlistToggleBtn = document.querySelector('.playlist-toggle-btn');
|
| 591 |
+
const vizTypeBtn = document.querySelector('.viz-type-btn');
|
| 592 |
+
const vizTypeDropdown = document.querySelector('.viz-type-dropdown');
|
| 593 |
+
const vizTypeOptions = document.querySelectorAll('.viz-type-options button');
|
| 594 |
|
| 595 |
// Show playlist by default
|
| 596 |
+
mainContent?.classList.add('visible');
|
| 597 |
+
playlistContainer?.classList.add('visible');
|
| 598 |
+
playlistToggleBtn?.classList.add('active');
|
| 599 |
|
| 600 |
uploadToggleBtn?.addEventListener('click', () => {
|
| 601 |
+
const isVisible = uploadArea?.classList.contains('visible');
|
| 602 |
|
| 603 |
// Hide playlist if it's visible
|
| 604 |
+
playlistContainer?.classList.remove('visible');
|
| 605 |
+
playlistToggleBtn?.classList.remove('active');
|
| 606 |
|
| 607 |
// Toggle upload area
|
| 608 |
+
uploadArea?.classList.toggle('visible');
|
| 609 |
+
uploadToggleBtn?.classList.toggle('active');
|
| 610 |
|
| 611 |
// Show/hide main content
|
| 612 |
+
mainContent?.classList.toggle('visible', !isVisible || playlistContainer?.classList.contains('visible'));
|
| 613 |
});
|
| 614 |
|
| 615 |
playlistToggleBtn?.addEventListener('click', () => {
|
| 616 |
+
const isVisible = playlistContainer?.classList.contains('visible');
|
| 617 |
|
| 618 |
// Hide upload area if it's visible
|
| 619 |
+
uploadArea?.classList.remove('visible');
|
| 620 |
+
uploadToggleBtn?.classList.remove('active');
|
| 621 |
|
| 622 |
// Toggle playlist
|
| 623 |
+
playlistContainer?.classList.toggle('visible');
|
| 624 |
+
playlistToggleBtn?.classList.toggle('active');
|
| 625 |
|
| 626 |
// Show/hide main content
|
| 627 |
+
mainContent?.classList.toggle('visible', !isVisible || uploadArea?.classList.contains('visible'));
|
| 628 |
+
});
|
| 629 |
+
|
| 630 |
+
// Handle visualization type button click
|
| 631 |
+
vizTypeBtn?.addEventListener('click', (e) => {
|
| 632 |
+
e.stopPropagation(); // Prevent document click from immediately closing
|
| 633 |
+
vizTypeDropdown?.classList.toggle('visible');
|
| 634 |
+
vizTypeBtn?.classList.toggle('active');
|
| 635 |
+
|
| 636 |
+
// Close other dropdowns
|
| 637 |
+
uploadArea?.classList.remove('visible');
|
| 638 |
+
uploadToggleBtn?.classList.remove('active');
|
| 639 |
+
playlistContainer?.classList.remove('visible');
|
| 640 |
+
playlistToggleBtn?.classList.remove('active');
|
| 641 |
+
});
|
| 642 |
+
|
| 643 |
+
// Handle visualization type selection
|
| 644 |
+
vizTypeOptions?.forEach(button => {
|
| 645 |
+
button.addEventListener('click', (e) => {
|
| 646 |
+
e.stopPropagation(); // Prevent document click from closing dropdown
|
| 647 |
+
const type = button.dataset.type;
|
| 648 |
+
|
| 649 |
+
// Update active state
|
| 650 |
+
vizTypeOptions.forEach(btn => btn.classList.remove('active'));
|
| 651 |
+
button.classList.add('active');
|
| 652 |
+
|
| 653 |
+
// Update visualization
|
| 654 |
+
visualizationType = type;
|
| 655 |
+
createVisualization();
|
| 656 |
+
|
| 657 |
+
// Close dropdown
|
| 658 |
+
vizTypeDropdown?.classList.remove('visible');
|
| 659 |
+
vizTypeBtn?.classList.remove('active');
|
| 660 |
+
});
|
| 661 |
+
});
|
| 662 |
+
|
| 663 |
+
// Close dropdown when clicking outside
|
| 664 |
+
document.addEventListener('click', (e) => {
|
| 665 |
+
if (vizTypeBtn && vizTypeDropdown &&
|
| 666 |
+
!vizTypeBtn.contains(e.target) &&
|
| 667 |
+
!vizTypeDropdown.contains(e.target)) {
|
| 668 |
+
vizTypeDropdown.classList.remove('visible');
|
| 669 |
+
vizTypeBtn.classList.remove('active');
|
| 670 |
+
}
|
| 671 |
});
|
| 672 |
}
|
templates/index.html
CHANGED
|
@@ -22,6 +22,9 @@
|
|
| 22 |
<button class="header-btn playlist-toggle-btn" title="Show playlist">
|
| 23 |
<i class="fas fa-list"></i>
|
| 24 |
</button>
|
|
|
|
|
|
|
|
|
|
| 25 |
<button class="header-btn theme-btn" title="Toggle theme">
|
| 26 |
<i class="fas fa-moon"></i>
|
| 27 |
</button>
|
|
@@ -97,18 +100,20 @@
|
|
| 97 |
<input type="range" id="volume" min="0" max="1" step="0.1" value="0.5">
|
| 98 |
</div>
|
| 99 |
</div>
|
| 100 |
-
|
| 101 |
-
<select id="visualization-type">
|
| 102 |
-
<option value="bars">Circular Bars</option>
|
| 103 |
-
<option value="sphere">Sphere</option>
|
| 104 |
-
<option value="particles">Particles</option>
|
| 105 |
-
</select>
|
| 106 |
</div>
|
| 107 |
</div>
|
| 108 |
|
| 109 |
<div class="error-toast"></div>
|
| 110 |
<div class="tooltip"></div>
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
| 113 |
</body>
|
| 114 |
</html>
|
|
|
|
| 22 |
<button class="header-btn playlist-toggle-btn" title="Show playlist">
|
| 23 |
<i class="fas fa-list"></i>
|
| 24 |
</button>
|
| 25 |
+
<button class="header-btn viz-type-btn" title="Change visualization">
|
| 26 |
+
<i class="fas fa-cube"></i>
|
| 27 |
+
</button>
|
| 28 |
<button class="header-btn theme-btn" title="Toggle theme">
|
| 29 |
<i class="fas fa-moon"></i>
|
| 30 |
</button>
|
|
|
|
| 100 |
<input type="range" id="volume" min="0" max="1" step="0.1" value="0.5">
|
| 101 |
</div>
|
| 102 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
</div>
|
| 104 |
</div>
|
| 105 |
|
| 106 |
<div class="error-toast"></div>
|
| 107 |
<div class="tooltip"></div>
|
| 108 |
|
| 109 |
+
<div class="viz-type-dropdown">
|
| 110 |
+
<div class="viz-type-options">
|
| 111 |
+
<button data-type="bars" class="active">Circular Bars</button>
|
| 112 |
+
<button data-type="sphere">Sphere</button>
|
| 113 |
+
<button data-type="particles">Particles</button>
|
| 114 |
+
</div>
|
| 115 |
+
</div>
|
| 116 |
+
|
| 117 |
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
|
| 118 |
</body>
|
| 119 |
</html>
|