feat/create_wamp
This commit is contained in:
parent
a9a586eb0e
commit
9edb55772d
|
@ -1,55 +1,342 @@
|
|||
<script setup>
|
||||
import BaseWindow from '~/components/BaseWindow.vue';
|
||||
|
||||
defineProps({
|
||||
windowData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'close',
|
||||
'minimize',
|
||||
'bring-to-front',
|
||||
'update-position',
|
||||
'update-dimensions',
|
||||
]);
|
||||
|
||||
const handleClose = () => emit('close');
|
||||
const handleMinimize = () => emit('minimize');
|
||||
const handleBringToFront = () => emit('bring-to-front');
|
||||
const handleUpdatePosition = (newPosition) => emit('update-position', newPosition);
|
||||
const handleUpdateDimensions = (newDimensions) => emit('update-dimensions', newDimensions);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BaseWindow
|
||||
:window-data="windowData"
|
||||
@close="handleClose"
|
||||
@minimize="handleMinimize"
|
||||
@bring-to-front="handleBringToFront"
|
||||
@update-position="handleUpdatePosition"
|
||||
@update-dimensions="handleUpdateDimensions"
|
||||
>
|
||||
<div class="winamp-content">
|
||||
<!-- Winamp content goes here -->
|
||||
<p>This is the Winamp player.</p>
|
||||
<p>You can add your music player UI here.</p>
|
||||
<div class="winamp-window main-window" @mousedown="startDrag">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-text">WINAMP</div>
|
||||
<div class="title-bar-controls">
|
||||
<button aria-label="Minimize"></button>
|
||||
<button aria-label="Maximize"></button>
|
||||
<button aria-label="Close"></button>
|
||||
</div>
|
||||
</div>
|
||||
</BaseWindow>
|
||||
<div class="main-body">
|
||||
<div class="top-panel">
|
||||
<div class="visualizer"></div>
|
||||
<div class="track-display">
|
||||
<div class="track-title-marquee">
|
||||
<span>{{ currentTrack ? `${currentTrackIndex + 1}. ${currentTrack.artist} - ${currentTrack.title}` : 'No Track Loaded' }}</span>
|
||||
</div>
|
||||
<div class="time-display">{{ formatTime(currentTime) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="middle-panel">
|
||||
<div class="kbps">
|
||||
{{ Math.round(audioRate / 1000) || '0' }}
|
||||
<span>kbps</span>
|
||||
</div>
|
||||
<div class="khz">
|
||||
{{ Math.round(audioSampleRate / 1000) || '0' }}
|
||||
<span>kHz</span>
|
||||
</div>
|
||||
<div class="mono-stereo">{{ stereoStatus }}</div>
|
||||
</div>
|
||||
<div class="bottom-panel">
|
||||
<div class="control-buttons">
|
||||
<button class="control-btn prev" @click="$emit('prev')"></button>
|
||||
<button class="control-btn play" @click="$emit('play-pause')" :class="{ 'pause-active': isPlaying }"></button>
|
||||
<button class="control-btn stop" @click="$emit('stop')"></button>
|
||||
<button class="control-btn next" @click="$emit('next')"></button>
|
||||
</div>
|
||||
<div class="volume-slider-container">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
:value="volume"
|
||||
class="slider volume-slider"
|
||||
@input="$emit('update:volume', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
<div class="progress-bar-container">
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
:max="duration"
|
||||
:value="currentTime"
|
||||
class="slider progress-slider"
|
||||
@input="$emit('seek', $event.target.value)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, computed } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
currentTrack: Object,
|
||||
currentTrackIndex: Number,
|
||||
isPlaying: Boolean,
|
||||
currentTime: Number,
|
||||
duration: Number,
|
||||
volume: Number,
|
||||
audioRate: Number, // Example for bitrate, you'd calculate this or get from metadata
|
||||
audioSampleRate: Number, // Example for sample rate
|
||||
stereoStatus: String, // 'mono' or 'stereo'
|
||||
});
|
||||
|
||||
const emit = defineEmits(['prev', 'play-pause', 'stop', 'next', 'update:volume', 'seek', 'start-drag']);
|
||||
|
||||
const formatTime = (time) => {
|
||||
if (isNaN(time) || time === 0) return '0:00';
|
||||
const minutes = Math.floor(time / 60);
|
||||
const seconds = Math.floor(time % 60).toString().padStart(2, '0');
|
||||
return `${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
const startDrag = (event) => {
|
||||
if (event.target.classList.contains('title-bar')) {
|
||||
emit('start-drag', event, 'main-window');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.winamp-content {
|
||||
padding: 10px;
|
||||
background-color: #C0C0C0;
|
||||
@import url('https://fonts.googleapis.com/css2?family=VT323&display=swap');
|
||||
|
||||
.winamp-window {
|
||||
font-family: 'VT323', monospace;
|
||||
background-color: #3b3b3b; /* Dark grey base */
|
||||
border: 1px solid #7a7a7a;
|
||||
border-right-color: #000;
|
||||
border-bottom-color: #000;
|
||||
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.5);
|
||||
position: absolute;
|
||||
color: #c0c0c0; /* Light grey text */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.main-window {
|
||||
width: 275px;
|
||||
height: 116px; /* Adjusted height to match the image */
|
||||
left: 50px;
|
||||
top: 50px;
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
background: #000;
|
||||
color: #00ff00; /* Green text */
|
||||
padding: 1px 3px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 15px;
|
||||
cursor: grab;
|
||||
font-size: 14px;
|
||||
border-bottom: 1px solid #3b3b3b;
|
||||
}
|
||||
|
||||
.title-bar:active { cursor: grabbing; }
|
||||
|
||||
.title-bar-text {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.title-bar-controls button {
|
||||
width: 13px;
|
||||
height: 11px;
|
||||
background: #c0c0c0;
|
||||
border: 1px solid #fff;
|
||||
border-right-color: #404040;
|
||||
border-bottom-color: #404040;
|
||||
margin-left: 1px;
|
||||
font-size: 0; /* Hide default button text */
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title-bar-controls button[aria-label="Close"] {
|
||||
background-color: #800000; /* Red close button */
|
||||
border: 1px solid #ff0000;
|
||||
border-right-color: #400000;
|
||||
border-bottom-color: #400000;
|
||||
}
|
||||
.title-bar-controls button[aria-label="Close"]::before,
|
||||
.title-bar-controls button[aria-label="Close"]::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 5px; /* Center 'x' */
|
||||
left: 2px;
|
||||
width: 9px;
|
||||
height: 1px;
|
||||
background: white;
|
||||
}
|
||||
.title-bar-controls button[aria-label="Close"]::before {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.title-bar-controls button[aria-label="Close"]::after {
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
|
||||
/* Main Player Body */
|
||||
.main-body {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
.top-panel {
|
||||
display: flex;
|
||||
background-color: #000;
|
||||
border: 1px inset #808080;
|
||||
padding: 2px;
|
||||
height: 25px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.visualizer {
|
||||
width: 40px; /* Placeholder for visualizer */
|
||||
height: 100%;
|
||||
background-color: #003300; /* Dark green for silent visualizer */
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.track-display {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
justify-content: space-between;
|
||||
color: #00ff00; /* Bright green text */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.track-title-marquee {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
position: relative;
|
||||
height: 14px; /* Adjust height for single line of text */
|
||||
}
|
||||
|
||||
.track-title-marquee span {
|
||||
position: absolute;
|
||||
animation: marquee 10s linear infinite;
|
||||
padding-left: 100%; /* Start off-screen */
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
0% { transform: translateX(0%); }
|
||||
100% { transform: translateX(-100%); }
|
||||
}
|
||||
|
||||
.time-display {
|
||||
font-size: 16px;
|
||||
text-align: right;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.middle-panel {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background-color: #000;
|
||||
border: 1px inset #808080;
|
||||
padding: 2px;
|
||||
color: #c0c0c0;
|
||||
font-size: 12px;
|
||||
height: 16px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.kbps, .khz, .mono-stereo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-family: 'MS Sans Serif', 'Arial', sans-serif;
|
||||
font-size: 14px;
|
||||
}
|
||||
.kbps span, .khz span {
|
||||
font-size: 10px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.bottom-panel {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background-color: #3b3b3b;
|
||||
padding: 2px;
|
||||
border: 1px solid #7a7a7a;
|
||||
border-top-color: #000;
|
||||
border-left-color: #000;
|
||||
}
|
||||
|
||||
.control-buttons {
|
||||
display: flex;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
width: 20px;
|
||||
height: 18px;
|
||||
border: 1px solid #7a7a7a;
|
||||
border-right-color: #000;
|
||||
border-bottom-color: #000;
|
||||
background-color: #3b3b3b;
|
||||
cursor: pointer;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 80%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
border: 1px solid #000;
|
||||
border-right-color: #7a7a7a;
|
||||
border-bottom-color: #7a7a7a;
|
||||
}
|
||||
|
||||
|
||||
/* Data URI for button icons (basic representation, replace with actual pixel art) */
|
||||
.control-btn.prev { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M11 18V6l-7 6zm3.5-6l7 6V6z"/></svg>'); }
|
||||
.control-btn.play { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M8 5v14l11-7z"/></svg>'); }
|
||||
.control-btn.play.pause-active { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>'); }
|
||||
.control-btn.stop { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M6 6h12v12H6z"/></svg>'); }
|
||||
.control-btn.next { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%23c0c0c0"><path d="M13 6v12l7-6zm-7-6v12l7-6z"/></svg>'); }
|
||||
|
||||
|
||||
/* Sliders */
|
||||
.slider {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background: #000;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
border: 1px inset #808080; /* Sunken effect */
|
||||
}
|
||||
|
||||
.slider::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
background: #c0c0c0;
|
||||
border: 1px solid #fff;
|
||||
border-right-color: #404040;
|
||||
border-bottom-color: #404040;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.slider::-moz-range-thumb {
|
||||
width: 8px;
|
||||
height: 16px;
|
||||
background: #c0c0c0;
|
||||
border: 1px solid #fff;
|
||||
border-right-color: #404040;
|
||||
border-bottom-color: #404040;
|
||||
cursor: grab;
|
||||
}
|
||||
|
||||
.volume-slider-container {
|
||||
width: 60px;
|
||||
}
|
||||
.volume-slider {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.progress-bar-container {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.progress-slider {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue