feat: agregar búsqueda de canciones y configurar hook de Gitea Actions
- Agregar componente SearchFilter.client.vue modular y expandible - Integrar búsqueda en MainContainer con contador de resultados - Implementar filtrado de canciones por nombre en pages/index.vue - Configurar hook PostToolUse para monitorear Gitea Actions - Copiar y adaptar script monitor-gitea-action.sh para repodructor
This commit is contained in:
197
.claude/hooks/monitor-gitea-action.sh
Executable file
197
.claude/hooks/monitor-gitea-action.sh
Executable file
@@ -0,0 +1,197 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Monitor Gitea Action after git push
|
||||||
|
# Este script se ejecuta después de un git push y espera a que termine la Gitea Action
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Configuración
|
||||||
|
GITEA_URL="https://gitea.nucleoriofrio.com"
|
||||||
|
OWNER="nucleo000"
|
||||||
|
REPO="repodructor"
|
||||||
|
|
||||||
|
# Intentar cargar el token desde el entorno o desde ~/.bashrc
|
||||||
|
GITEA_TOKEN="${GITEA_TOKEN:-}"
|
||||||
|
if [ -z "$GITEA_TOKEN" ] && [ -f "$HOME/.bashrc" ]; then
|
||||||
|
# Intentar extraer el token de .bashrc
|
||||||
|
GITEA_TOKEN=$(grep -oP "export GITEA_TOKEN=['\"]?\K[^'\"]*" "$HOME/.bashrc" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
MAX_WAIT_SECONDS=600 # 10 minutos
|
||||||
|
POLL_INTERVAL=10 # Consultar cada 10 segundos
|
||||||
|
|
||||||
|
# Leer el input JSON del hook
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
# Verificar si el comando fue un git push
|
||||||
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // ""')
|
||||||
|
if [[ ! "$COMMAND" =~ git[[:space:]]+push ]]; then
|
||||||
|
# No fue un git push, salir sin hacer nada
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verificar que existe el token
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"decision": "block",
|
||||||
|
"reason": "⚠️ Git push exitoso, pero no puedo monitorear la Gitea Action: falta GITEA_TOKEN.\n\nConfigura el token en ~/.bashrc o ~/.zshrc:\nexport GITEA_TOKEN='tu_token_aqui'"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Función para verificar si las actions están habilitadas
|
||||||
|
check_actions_enabled() {
|
||||||
|
local repo_info=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO")
|
||||||
|
echo "$repo_info" | jq -r '.has_actions // false'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Función para consultar el estado de la última action
|
||||||
|
get_latest_action_status() {
|
||||||
|
local response=$(curl -s -H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
"$GITEA_URL/api/v1/repos/$OWNER/$REPO/actions/tasks?limit=1")
|
||||||
|
|
||||||
|
# Verificar si hay un error de permisos
|
||||||
|
if echo "$response" | jq -e '.message' > /dev/null 2>&1; then
|
||||||
|
echo "ERROR: $(echo "$response" | jq -r '.message')" >&2
|
||||||
|
echo ""
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$response" | jq -r '.workflow_runs[0] // .data[0] // empty'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Función para formatear el resultado
|
||||||
|
format_result() {
|
||||||
|
local status="$1"
|
||||||
|
local task_data="$2"
|
||||||
|
|
||||||
|
local id=$(echo "$task_data" | jq -r '.id // "N/A"')
|
||||||
|
local workflow_name=$(echo "$task_data" | jq -r '.name // "N/A"')
|
||||||
|
local workflow_file=$(echo "$task_data" | jq -r '.workflow_id // "N/A"')
|
||||||
|
local run_number=$(echo "$task_data" | jq -r '.run_number // "N/A"')
|
||||||
|
local event=$(echo "$task_data" | jq -r '.event // "N/A"')
|
||||||
|
local branch=$(echo "$task_data" | jq -r '.head_branch // "N/A"')
|
||||||
|
local title=$(echo "$task_data" | jq -r '.display_title // "N/A"')
|
||||||
|
local created=$(echo "$task_data" | jq -r '.created_at // "N/A"')
|
||||||
|
local started=$(echo "$task_data" | jq -r '.run_started_at // .started_at // "N/A"')
|
||||||
|
local updated=$(echo "$task_data" | jq -r '.updated_at // .stopped_at // "N/A"')
|
||||||
|
local commit=$(echo "$task_data" | jq -r '.head_sha[0:8] // "N/A"')
|
||||||
|
local run_url=$(echo "$task_data" | jq -r '.url // ""')
|
||||||
|
|
||||||
|
# Calcular duración si es posible
|
||||||
|
local duration="N/A"
|
||||||
|
if [[ "$started" != "N/A" && "$updated" != "N/A" ]]; then
|
||||||
|
local start_ts=$(date -d "$started" +%s 2>/dev/null || echo "0")
|
||||||
|
local end_ts=$(date -d "$updated" +%s 2>/dev/null || echo "0")
|
||||||
|
if [[ $start_ts -gt 0 && $end_ts -gt 0 ]]; then
|
||||||
|
local diff=$((end_ts - start_ts))
|
||||||
|
if [[ $diff -lt 60 ]]; then
|
||||||
|
duration="${diff}s"
|
||||||
|
else
|
||||||
|
local mins=$((diff / 60))
|
||||||
|
local secs=$((diff % 60))
|
||||||
|
duration="${mins}m ${secs}s"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$status" in
|
||||||
|
success)
|
||||||
|
local emoji="✅"
|
||||||
|
local msg="EXITOSO"
|
||||||
|
;;
|
||||||
|
failure)
|
||||||
|
local emoji="❌"
|
||||||
|
local msg="FALLÓ"
|
||||||
|
;;
|
||||||
|
cancelled)
|
||||||
|
local emoji="🚫"
|
||||||
|
local msg="CANCELADO"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local emoji="⚠️"
|
||||||
|
local msg="DESCONOCIDO ($status)"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Construir URL de logs si no está disponible
|
||||||
|
if [[ -z "$run_url" || "$run_url" == "null" ]]; then
|
||||||
|
run_url="$GITEA_URL/$OWNER/$REPO/actions/runs/$id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"decision": "block",
|
||||||
|
"reason": "$emoji Gitea Action completada: $msg\n\n📋 Detalles:\n • Workflow: $workflow_name ($workflow_file)\n • Run #$run_number\n • Evento: $event\n • Branch: $branch\n • Commit: $commit\n • Título: $title\n • Duración: $duration\n • Iniciado: $started\n • Finalizado: $updated\n\n🔗 Ver logs completos:\n $run_url"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verificar si las actions están habilitadas
|
||||||
|
echo "🔍 Verificando si Actions están habilitadas..." >&2
|
||||||
|
ACTIONS_ENABLED=$(check_actions_enabled)
|
||||||
|
|
||||||
|
if [[ "$ACTIONS_ENABLED" != "true" ]]; then
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"decision": "block",
|
||||||
|
"reason": "⚠️ Git push exitoso, pero las Gitea Actions NO están habilitadas en este repositorio.\n\n📝 Para habilitar Actions:\n1. Ve a: $GITEA_URL/$OWNER/$REPO/settings\n2. Busca la sección 'Actions' o 'Workflows'\n3. Activa las Actions\n\nLuego podrás ver tus workflows en:\n$GITEA_URL/$OWNER/$REPO/actions"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Notificar que empezamos a monitorear
|
||||||
|
echo "🔄 Monitoreando Gitea Action (máximo ${MAX_WAIT_SECONDS}s)..." >&2
|
||||||
|
|
||||||
|
# Esperar un poco antes de la primera consulta (dar tiempo a que Gitea cree la action)
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# Polling loop
|
||||||
|
elapsed=0
|
||||||
|
while [ $elapsed -lt $MAX_WAIT_SECONDS ]; do
|
||||||
|
# Consultar el estado
|
||||||
|
TASK_DATA=$(get_latest_action_status)
|
||||||
|
|
||||||
|
if [ -z "$TASK_DATA" ]; then
|
||||||
|
echo "⏳ Esperando que Gitea cree la action... (${elapsed}s)" >&2
|
||||||
|
sleep $POLL_INTERVAL
|
||||||
|
elapsed=$((elapsed + POLL_INTERVAL))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
STATUS=$(echo "$TASK_DATA" | jq -r '.status // "unknown"')
|
||||||
|
|
||||||
|
echo "📊 Estado actual: $STATUS (${elapsed}s)" >&2
|
||||||
|
|
||||||
|
# Verificar si terminó
|
||||||
|
case "$STATUS" in
|
||||||
|
success|failure|cancelled)
|
||||||
|
# Action terminó!
|
||||||
|
format_result "$STATUS" "$TASK_DATA"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
running|pending|waiting)
|
||||||
|
# Todavía corriendo
|
||||||
|
sleep $POLL_INTERVAL
|
||||||
|
elapsed=$((elapsed + POLL_INTERVAL))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Estado desconocido
|
||||||
|
echo "⚠️ Estado desconocido: $STATUS" >&2
|
||||||
|
sleep $POLL_INTERVAL
|
||||||
|
elapsed=$((elapsed + POLL_INTERVAL))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Timeout alcanzado
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"decision": "block",
|
||||||
|
"reason": "⏱️ Timeout: La Gitea Action todavía está corriendo después de ${MAX_WAIT_SECONDS}s.\n\n🔗 Verifica el estado manualmente en:\n$GITEA_URL/$OWNER/$REPO/actions"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
@@ -13,6 +13,10 @@
|
|||||||
|
|
||||||
<div class="header-controls">
|
<div class="header-controls">
|
||||||
<AuthIndicator />
|
<AuthIndicator />
|
||||||
|
<SearchFilter
|
||||||
|
:results-count="filteredCount"
|
||||||
|
@search="$emit('search', $event)"
|
||||||
|
/>
|
||||||
<PlaybackControls
|
<PlaybackControls
|
||||||
@shuffle-changed="$emit('shuffle-changed', $event)"
|
@shuffle-changed="$emit('shuffle-changed', $event)"
|
||||||
@repeat-changed="$emit('repeat-changed', $event)"
|
@repeat-changed="$emit('repeat-changed', $event)"
|
||||||
@@ -38,6 +42,7 @@ import { Music } from 'lucide-vue-next'
|
|||||||
import AuthIndicator from './AuthIndicator.client.vue'
|
import AuthIndicator from './AuthIndicator.client.vue'
|
||||||
import ThemeToggle from './ThemeToggle.client.vue'
|
import ThemeToggle from './ThemeToggle.client.vue'
|
||||||
import PlaybackControls from './PlaybackControls.client.vue'
|
import PlaybackControls from './PlaybackControls.client.vue'
|
||||||
|
import SearchFilter from './SearchFilter.client.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
currentTrack: {
|
currentTrack: {
|
||||||
@@ -47,10 +52,14 @@ const props = defineProps({
|
|||||||
isPlaying: {
|
isPlaying: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
filteredCount: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
defineEmits(['shuffle-changed', 'repeat-changed'])
|
defineEmits(['shuffle-changed', 'repeat-changed', 'search'])
|
||||||
|
|
||||||
// Computed property to determine if there's an active track
|
// Computed property to determine if there's an active track
|
||||||
const hasActiveTrack = computed(() => !!props.currentTrack)
|
const hasActiveTrack = computed(() => !!props.currentTrack)
|
||||||
|
|||||||
242
components/SearchFilter.client.vue
Normal file
242
components/SearchFilter.client.vue
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<div class="search-filter">
|
||||||
|
<div class="search-container" :class="{ expanded: isExpanded }">
|
||||||
|
<IconButton
|
||||||
|
:icon="Search"
|
||||||
|
title="Buscar canciones"
|
||||||
|
size="small"
|
||||||
|
@click="toggleSearch"
|
||||||
|
class="search-btn"
|
||||||
|
:active="isExpanded || searchQuery.length > 0"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<transition name="search-input">
|
||||||
|
<div v-if="isExpanded" class="input-wrapper">
|
||||||
|
<input
|
||||||
|
ref="searchInput"
|
||||||
|
v-model="searchQuery"
|
||||||
|
type="text"
|
||||||
|
placeholder="Buscar por nombre..."
|
||||||
|
class="search-input"
|
||||||
|
@blur="handleBlur"
|
||||||
|
@keydown.escape="closeSearch"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
v-if="searchQuery.length > 0"
|
||||||
|
@click="clearSearch"
|
||||||
|
class="clear-btn"
|
||||||
|
title="Limpiar búsqueda"
|
||||||
|
>
|
||||||
|
<X :size="16" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<transition name="results-badge">
|
||||||
|
<div v-if="searchQuery.length > 0 && resultsCount !== null" class="results-badge">
|
||||||
|
{{ resultsCount }} resultado{{ resultsCount !== 1 ? 's' : '' }}
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, watch, nextTick } from 'vue'
|
||||||
|
import { Search, X } from 'lucide-vue-next'
|
||||||
|
import IconButton from './IconButton.client.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
resultsCount: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(['search'])
|
||||||
|
|
||||||
|
const isExpanded = ref(false)
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const searchInput = ref(null)
|
||||||
|
|
||||||
|
const toggleSearch = async () => {
|
||||||
|
isExpanded.value = !isExpanded.value
|
||||||
|
|
||||||
|
if (isExpanded.value) {
|
||||||
|
await nextTick()
|
||||||
|
searchInput.value?.focus()
|
||||||
|
} else if (searchQuery.value.length === 0) {
|
||||||
|
// Solo colapsar si no hay búsqueda activa
|
||||||
|
emit('search', '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSearch = () => {
|
||||||
|
if (searchQuery.value.length === 0) {
|
||||||
|
isExpanded.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleBlur = () => {
|
||||||
|
// Mantener expandido si hay una búsqueda activa
|
||||||
|
if (searchQuery.value.length === 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
isExpanded.value = false
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearSearch = () => {
|
||||||
|
searchQuery.value = ''
|
||||||
|
searchInput.value?.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emitir el término de búsqueda cuando cambie
|
||||||
|
watch(searchQuery, (newValue) => {
|
||||||
|
emit('search', newValue.toLowerCase().trim())
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.search-filter {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-btn:hover {
|
||||||
|
animation: pulse-search 0.6s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 200px;
|
||||||
|
padding: 8px 32px 8px 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 12px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input::placeholder {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
border-color: var(--accent-primary);
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-badge {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
background: var(--bg-glass);
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--border-glass);
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
.search-input-enter-active,
|
||||||
|
.search-input-leave-active {
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-10px);
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-10px);
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-badge-enter-active,
|
||||||
|
.results-badge-leave-active {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-badge-enter-from,
|
||||||
|
.results-badge-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-search {
|
||||||
|
0%, 100% { transform: scale(1); }
|
||||||
|
50% { transform: scale(1.1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.search-input {
|
||||||
|
width: 160px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 6px 28px 6px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results-badge {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 3px 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 520px) {
|
||||||
|
.search-filter {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
width: 140px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -10,8 +10,10 @@
|
|||||||
<MainContainer
|
<MainContainer
|
||||||
:current-track="currentTrack"
|
:current-track="currentTrack"
|
||||||
:is-playing="isPlaying"
|
:is-playing="isPlaying"
|
||||||
|
:filtered-count="filteredCount"
|
||||||
@shuffle-changed="handleShuffleChanged"
|
@shuffle-changed="handleShuffleChanged"
|
||||||
@repeat-changed="handleRepeatChanged"
|
@repeat-changed="handleRepeatChanged"
|
||||||
|
@search="handleSearch"
|
||||||
>
|
>
|
||||||
<!-- Track List -->
|
<!-- Track List -->
|
||||||
<TrackList
|
<TrackList
|
||||||
@@ -91,16 +93,41 @@ const isShuffled = useLocalStorage('shuffle', false)
|
|||||||
const repeatMode = useLocalStorage('repeat', 'none') // 'none', 'all', 'one'
|
const repeatMode = useLocalStorage('repeat', 'none') // 'none', 'all', 'one'
|
||||||
const shuffledIndices = ref([])
|
const shuffledIndices = ref([])
|
||||||
|
|
||||||
|
// Search state
|
||||||
|
const searchQuery = ref('')
|
||||||
|
|
||||||
// Refs
|
// Refs
|
||||||
const audioPlayer = ref(null)
|
const audioPlayer = ref(null)
|
||||||
|
|
||||||
// Computed - removed progressPercent as it's not used in this component
|
// Computed - removed progressPercent as it's not used in this component
|
||||||
|
|
||||||
const displayTracks = computed(() => {
|
const displayTracks = computed(() => {
|
||||||
if (isShuffled.value && shuffledIndices.value.length > 0) {
|
let tracksToDisplay = tracks.value
|
||||||
return shuffledIndices.value.map(index => tracks.value[index])
|
|
||||||
|
// Aplicar filtro de búsqueda
|
||||||
|
if (searchQuery.value.length > 0) {
|
||||||
|
tracksToDisplay = tracksToDisplay.filter(track =>
|
||||||
|
track.name.toLowerCase().includes(searchQuery.value)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return tracks.value
|
|
||||||
|
// Aplicar shuffle si está activado
|
||||||
|
if (isShuffled.value && shuffledIndices.value.length > 0) {
|
||||||
|
const shuffledTracks = shuffledIndices.value
|
||||||
|
.map(index => tracks.value[index])
|
||||||
|
.filter(track =>
|
||||||
|
searchQuery.value.length === 0 ||
|
||||||
|
track.name.toLowerCase().includes(searchQuery.value)
|
||||||
|
)
|
||||||
|
return shuffledTracks
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracksToDisplay
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredCount = computed(() => {
|
||||||
|
if (searchQuery.value.length === 0) return null
|
||||||
|
return displayTracks.value.length
|
||||||
})
|
})
|
||||||
|
|
||||||
// Methods
|
// Methods
|
||||||
@@ -293,6 +320,10 @@ const handleRepeatChanged = (mode) => {
|
|||||||
repeatMode.value = mode
|
repeatMode.value = mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleSearch = (query) => {
|
||||||
|
searchQuery.value = query
|
||||||
|
}
|
||||||
|
|
||||||
const handleTrackSelected = ({ track, index }) => {
|
const handleTrackSelected = ({ track, index }) => {
|
||||||
playTrack(track, index)
|
playTrack(track, index)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user