fix: bugs
Deploy banu-front / deploy (push) Successful in 1m27s

This commit is contained in:
sajjadtalkhabi
2026-07-09 13:50:49 +03:30
parent da34813b1d
commit dbc166f015
5 changed files with 158 additions and 33 deletions
+4
View File
@@ -0,0 +1,4 @@
<svg viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" xmlns="http://www.w3.org/2000/svg">
<path d="M7 3.75v12.5"/>
<path d="M13 3.75v12.5"/>
</svg>

After

Width:  |  Height:  |  Size: 199 B

+1 -1
View File
@@ -1,6 +1,6 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_21269)"> <g clip-path="url(#clip0_1_21269)">
<path d="M5.625 3.1154V16.8841C5.62704 16.994 5.65802 17.1014 5.7148 17.1955C5.77158 17.2896 5.85217 17.367 5.94843 17.42C6.0447 17.473 6.15323 17.4997 6.2631 17.4974C6.37297 17.4951 6.48028 17.4638 6.57422 17.4068L17.8305 10.5224C17.9204 10.468 17.9947 10.3913 18.0463 10.2998C18.0979 10.2082 18.1251 10.1049 18.1251 9.99977C18.1251 9.89467 18.0979 9.79135 18.0463 9.69979C17.9947 9.60823 17.9204 9.53154 17.8305 9.47711L6.57422 2.59274C6.48028 2.53571 6.37297 2.50447 6.2631 2.50214C6.15323 2.49982 6.0447 2.52651 5.94843 2.57951C5.85217 2.63252 5.77158 2.70996 5.7148 2.80405C5.65802 2.89813 5.62704 3.00552 5.625 3.1154Z" stroke="#848484" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round"/> <path d="M5.625 3.1154V16.8841C5.62704 16.994 5.65802 17.1014 5.7148 17.1955C5.77158 17.2896 5.85217 17.367 5.94843 17.42C6.0447 17.473 6.15323 17.4997 6.2631 17.4974C6.37297 17.4951 6.48028 17.4638 6.57422 17.4068L17.8305 10.5224C17.9204 10.468 17.9947 10.3913 18.0463 10.2998C18.0979 10.2082 18.1251 10.1049 18.1251 9.99977C18.1251 9.89467 18.0979 9.79135 18.0463 9.69979C17.9947 9.60823 17.9204 9.53154 17.8305 9.47711L6.57422 2.59274C6.48028 2.53571 6.37297 2.50447 6.2631 2.50214C6.15323 2.49982 6.0447 2.52651 5.94843 2.57951C5.85217 2.63252 5.77158 2.70996 5.7148 2.80405C5.65802 2.89813 5.62704 3.00552 5.625 3.1154Z" stroke="currentColor" stroke-width="0.75" stroke-linecap="round" stroke-linejoin="round"/>
</g> </g>
<defs> <defs>
<clipPath id="clip0_1_21269"> <clipPath id="clip0_1_21269">

Before

Width:  |  Height:  |  Size: 988 B

After

Width:  |  Height:  |  Size: 993 B

+151 -31
View File
@@ -20,20 +20,29 @@
<SvgIcon name="close" :size="40" color="var(--color-error)" /> <SvgIcon name="close" :size="40" color="var(--color-error)" />
</button> </button>
<div v-else class="voice-recorder__preview"> <div v-else class="voice-recorder__player">
<button type="button" class="voice-recorder__main-btn" @click="togglePlay"> <button type="button" class="voice-recorder__play-btn" @click="togglePlay">
<SvgIcon <SvgIcon :name="isPlaying ? 'pause' : 'play'" :size="24" color="#848484" />
:name="isPlaying ? 'close' : 'paper-plane-right'"
:size="40"
color="var(--color-thd-gray)"
/>
</button> </button>
<div
ref="trackEl"
class="voice-recorder__track"
@pointerdown="onSeekStart"
@pointermove="onSeekMove"
@pointerup="onSeekEnd"
@pointercancel="onSeekEnd"
>
<div class="voice-recorder__track-fill" :style="{ width: `${progress}%` }" />
<span class="voice-recorder__knob" :style="{ left: `${progress}%` }" />
</div>
<span class="voice-recorder__time">{{ timeLabel }}</span>
<audio <audio
ref="audioEl" ref="audioEl"
:src="audioUrl" :src="audioUrl"
class="voice-recorder__audio" class="voice-recorder__audio"
@ended="onEnded" @ended="onEnded"
@timeupdate="onTimeUpdate" @timeupdate="onTimeUpdate"
@loadedmetadata="onLoadedMetadata"
/> />
</div> </div>
@@ -75,10 +84,6 @@
/> />
</div> </div>
<div v-if="audioUrl" class="voice-recorder__progress">
<div class="voice-recorder__progress-fill" :style="{ width: `${progress}%` }" />
</div>
<p v-if="error" class="voice-recorder__error">{{ error }}</p> <p v-if="error" class="voice-recorder__error">{{ error }}</p>
</div> </div>
@@ -113,8 +118,12 @@ const isRecording = ref(false)
const isPlaying = ref(false) const isPlaying = ref(false)
const elapsedMs = ref(0) const elapsedMs = ref(0)
const progress = ref(0) const progress = ref(0)
const duration = ref(0)
const currentSeconds = ref(0)
const audioEl = ref(null) const audioEl = ref(null)
const trackEl = ref(null)
const fileInput = ref(null) const fileInput = ref(null)
let seeking = false
let mediaRecorder = null let mediaRecorder = null
let mediaStream = null let mediaStream = null
@@ -171,6 +180,8 @@ const setRecording = (file) => {
audioFile.value = file audioFile.value = file
audioUrl.value = URL.createObjectURL(file) audioUrl.value = URL.createObjectURL(file)
progress.value = 0 progress.value = 0
duration.value = 0
currentSeconds.value = 0
isPlaying.value = false isPlaying.value = false
emit('update:modelValue', file) emit('update:modelValue', file)
emit('recorded', file) emit('recorded', file)
@@ -258,12 +269,71 @@ const togglePlay = () => {
const onEnded = () => { const onEnded = () => {
isPlaying.value = false isPlaying.value = false
progress.value = 0 progress.value = 0
currentSeconds.value = 0
} }
const onTimeUpdate = () => { const onTimeUpdate = () => {
const a = audioEl.value const a = audioEl.value
if (!a?.duration) return if (!a || !Number.isFinite(duration.value) || duration.value <= 0) return
progress.value = (a.currentTime / a.duration) * 100 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( watch(
@@ -338,11 +408,76 @@ onBeforeUnmount(() => {
} }
} }
&__preview { &__player {
direction: ltr;
display: flex; display: flex;
flex-direction: column;
align-items: center; 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 { &__audio {
@@ -406,21 +541,6 @@ onBeforeUnmount(() => {
display: none; 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 { &__error {
margin: 0; margin: 0;
font-family: var(--font-family-fa); font-family: var(--font-family-fa);
+1
View File
@@ -41,6 +41,7 @@ export type IconName =
| 'newspaper' | 'newspaper'
| 'paper-plane' | 'paper-plane'
| 'paper-plane-right' | 'paper-plane-right'
| 'pause'
| 'pencil' | 'pencil'
| 'phone' | 'phone'
| 'play' | 'play'
@@ -3,7 +3,7 @@
<SvgIcon name="card-corner-arrow" :size="'1.5rem'" class="activity-card__corner" /> <SvgIcon name="card-corner-arrow" :size="'1.5rem'" class="activity-card__corner" />
<div class="activity-card__thumb"> <div class="activity-card__thumb">
<SvgIcon name="play" :size="'1.5rem'" /> <SvgIcon name="play" :size="'1.5rem'" color="#848484" />
</div> </div>
<div class="activity-card__body"> <div class="activity-card__body">