Skip to content

Commit 0540659

Browse files
committed
feat: much less ugly modet UI
1 parent 60e0e42 commit 0540659

File tree

1 file changed

+155
-8
lines changed

1 file changed

+155
-8
lines changed

ui/src/views/RecordingView.vue

Lines changed: 155 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ const setVideoPos = () => {
3636
video.value.currentTime = data.sliderPos;
3737
};
3838
39+
const formatTime = (seconds: number) => {
40+
let base = 0;
41+
if (recording.value) {
42+
base = (new Date(recording.value.start)).getTime() / 1000;
43+
}
44+
45+
const date = new Date((base + seconds) * 1000);
46+
return date.toLocaleTimeString();
47+
};
48+
3949
data.sliderPosInterval = setInterval(() => {
4050
if (video.value) {
4151
data.sliderPos = video.value.currentTime;
@@ -61,7 +71,7 @@ onBeforeUnmount(() => {
6171
<div class="w-full h-full flex items-center justify-center bg-gray-700">
6272
<video
6373
v-if="recording"
64-
style="max-height: 80vh"
74+
style="max-height: 73vh"
6575
:src="recording.path"
6676
muted
6777
controls
@@ -96,11 +106,43 @@ onBeforeUnmount(() => {
96106
</div>
97107
</div> -->
98108

99-
<div v-if="recording && recording.performed_motion_detect" class="bg-gray-800 text-white p-4">
100-
<input class="range" type="range" list="range" step="any" :min="0" :max="recordingDuration" v-model="data.sliderPos" @input="setVideoPos" />
101-
<datalist id="range">
102-
<option v-for="m in recording.motion" :value="m.t" :label="`${m.s}`"></option>
103-
</datalist>
109+
<div v-if="recording && recording.performed_motion_detect" class="bg-black text-white px-6 py-4">
110+
<div class="timeline-container">
111+
<div class="motion-markers">
112+
<div
113+
v-for="m in recording.motion"
114+
:key="m.t"
115+
class="motion-marker"
116+
:style="{
117+
left: `${(m.t / recordingDuration) * 100}%`,
118+
height: `${Math.max(10, (m.s / 100) * 100)}%`
119+
}"
120+
:title="`Motion score: ${m.s}`"
121+
></div>
122+
</div>
123+
124+
<div class="timeline-track">
125+
<div class="timeline-progress" :style="{ width: `${(data.sliderPos / recordingDuration) * 100}%` }"></div>
126+
</div>
127+
128+
<input
129+
class="timeline-slider"
130+
type="range"
131+
step="any"
132+
:min="0"
133+
:max="recordingDuration"
134+
v-model="data.sliderPos"
135+
@input="setVideoPos"
136+
/>
137+
138+
<div class="timeline-labels">
139+
<div class="timeline-label timeline-label--first">{{ formatTime(0) }}</div>
140+
<div class="timeline-label timeline-label--mid">{{ formatTime(recordingDuration / 4) }}</div>
141+
<div class="timeline-label timeline-label--mid">{{ formatTime(recordingDuration / 2) }}</div>
142+
<div class="timeline-label timeline-label--mid">{{ formatTime(3 * recordingDuration / 4) }}</div>
143+
<div class="timeline-label timeline-label--last">{{ formatTime(recordingDuration) }}</div>
144+
</div>
145+
</div>
104146
</div>
105147

106148
<div v-if="recording" class="bg-gray-900 text-white p-4">
@@ -141,7 +183,112 @@ onBeforeUnmount(() => {
141183
</template>
142184

143185
<style scoped>
144-
.range {
186+
.timeline-container {
187+
position: relative;
188+
width: 100%;
189+
height: 60px;
190+
}
191+
192+
.motion-markers {
193+
position: absolute;
194+
top: 0;
195+
left: 0;
196+
right: 0;
197+
height: 20px;
198+
pointer-events: none;
199+
}
200+
201+
.motion-marker {
202+
position: absolute;
203+
bottom: 0;
204+
width: 2px;
205+
background-color: #ef4444;
206+
transform: translateX(-50%);
207+
opacity: 0.8;
208+
}
209+
210+
.timeline-track {
211+
position: absolute;
212+
top: 20px;
213+
left: 0;
214+
right: 0;
215+
height: 4px;
216+
background-color: #374151;
217+
border-radius: 2px;
218+
overflow: hidden;
219+
}
220+
221+
.timeline-progress {
222+
height: 100%;
223+
background-color: #6b7280;
224+
transition: width 0.1s linear;
225+
}
226+
227+
.timeline-slider {
228+
position: absolute;
229+
top: 12px;
230+
left: 0;
231+
right: 0;
145232
width: 100%;
233+
height: 20px;
234+
-webkit-appearance: none;
235+
appearance: none;
236+
background: transparent;
237+
cursor: pointer;
238+
outline: none;
239+
z-index: 10;
240+
}
241+
242+
.timeline-slider::-webkit-slider-thumb {
243+
-webkit-appearance: none;
244+
appearance: none;
245+
width: 16px;
246+
height: 16px;
247+
border-radius: 50%;
248+
background: white;
249+
cursor: pointer;
250+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
251+
transition: transform 0.1s ease;
252+
}
253+
254+
.timeline-slider::-webkit-slider-thumb:hover {
255+
transform: scale(1.2);
256+
}
257+
258+
.timeline-slider::-moz-range-thumb {
259+
width: 16px;
260+
height: 16px;
261+
border-radius: 50%;
262+
background: white;
263+
cursor: pointer;
264+
border: none;
265+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
266+
transition: transform 0.1s ease;
267+
}
268+
269+
.timeline-slider::-moz-range-thumb:hover {
270+
transform: scale(1.2);
271+
}
272+
273+
.timeline-labels {
274+
position: absolute;
275+
top: 32px;
276+
left: 0;
277+
right: 0;
278+
display: flex;
279+
justify-content: space-between;
280+
font-size: 11px;
281+
color: #9ca3af;
282+
pointer-events: none;
283+
}
284+
285+
.timeline-label {
286+
user-select: none;
287+
}
288+
289+
@media (max-width: 600px) {
290+
.timeline-label--mid {
291+
display: none;
292+
}
146293
}
147-
</style>
294+
</style>

0 commit comments

Comments
 (0)