UI mejorada
This commit is contained in:
@@ -201,7 +201,7 @@ onMounted(() => {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
z-index: -1;
|
z-index: 0; /* Render above body background but below main content */
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
@@ -357,4 +357,4 @@ onMounted(() => {
|
|||||||
will-change: transform;
|
will-change: transform;
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -56,7 +56,15 @@ const hasActiveTrack = computed(() => !!props.currentTrack)
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.main-container {
|
.main-container {
|
||||||
height: 90vh;
|
/* Parametrized viewport sizing */
|
||||||
|
--app-vertical-margin: 10px; /* Top/bottom margin between both components */
|
||||||
|
--playback-controls-height: 60px; /* Adjustable height for PlaybackControls */
|
||||||
|
--tracklist-height: calc(100vh - (var(--app-vertical-margin) * 2) - var(--playback-controls-height));
|
||||||
|
|
||||||
|
/* Container takes full viewport minus vertical margins */
|
||||||
|
height: calc(100vh - (var(--app-vertical-margin) * 2));
|
||||||
|
margin-top: var(--app-vertical-margin);
|
||||||
|
margin-bottom: var(--app-vertical-margin);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
@@ -64,7 +72,9 @@ const hasActiveTrack = computed(() => !!props.currentTrack)
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
/* Keep shadows visible and allow backdrop to show through */
|
||||||
|
overflow: visible;
|
||||||
|
z-index: 1; /* Ensure content sits above Aurora background */
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-header {
|
.app-header {
|
||||||
@@ -186,7 +196,8 @@ const hasActiveTrack = computed(() => !!props.currentTrack)
|
|||||||
.main-container {
|
.main-container {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
height: 92vh;
|
/* Keep same parametrized behavior on mobile */
|
||||||
|
height: calc(100vh - (var(--app-vertical-margin) * 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-content {
|
.header-content {
|
||||||
@@ -275,4 +286,4 @@ const hasActiveTrack = computed(() => !!props.currentTrack)
|
|||||||
background: var(--accent-primary);
|
background: var(--accent-primary);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -65,6 +65,8 @@ const cycleRepeat = () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.playback-controls {
|
.playback-controls {
|
||||||
|
/* Allow parent to control overall height budget */
|
||||||
|
height: var(--playback-controls-height, auto);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -101,4 +103,4 @@ const cycleRepeat = () => {
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -78,6 +78,8 @@ const handleTrackClick = (track, index) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.track-list {
|
.track-list {
|
||||||
|
/* Cap height so sum with PlaybackControls fits 100vh; flex handles actual sizing */
|
||||||
|
max-height: var(--tracklist-height, none);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
background: rgba(255, 255, 255, 0.05);
|
background: rgba(255, 255, 255, 0.05);
|
||||||
@@ -277,4 +279,4 @@ const handleTrackClick = (track, index) => {
|
|||||||
gap: 6px;
|
gap: 6px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ services:
|
|||||||
container_name: repodructor
|
container_name: repodructor
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
# Mount manually mounted NAS directory
|
# Mount music directory from server
|
||||||
- /srv/repodructor/musica:/app/public/music:ro
|
- /srv/repodructor/musica:/app/public/music:ro
|
||||||
environment:
|
environment:
|
||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
// Server configuration for proxy compatibility
|
// Server configuration for proxy compatibility
|
||||||
devServer: {
|
devServer: {
|
||||||
host: '0.0.0.0',
|
host: process.env.NUXT_HOST || '0.0.0.0',
|
||||||
port: 3000,
|
port: process.env.NUXT_PORT ? Number(process.env.NUXT_PORT) : 3000,
|
||||||
https: false
|
https: false
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -26,13 +26,12 @@ export default defineNuxtConfig({
|
|||||||
// Vite configuration for HMR through proxy
|
// Vite configuration for HMR through proxy
|
||||||
vite: {
|
vite: {
|
||||||
server: {
|
server: {
|
||||||
hmr: {
|
// Configure HMR via env when developing behind HTTPS proxy
|
||||||
// Use proxy host instead of direct connection
|
hmr: process.env.NODE_ENV !== 'production' ? {
|
||||||
host: 'musica.nucleoriofrio.com',
|
host: process.env.HMR_HOST || undefined,
|
||||||
// Use default HTTPS port (443) through proxy
|
clientPort: process.env.HMR_CLIENT_PORT ? Number(process.env.HMR_CLIENT_PORT) : undefined,
|
||||||
clientPort: 443,
|
protocol: process.env.HMR_PROTOCOL || undefined
|
||||||
protocol: 'wss'
|
} : undefined
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -139,7 +138,7 @@ export default defineNuxtConfig({
|
|||||||
// Runtime configuration
|
// Runtime configuration
|
||||||
runtimeConfig: {
|
runtimeConfig: {
|
||||||
public: {
|
public: {
|
||||||
musicPath: '/music'
|
musicPath: process.env.NUXT_PUBLIC_MUSIC_PATH || '/music'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,18 +16,23 @@ export default defineEventHandler(async (event) => {
|
|||||||
const headers = getHeaders(event)
|
const headers = getHeaders(event)
|
||||||
const realIP = headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown'
|
const realIP = headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown'
|
||||||
console.log(`[MUSIC API] Request from ${realIP} for file: ${filename}`)
|
console.log(`[MUSIC API] Request from ${realIP} for file: ${filename}`)
|
||||||
|
console.log('Original filename bytes:', [...filename].map(c => c.charCodeAt(0)))
|
||||||
|
|
||||||
// Decode the filename
|
// Decode the filename
|
||||||
try {
|
try {
|
||||||
filename = decodeURIComponent(filename)
|
filename = decodeURIComponent(filename)
|
||||||
console.log('Decoded filename:', filename)
|
console.log('Decoded filename:', filename)
|
||||||
|
console.log('Decoded filename bytes:', [...filename].map(c => c.charCodeAt(0)))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error decoding filename:', error)
|
console.error('Error decoding filename:', error)
|
||||||
// If decoding fails, use original filename
|
// If decoding fails, use original filename
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const musicDir = join(process.cwd(), 'public', 'music')
|
const config = useRuntimeConfig()
|
||||||
|
const defaultPublicPath = config.public?.musicPath || '/music'
|
||||||
|
const publicRel = defaultPublicPath.replace(/^\//, '')
|
||||||
|
const musicDir = process.env.MUSIC_DIR || join(process.cwd(), 'public', publicRel)
|
||||||
const filePath = join(musicDir, filename)
|
const filePath = join(musicDir, filename)
|
||||||
|
|
||||||
// Security check: ensure the file is within the music directory
|
// Security check: ensure the file is within the music directory
|
||||||
@@ -41,7 +46,16 @@ export default defineEventHandler(async (event) => {
|
|||||||
// Check if file exists
|
// Check if file exists
|
||||||
try {
|
try {
|
||||||
await fs.access(filePath)
|
await fs.access(filePath)
|
||||||
} catch {
|
console.log('File found successfully:', filePath)
|
||||||
|
} catch (error) {
|
||||||
|
console.log('File NOT found:', filePath)
|
||||||
|
console.log('Directory contents:')
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(musicDir)
|
||||||
|
files.forEach(file => console.log(' -', file))
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Cannot read directory:', e)
|
||||||
|
}
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
statusMessage: 'File not found'
|
statusMessage: 'File not found'
|
||||||
@@ -100,4 +114,4 @@ export default defineEventHandler(async (event) => {
|
|||||||
statusMessage: 'Failed to serve music file'
|
statusMessage: 'Failed to serve music file'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ export default defineEventHandler(async (event) => {
|
|||||||
const realIP = headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown'
|
const realIP = headers['x-real-ip'] || headers['x-forwarded-for'] || 'unknown'
|
||||||
console.log(`[MUSIC API] Music list request from ${realIP}`)
|
console.log(`[MUSIC API] Music list request from ${realIP}`)
|
||||||
|
|
||||||
const musicDir = join(process.cwd(), 'public', 'music')
|
const config = useRuntimeConfig()
|
||||||
|
const defaultPublicPath = config.public?.musicPath || '/music'
|
||||||
|
const publicRel = defaultPublicPath.replace(/^\//, '')
|
||||||
|
const musicDir = process.env.MUSIC_DIR || join(process.cwd(), 'public', publicRel)
|
||||||
|
|
||||||
// Check if music directory exists
|
// Check if music directory exists
|
||||||
try {
|
try {
|
||||||
@@ -54,4 +57,4 @@ export default defineEventHandler(async (event) => {
|
|||||||
statusMessage: 'Failed to load music files'
|
statusMessage: 'Failed to load music files'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user