This commit is contained in:
Mariano Gabriel
2026-06-30 20:31:27 -03:00
parent cc64544d50
commit 221ed53696
43 changed files with 6669 additions and 0 deletions

View File

@@ -0,0 +1,121 @@
<script setup lang="ts">
import { ref, watch, nextTick } from 'vue'
import Panel from '@framework/components/Panel.vue'
import { useStore } from '@/store'
const { state, activeFrameIndex, selectedFrames, seek, setSelectAll } = useStore()
const cardEls = ref<Record<number, HTMLElement>>({})
function setCard(i: number, el: Element | null) {
if (el) cardEls.value[i] = el as HTMLElement
}
function fmt(seconds: number | null): string {
if (seconds == null) return '—'
const m = Math.floor(seconds / 60)
const s = Math.floor(seconds % 60)
return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
}
watch(activeFrameIndex, async (i) => {
if (i < 0) return
await nextTick()
cardEls.value[i]?.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' })
})
</script>
<template>
<Panel title="Frames">
<template #actions>
<span class="count">{{ selectedFrames.length }}/{{ state.frames.length }} selected</span>
<button @click="setSelectAll(true)">All</button>
<button @click="setSelectAll(false)">None</button>
</template>
<div class="strip">
<p v-if="!state.frames.length" class="empty">No frames for this run.</p>
<div
v-for="(f, i) in state.frames"
:key="f.file"
:ref="(el) => setCard(i, el as Element | null)"
class="card"
:class="{ active: i === activeFrameIndex, off: !f.selected }"
>
<div class="thumb" @click="f.time != null && seek(f.time)" :title="f.time != null ? 'Seek here' : 'No timestamp'">
<img :src="f.url" loading="lazy" :alt="f.file" />
<span class="time">{{ fmt(f.time) }}</span>
</div>
<label class="pick">
<input type="checkbox" v-model="f.selected" />
include
</label>
</div>
</div>
</Panel>
</template>
<style scoped>
.strip {
height: 100%;
overflow-y: auto;
padding: var(--space-2);
display: flex;
flex-wrap: wrap;
gap: var(--space-2);
align-content: flex-start;
}
.empty {
color: var(--text-secondary);
padding: var(--space-4);
}
.card {
width: 150px;
border: 1px solid var(--border);
border-radius: var(--panel-radius);
overflow: hidden;
background: var(--surface-1);
}
.card.active {
border-color: var(--status-processing);
box-shadow: 0 0 0 1px var(--status-processing);
}
.card.off {
opacity: 0.45;
}
.thumb {
position: relative;
cursor: pointer;
line-height: 0;
}
.thumb img {
width: 100%;
height: 84px;
object-fit: cover;
display: block;
background: #000;
}
.time {
position: absolute;
bottom: 2px;
right: 2px;
font-family: var(--font-mono);
font-size: var(--font-size-sm);
background: rgba(0, 0, 0, 0.65);
color: var(--text-primary);
padding: 0 4px;
border-radius: 3px;
}
.pick {
display: flex;
align-items: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
font-size: var(--font-size-sm);
color: var(--text-secondary);
cursor: pointer;
}
.count {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
</style>