@@ -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+
3949data .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 : 80 vh "
74+ style =" max-height : 73 vh "
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