initiation librodrome
This commit is contained in:
211
app/components/admin/AdminMediaBrowser.vue
Normal file
211
app/components/admin/AdminMediaBrowser.vue
Normal file
@@ -0,0 +1,211 @@
|
||||
<template>
|
||||
<div class="media-browser">
|
||||
<!-- Toolbar -->
|
||||
<div class="media-toolbar">
|
||||
<div class="flex items-center gap-2">
|
||||
<button
|
||||
v-for="t in types"
|
||||
:key="t.value"
|
||||
class="filter-btn"
|
||||
:class="{ 'filter-btn--active': filter === t.value }"
|
||||
@click="filter = t.value"
|
||||
>
|
||||
{{ t.label }}
|
||||
</button>
|
||||
</div>
|
||||
<span class="text-xs text-white/40">{{ filtered.length }} fichier(s)</span>
|
||||
</div>
|
||||
|
||||
<!-- Grid -->
|
||||
<div class="media-grid">
|
||||
<div
|
||||
v-for="file in filtered"
|
||||
:key="file.path"
|
||||
class="media-card"
|
||||
:class="{ 'media-card--selected': selected === file.path }"
|
||||
@click="selected = selected === file.path ? null : file.path"
|
||||
>
|
||||
<div v-if="file.type === 'image'" class="media-thumb">
|
||||
<img :src="file.path" :alt="file.name" />
|
||||
</div>
|
||||
<div v-else class="media-icon">
|
||||
<div
|
||||
:class="file.type === 'audio' ? 'i-lucide-music' : file.type === 'document' ? 'i-lucide-file-text' : 'i-lucide-file'"
|
||||
class="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<div class="media-info">
|
||||
<span class="media-name">{{ file.name }}</span>
|
||||
<span class="media-size">{{ formatSize(file.size) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions for selected -->
|
||||
<div v-if="selected" class="media-actions">
|
||||
<code class="text-xs text-accent">{{ selected }}</code>
|
||||
<button class="delete-btn" @click="$emit('delete', selected)">
|
||||
<div class="i-lucide-trash-2 h-4 w-4" />
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface MediaFile {
|
||||
name: string
|
||||
path: string
|
||||
size: number
|
||||
type: string
|
||||
modifiedAt: string
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
files: MediaFile[]
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
delete: [path: string]
|
||||
}>()
|
||||
|
||||
const filter = ref('all')
|
||||
const selected = ref<string | null>(null)
|
||||
|
||||
const types = [
|
||||
{ value: 'all', label: 'Tous' },
|
||||
{ value: 'image', label: 'Images' },
|
||||
{ value: 'audio', label: 'Audio' },
|
||||
{ value: 'document', label: 'Documents' },
|
||||
]
|
||||
|
||||
const filtered = computed(() => {
|
||||
if (filter.value === 'all') return props.files
|
||||
return props.files.filter(f => f.type === filter.value)
|
||||
})
|
||||
|
||||
function formatSize(bytes: number): string {
|
||||
if (bytes < 1024) return bytes + ' B'
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB'
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.media-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
padding: 0.25rem 0.625rem;
|
||||
border-radius: 9999px;
|
||||
border: 1px solid hsl(20 8% 18%);
|
||||
background: none;
|
||||
color: hsl(20 8% 55%);
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.filter-btn--active {
|
||||
background: hsl(12 76% 48% / 0.15);
|
||||
border-color: hsl(12 76% 48% / 0.3);
|
||||
color: hsl(12 76% 68%);
|
||||
}
|
||||
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.media-card {
|
||||
border: 1px solid hsl(20 8% 14%);
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.media-card:hover {
|
||||
border-color: hsl(20 8% 22%);
|
||||
}
|
||||
|
||||
.media-card--selected {
|
||||
border-color: hsl(12 76% 48%);
|
||||
box-shadow: 0 0 0 1px hsl(12 76% 48% / 0.3);
|
||||
}
|
||||
|
||||
.media-thumb {
|
||||
aspect-ratio: 1;
|
||||
overflow: hidden;
|
||||
background: hsl(20 8% 6%);
|
||||
}
|
||||
|
||||
.media-thumb img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.media-icon {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: hsl(20 8% 6%);
|
||||
color: hsl(20 8% 40%);
|
||||
}
|
||||
|
||||
.media-info {
|
||||
padding: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.125rem;
|
||||
}
|
||||
|
||||
.media-name {
|
||||
font-size: 0.72rem;
|
||||
color: white;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.media-size {
|
||||
font-size: 0.65rem;
|
||||
color: hsl(20 8% 40%);
|
||||
}
|
||||
|
||||
.media-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 1rem;
|
||||
padding: 0.75rem;
|
||||
border: 1px solid hsl(20 8% 14%);
|
||||
border-radius: 0.5rem;
|
||||
background: hsl(20 8% 5%);
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 0.375rem;
|
||||
border: 1px solid hsl(0 60% 45% / 0.3);
|
||||
background: hsl(0 60% 45% / 0.1);
|
||||
color: hsl(0 60% 65%);
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.delete-btn:hover {
|
||||
background: hsl(0 60% 45% / 0.2);
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user