85 lines
2.8 KiB
Vue
85 lines
2.8 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref } from 'vue'
|
|
import { useStore } from '@/store'
|
|
|
|
const { state, loadRuns, openRun, setOutputDir } = useStore()
|
|
|
|
onMounted(loadRuns)
|
|
|
|
const showFolder = ref(false)
|
|
const folder = ref('')
|
|
|
|
function onChange(e: Event) {
|
|
const id = (e.target as HTMLSelectElement).value
|
|
if (id) openRun(id)
|
|
}
|
|
|
|
async function scanFolder() {
|
|
await setOutputDir(folder.value)
|
|
if (!state.error) showFolder.value = false
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="run-picker">
|
|
<label class="lbl">Meeting</label>
|
|
<select :value="state.currentRunId ?? ''" @change="onChange">
|
|
<option value="" disabled>Select a run…</option>
|
|
<option v-for="r in state.runs" :key="r.id" :value="r.id">
|
|
{{ r.name }} · {{ r.frameCount }} frames{{ r.hasVideo ? '' : ' · no video' }}
|
|
</option>
|
|
</select>
|
|
<button class="folder-btn" :title="`Scan folder — ${state.outputDir ?? ''}`" @click="showFolder = !showFolder">📁</button>
|
|
<span v-if="state.loading" class="hint">loading…</span>
|
|
<span v-else-if="state.error" class="hint err">{{ state.error }}</span>
|
|
<span v-else-if="!state.runs.length" class="hint" :title="state.outputDir ?? ''">
|
|
no runs found in {{ state.outputDir ?? '(unknown)' }}
|
|
</span>
|
|
|
|
<div v-if="showFolder" class="folder-pop">
|
|
<div class="pop-title">Scan folder (recursive) for meetings</div>
|
|
<div class="pop-row">
|
|
<input v-model="folder" :placeholder="state.outputDir ?? '/path/to/meetings'" @keyup.enter="scanFolder" />
|
|
<button :disabled="state.loading || !folder.trim()" @click="scanFolder">Scan</button>
|
|
</div>
|
|
<div class="pop-cur">current: {{ state.outputDir ?? '(unset)' }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.run-picker {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
position: relative;
|
|
}
|
|
.folder-btn { padding: var(--space-1) var(--space-2); line-height: 1; }
|
|
.folder-pop {
|
|
position: absolute; top: 110%; left: 0; z-index: 20; min-width: 340px;
|
|
background: var(--surface-1); border: var(--panel-border); border-radius: var(--panel-radius);
|
|
padding: var(--space-2); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
|
|
}
|
|
.pop-title { font-size: 11px; color: var(--text-secondary); margin-bottom: var(--space-1); }
|
|
.pop-row { display: flex; gap: var(--space-1); }
|
|
.pop-row input { flex: 1; min-width: 0; padding: var(--space-1) var(--space-2); }
|
|
.pop-cur { font-size: var(--font-size-sm); color: var(--text-secondary); margin-top: var(--space-1); overflow: hidden; text-overflow: ellipsis; }
|
|
.lbl {
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-sm);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
select {
|
|
padding: var(--space-1) var(--space-2);
|
|
min-width: 280px;
|
|
}
|
|
.hint {
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
.hint.err {
|
|
color: var(--status-error);
|
|
}
|
|
</style>
|