From dbc166f015b3dae2e60446e6165bbbbc98c586ff Mon Sep 17 00:00:00 2001 From: sajjadtalkhabi <97734061+sajjadtalkhabi@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:50:49 +0330 Subject: [PATCH] fix: bugs --- src/assets/icons/pause.svg | 4 + src/assets/icons/play.svg | 2 +- src/components/form/VoiceRecorder.vue | 182 +++++++++++++++--- src/components/icons/icon-names.d.ts | 1 + .../components/DashboardActivityCard.vue | 2 +- 5 files changed, 158 insertions(+), 33 deletions(-) create mode 100644 src/assets/icons/pause.svg diff --git a/src/assets/icons/pause.svg b/src/assets/icons/pause.svg new file mode 100644 index 0000000..3d553ee --- /dev/null +++ b/src/assets/icons/pause.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/icons/play.svg b/src/assets/icons/play.svg index eab995b..5575f6c 100644 --- a/src/assets/icons/play.svg +++ b/src/assets/icons/play.svg @@ -1,6 +1,6 @@ - + diff --git a/src/components/form/VoiceRecorder.vue b/src/components/form/VoiceRecorder.vue index 7b0cf2d..c86d8ce 100644 --- a/src/components/form/VoiceRecorder.vue +++ b/src/components/form/VoiceRecorder.vue @@ -20,20 +20,29 @@ -
- +
+
+ +
+ {{ timeLabel }}
@@ -75,10 +84,6 @@ />
-
-
-
-

{{ error }}

@@ -113,8 +118,12 @@ const isRecording = ref(false) const isPlaying = ref(false) const elapsedMs = ref(0) const progress = ref(0) +const duration = ref(0) +const currentSeconds = ref(0) const audioEl = ref(null) +const trackEl = ref(null) const fileInput = ref(null) +let seeking = false let mediaRecorder = null let mediaStream = null @@ -171,6 +180,8 @@ const setRecording = (file) => { audioFile.value = file audioUrl.value = URL.createObjectURL(file) progress.value = 0 + duration.value = 0 + currentSeconds.value = 0 isPlaying.value = false emit('update:modelValue', file) emit('recorded', file) @@ -258,12 +269,71 @@ const togglePlay = () => { const onEnded = () => { isPlaying.value = false progress.value = 0 + currentSeconds.value = 0 } const onTimeUpdate = () => { const a = audioEl.value - if (!a?.duration) return - progress.value = (a.currentTime / a.duration) * 100 + if (!a || !Number.isFinite(duration.value) || duration.value <= 0) return + currentSeconds.value = a.currentTime + progress.value = (a.currentTime / duration.value) * 100 +} + +// MediaRecorder blobs report `Infinity` duration in Chromium until the element +// is seeked past the end once — force that, then rewind. +const onLoadedMetadata = () => { + const a = audioEl.value + if (!a) return + if (Number.isFinite(a.duration)) { + duration.value = a.duration + return + } + const restore = () => { + if (!Number.isFinite(a.duration)) return + duration.value = a.duration + a.currentTime = 0 + a.removeEventListener('timeupdate', restore) + } + a.addEventListener('timeupdate', restore) + a.currentTime = 1e10 +} + +const formatClock = (secs) => { + if (!Number.isFinite(secs) || secs < 0) return '00:00' + const total = Math.floor(secs) + const h = Math.floor(total / 3600) + const mm = String(Math.floor((total % 3600) / 60)).padStart(2, '0') + const ss = String(total % 60).padStart(2, '0') + return h > 0 ? `${h}:${mm}:${ss}` : `${mm}:${ss}` +} + +const timeLabel = computed(() => + formatClock(isPlaying.value || currentSeconds.value > 0 ? currentSeconds.value : duration.value) +) + +const seekTo = (clientX) => { + const a = audioEl.value + const track = trackEl.value + if (!a || !track || !Number.isFinite(duration.value) || duration.value <= 0) return + const rect = track.getBoundingClientRect() + const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width)) + a.currentTime = ratio * duration.value + currentSeconds.value = a.currentTime + progress.value = ratio * 100 +} + +const onSeekStart = (event) => { + seeking = true + trackEl.value?.setPointerCapture?.(event.pointerId) + seekTo(event.clientX) +} + +const onSeekMove = (event) => { + if (seeking) seekTo(event.clientX) +} + +const onSeekEnd = () => { + seeking = false } watch( @@ -338,11 +408,76 @@ onBeforeUnmount(() => { } } - &__preview { + &__player { + direction: ltr; display: flex; - flex-direction: column; align-items: center; - gap: 0.5rem; + gap: 1.25rem; + width: 100%; + padding: 1.25rem 1.5rem; + background: #fff; + border-radius: 1rem; + } + + &__play-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 0; + border: none; + background: transparent; + cursor: pointer; + flex-shrink: 0; + transition: transform 0.15s ease; + + &:hover { + transform: scale(1.08); + } + } + + &__track { + position: relative; + flex: 1; + height: 2px; + border-radius: 9999px; + background: #e8e8e8; + cursor: pointer; + touch-action: none; + + &::before { + content: ''; + position: absolute; + inset: -0.625rem 0; + } + } + + &__track-fill { + position: absolute; + top: 0; + left: 0; + height: 100%; + border-radius: 9999px; + background: #5d5d5d; + } + + &__knob { + position: absolute; + top: 50%; + transform: translate(-50%, -50%); + width: 0.625rem; + height: 0.625rem; + border-radius: 9999px; + background: #4b4b4b; + pointer-events: none; + } + + &__time { + font-family: var(--font-family-en); + font-size: 0.75rem; + color: #b5b5b5; + flex-shrink: 0; + min-width: 3rem; + text-align: right; } &__audio { @@ -406,21 +541,6 @@ onBeforeUnmount(() => { display: none; } - &__progress { - width: 100%; - height: 0.125rem; - border-radius: 9999px; - background: var(--color-thd-gray); - overflow: hidden; - } - - &__progress-fill { - height: 100%; - background: rgba(0, 0, 0, 40%); - border-radius: 9999px; - transition: width 0.2s ease; - } - &__error { margin: 0; font-family: var(--font-family-fa); diff --git a/src/components/icons/icon-names.d.ts b/src/components/icons/icon-names.d.ts index f8c4c83..09fceac 100644 --- a/src/components/icons/icon-names.d.ts +++ b/src/components/icons/icon-names.d.ts @@ -41,6 +41,7 @@ export type IconName = | 'newspaper' | 'paper-plane' | 'paper-plane-right' + | 'pause' | 'pencil' | 'phone' | 'play' diff --git a/src/features/student/pages/components/DashboardActivityCard.vue b/src/features/student/pages/components/DashboardActivityCard.vue index fc54481..249188d 100644 --- a/src/features/student/pages/components/DashboardActivityCard.vue +++ b/src/features/student/pages/components/DashboardActivityCard.vue @@ -3,7 +3,7 @@
- +