55 enter-from-class =" opacity-0 -translate-y-4"
66 enter-to-class =" opacity-100 translate-y-0"
77 >
8- <div class =" mt-5 p-3 bg-gray-50 border-2 border-gray-300 rounded-lg font-mono " >
8+ <div class =" mt-5 p-3 bg-gray-50 border-2 border-gray-300 rounded-lg" >
99 <div
1010 v-for =" (progress, digest) in progressData"
1111 :key =" digest"
1212 class =" py-1"
1313 >
14- <div class =" flex items-center whitespace-pre " >
14+ <div class =" flex items-center" >
1515 <span class =" w-32 text-gray-600 shrink-0" >{{ formatDigest(digest) }}:</span >
16- <div class =" flex-1 flex items-center" >
17- <span class =" progress-bar" >{{ formatProgressBar(progress) }}</span >
18- <span class =" ml-2 text-gray-600 shrink-0" >{{ formatSize(progress) }}</span >
16+ <div class =" flex-1 flex items-center gap-2 min-w-0" >
17+ <span ref =" barContainer" class =" progress-bar font-mono" >
18+ {{ formatProgressBar(progress) }}
19+ </span >
20+ <span class =" text-gray-600 shrink-0 font-mono" >{{ formatSize(progress) }}</span >
1921 </div >
2022 </div >
2123 </div >
24+ <span ref =" charMeasure" class =" absolute opacity-0 pointer-events-none font-mono" >0</span >
2225 </div >
2326 </Transition >
2427</template >
2528
2629<script setup lang="ts">
30+ import { ref , computed } from " vue" ;
31+ import { useElementSize } from " @vueuse/core" ;
2732import type { DownloadProgress } from " ~/types/docker" ;
2833
2934const props = defineProps <{
@@ -33,10 +38,28 @@ const props = defineProps<{
3338const formatDigest = (digest : string ): string =>
3439 digest .replace (" sha256:" , " " ).substring (0 , 12 );
3540
41+ const barContainer = ref <HTMLElement | null >(null );
42+ const charMeasure = ref <HTMLElement | null >(null );
43+ const { width : barWidth } = useElementSize (barContainer );
44+ const { width : charWidth } = useElementSize (charMeasure );
45+
46+ const barLength = computed (() => {
47+ const containerWidth = barWidth .value || 0 ;
48+ const singleChar = charWidth .value || 0 ;
49+ if (containerWidth <= 0 || singleChar <= 0 ) return 30 ;
50+ const available = Math .floor (containerWidth / singleChar ) - 4 ;
51+ return Math .max (10 , available );
52+ });
53+
3654const formatProgressBar = (progress : DownloadProgress ): string => {
37- const bar = " =" .repeat (Math .floor (progress .percentage / 2 ));
38- const space = " " .repeat (50 - Math .floor (progress .percentage / 2 ));
39- return ` [${bar }>${space }] ` ;
55+ const total = barLength .value ;
56+ const filled = Math .floor ((progress .percentage / 100 ) * total );
57+ const clamped = Math .min (Math .max (filled , 0 ), total );
58+ if (clamped >= total ) {
59+ return ` [${" =" .repeat (total )}] ` ;
60+ }
61+ const empty = Math .max (total - clamped - 1 , 0 );
62+ return ` [${" =" .repeat (clamped )}>${" " .repeat (empty )}] ` ;
4063};
4164
4265const formatSize = (progress : DownloadProgress ): string => {
@@ -48,7 +71,9 @@ const formatSize = (progress: DownloadProgress): string => {
4871
4972<style scoped>
5073.progress-bar {
51- font-family : monospace ;
74+ display : block ;
75+ flex : 1 ;
76+ min-width : 0 ;
5277 white-space : pre ;
5378}
54- </style >
79+ </style >
0 commit comments