refactor: flatten project structure, remove ti2026h-web subdirectory

This commit is contained in:
2026-08-01 16:08:44 +08:00
parent 6bbe83e1f1
commit 386d8d5852
26 changed files with 18 additions and 1 deletions
+191
View File
@@ -0,0 +1,191 @@
<script setup lang="ts">
import type { RecorderState } from '../composables/useRecorder'
defineProps<{
state: RecorderState
elapsed: string
frameCount: number
isLive: boolean
error: string | null
}>()
const emit = defineEmits<{
start: []
stop: []
cancel: []
}>()
</script>
<template>
<div class="recorder-controls">
<div class="controls-row">
<!-- Idle: show Record button -->
<button
v-if="state === 'idle'"
class="btn btn-record"
:disabled="!isLive"
@click="emit('start')"
title="Start recording"
>
<span class="btn-icon">⏺</span>
Record
</button>
<!-- Recording: show Stop and Cancel -->
<template v-if="state === 'recording'">
<button class="btn btn-stop" @click="emit('stop')" title="Stop and save recording">
<span class="btn-icon">⏹</span>
Stop &amp; Save
</button>
<button class="btn btn-cancel" @click="emit('cancel')" title="Cancel recording">
<span class="btn-icon">✕</span>
Cancel
</button>
</template>
<!-- Saving -->
<button v-if="state === 'saving'" class="btn btn-saving" disabled>
<span class="spinner"></span>
Saving…
</button>
</div>
<!-- Recording info -->
<div v-if="state === 'recording'" class="recording-info">
<span class="rec-dot"></span>
<span class="rec-time">{{ elapsed }}</span>
<span class="rec-frames">{{ frameCount }} frames</span>
</div>
<!-- Recorder fetch error -->
<div v-if="error" class="recorder-error">
⚠ {{ error }}
</div>
</div>
</template>
<style scoped>
.recorder-controls {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.controls-row {
display: flex;
gap: 10px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s, transform 0.15s;
}
.btn:active:not(:disabled) {
transform: scale(0.97);
}
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.btn-icon {
font-size: 1.1rem;
}
.btn-record {
background: #e74c3c;
color: #fff;
}
.btn-record:hover:not(:disabled) {
background: #c0392b;
}
.btn-stop {
background: #e74c3c;
color: #fff;
}
.btn-stop:hover {
background: #c0392b;
}
.btn-cancel {
background: #555;
color: #fff;
}
.btn-cancel:hover {
background: #444;
}
.btn-saving {
background: #666;
color: #fff;
}
.spinner {
display: inline-block;
width: 14px;
height: 14px;
border: 2px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.6s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.recording-info {
display: flex;
align-items: center;
gap: 10px;
font-size: 0.9rem;
color: #e74c3c;
font-variant-numeric: tabular-nums;
}
.rec-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #e74c3c;
animation: pulse 0.8s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.rec-time {
font-weight: 700;
}
.rec-frames {
color: #999;
}
.recorder-error {
font-size: 0.85rem;
color: #f39c12;
background: rgba(243, 156, 18, 0.1);
padding: 6px 12px;
border-radius: 4px;
max-width: 100%;
word-break: break-all;
}
</style>