Skip to content

Commit 602ede8

Browse files
committed
feat: init
1 parent 3e6eb98 commit 602ede8

16 files changed

Lines changed: 1765 additions & 86 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<script setup lang="ts">
2+
import type { SessionCompareChangeStatus } from '~~/shared/types'
3+
import { computed } from 'vue'
4+
5+
const props = defineProps<{
6+
status: SessionCompareChangeStatus
7+
}>()
8+
9+
const badge = computed(() => {
10+
switch (props.status) {
11+
case 'added':
12+
return {
13+
label: 'Added',
14+
icon: 'i-ph-plus-circle-duotone',
15+
class: 'text-red-500 bg-red-500/10',
16+
}
17+
case 'removed':
18+
return {
19+
label: 'Removed',
20+
icon: 'i-ph-minus-circle-duotone',
21+
class: 'text-green-500 bg-green-500/10',
22+
}
23+
case 'changed':
24+
return {
25+
label: 'Changed',
26+
icon: 'i-ph-arrows-clockwise-duotone',
27+
class: 'text-amber-500 bg-amber-500/10',
28+
}
29+
default:
30+
return {
31+
label: 'Unchanged',
32+
icon: 'i-ph-check-circle-duotone',
33+
class: 'text-gray-500 bg-gray-500/10',
34+
}
35+
}
36+
})
37+
</script>
38+
39+
<template>
40+
<span
41+
inline-flex items-center gap-1 rounded px2 py1 text-xs font-500
42+
:class="badge.class"
43+
>
44+
<span :class="badge.icon" />
45+
{{ badge.label }}
46+
</span>
47+
</template>
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
4+
const props = withDefaults(defineProps<{
5+
delta: number
6+
maxDelta?: number
7+
}>(), {
8+
maxDelta: 1,
9+
})
10+
11+
const width = computed(() => {
12+
if (!props.maxDelta)
13+
return 0
14+
return Math.min(Math.abs(props.delta) / props.maxDelta * 50, 50)
15+
})
16+
17+
const style = computed(() => {
18+
if (props.delta >= 0) {
19+
return {
20+
left: '50%',
21+
width: `${width.value}%`,
22+
}
23+
}
24+
25+
return {
26+
left: `${50 - width.value}%`,
27+
width: `${width.value}%`,
28+
}
29+
})
30+
31+
const barClass = computed(() => {
32+
if (props.delta > 0)
33+
return 'bg-red-500/80'
34+
if (props.delta < 0)
35+
return 'bg-green-500/80'
36+
return 'bg-gray-500/50'
37+
})
38+
</script>
39+
40+
<template>
41+
<div h-7 relative flex="~ items-center">
42+
<div absolute inset-x-0 h-1 rounded-full bg-base />
43+
<div absolute left="1/2" top-0 bottom-0 w-px bg-base />
44+
<div absolute h-2 rounded-full :class="barClass" :style="style" />
45+
</div>
46+
</template>
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue'
3+
import { bytesToHumanSize } from '~/utils/format'
4+
5+
const props = withDefaults(defineProps<{
6+
value: number
7+
format?: 'bytes' | 'duration' | 'number'
8+
signed?: boolean
9+
}>(), {
10+
format: 'number',
11+
signed: false,
12+
})
13+
14+
const sign = computed(() => {
15+
if (!props.signed || props.value === 0)
16+
return ''
17+
return props.value > 0 ? '+' : '-'
18+
})
19+
20+
const colorClass = computed(() => {
21+
if (!props.signed || props.value === 0)
22+
return 'text-gray-500'
23+
return props.value > 0 ? 'text-red-500' : 'text-green-500'
24+
})
25+
26+
const formatted = computed(() => {
27+
const value = props.signed ? Math.abs(props.value) : props.value
28+
29+
if (props.format === 'bytes') {
30+
if (value === 0) {
31+
return {
32+
amount: 0,
33+
unit: 'B',
34+
}
35+
}
36+
const [amount, unit] = bytesToHumanSize(value)
37+
return { amount, unit }
38+
}
39+
40+
if (props.format === 'duration') {
41+
if (value >= 1000) {
42+
return {
43+
amount: +(value / 1000).toFixed(2),
44+
unit: 's',
45+
}
46+
}
47+
return {
48+
amount: Math.round(value),
49+
unit: 'ms',
50+
}
51+
}
52+
53+
return {
54+
amount: value.toLocaleString(),
55+
unit: '',
56+
}
57+
})
58+
</script>
59+
60+
<template>
61+
<span font-mono ws-nowrap :class="colorClass">
62+
{{ sign }}{{ formatted.amount }}<span v-if="formatted.unit" text-xs op75 ml-0.5>{{ formatted.unit }}</span>
63+
</span>
64+
</template>

packages/rolldown/src/app/components/compare/SessionMeta.vue

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
<script setup lang="ts">
2+
export interface CompareSessionSummaryItem {
3+
label: string
4+
value: string
5+
icon: string
6+
}
7+
28
defineProps<{
39
sessions: Array<{ id: string, createdAt: Date, title: string }>
10+
summaries?: CompareSessionSummaryItem[][]
411
}>()
512
</script>
613

714
<template>
815
<div flex="~ gap5" w-full border="b base" pb3>
9-
<div v-for="(item) of sessions" :key="item.id" flex-1 border="~ base rounded" p4 grid="~ cols-[max-content_140px_2fr] max-lg:cols-[max-content_80px_2fr] gap-2 items-center">
16+
<div v-for="(item, index) of sessions" :key="item.id" flex-1 border="~ base rounded" p4 grid="~ cols-[max-content_140px_2fr] max-lg:cols-[max-content_80px_2fr] gap-2 items-center">
1017
<!-- session meta -->
1118
<div class="i-ph-hash-duotone" />
1219
<div>
@@ -23,6 +30,18 @@ defineProps<{
2330
<div font-mono>
2431
<time :datetime="item.createdAt.toISOString()">{{ item.createdAt.toLocaleString() }}</time>
2532
</div>
33+
34+
<div v-if="summaries?.[index]?.length" col-span-3 border="t base" mt2 pt3 flex="~ items-center gap-4 wrap">
35+
<div v-for="summary of summaries?.[index] || []" :key="summary.label" flex="~ items-center gap-2">
36+
<div :class="summary.icon" op50 />
37+
<div text-xs op50>
38+
{{ summary.label }}
39+
</div>
40+
<div font-mono font-600>
41+
{{ summary.value }}
42+
</div>
43+
</div>
44+
</div>
2645
</div>
2746
</div>
2847
</template>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<script setup lang="ts">
2+
import DisplayBadge from '@vitejs/devtools-ui/components/DisplayBadge.vue'
3+
4+
interface SingleSideDiffStat {
5+
value: string
6+
label: string
7+
tone?: 'increase' | 'decrease'
8+
}
9+
10+
withDefaults(defineProps<{
11+
status: 'added' | 'removed'
12+
sessionLabel: string
13+
title: string
14+
titleMeta?: string
15+
value: number
16+
delta: number
17+
format: 'bytes' | 'duration' | 'number'
18+
subtitle?: string
19+
badges?: string[]
20+
stats?: SingleSideDiffStat[]
21+
ratioText?: string
22+
}>(), {
23+
subtitle: '',
24+
titleMeta: '',
25+
badges: () => [],
26+
stats: () => [],
27+
ratioText: '',
28+
})
29+
</script>
30+
31+
<template>
32+
<div border="~ base rounded" p4 flex="~ col gap-3" hover="bg-active">
33+
<div flex="~ items-start gap-3">
34+
<div min-w-0 flex-1>
35+
<div text-xs op50 mb1>
36+
{{ sessionLabel }}
37+
</div>
38+
<div flex="~ items-baseline gap-2" min-w-0 font-mono :title="titleMeta ? `${title} (${titleMeta})` : title">
39+
<span truncate>{{ title }}</span>
40+
<span v-if="titleMeta" flex-none op50>({{ titleMeta }})</span>
41+
</div>
42+
<div v-if="subtitle" truncate text-xs op55 mt1 :title="subtitle">
43+
{{ subtitle }}
44+
</div>
45+
</div>
46+
<CompareDeltaValue :value="delta" :format="format" signed />
47+
</div>
48+
49+
<div v-if="badges.length || stats.length" flex="~ items-center gap-2 wrap" text-xs>
50+
<DisplayBadge v-for="badge of badges" :key="badge" :text="badge" />
51+
<span v-if="stats.length" flex="~ items-baseline gap-1" op55>
52+
<span>(</span>
53+
<template v-for="(stat, index) of stats" :key="stat.label">
54+
<span v-if="index" op70>|</span>
55+
<span font-mono font-600 :class="stat.tone === 'increase' ? 'text-green-500' : stat.tone === 'decrease' ? 'text-red-500' : 'op85'">{{ stat.value }}</span>
56+
<span>{{ stat.label }}</span>
57+
</template>
58+
<span>)</span>
59+
</span>
60+
</div>
61+
</div>
62+
</template>
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<script setup lang="ts">
2+
import type { SessionCompareChangeStatus } from '~~/shared/types'
3+
import DisplayBadge from '@vitejs/devtools-ui/components/DisplayBadge.vue'
4+
import { computed } from 'vue'
5+
6+
interface SplitDiffStat {
7+
value: string
8+
label: string
9+
tone?: 'increase' | 'decrease'
10+
hidden?: boolean
11+
}
12+
13+
const props = withDefaults(defineProps<{
14+
status: SessionCompareChangeStatus
15+
previousTitle?: string
16+
currentTitle?: string
17+
previousTitleMeta?: string
18+
currentTitleMeta?: string
19+
previousSubtitle?: string
20+
currentSubtitle?: string
21+
previousStats?: SplitDiffStat[]
22+
currentStats?: SplitDiffStat[]
23+
previousBadges?: string[]
24+
currentBadges?: string[]
25+
previous: number
26+
current: number
27+
delta: number
28+
format: 'bytes' | 'duration' | 'number'
29+
ratioText?: string
30+
}>(), {
31+
previousStats: () => [],
32+
currentStats: () => [],
33+
previousBadges: () => [],
34+
currentBadges: () => [],
35+
ratioText: '',
36+
})
37+
38+
const ratioClass = computed(() => {
39+
if (props.delta > 0)
40+
return 'text-red-500'
41+
if (props.delta < 0)
42+
return 'text-green-500'
43+
return 'text-gray-500'
44+
})
45+
</script>
46+
47+
<template>
48+
<div border="~ base rounded" of-hidden hover="bg-active">
49+
<div border="b base" bg-base p3 flex="~ items-center justify-end">
50+
<div flex="~ items-center gap-2 wrap justify-end">
51+
<CompareDeltaValue :value="delta" :format="format" signed />
52+
<span v-if="ratioText" rounded bg-active px2 py0.5 font-mono text-xs :class="ratioClass">
53+
{{ ratioText }}
54+
</span>
55+
</div>
56+
</div>
57+
58+
<div grid="~ cols-2" min-h-30>
59+
<div p4 min-w-0 flex="~ col gap-3 justify-between" :class="{ op35: !previousTitle }">
60+
<template v-if="previousTitle">
61+
<div min-w-0>
62+
<div flex="~ items-center gap-2" text-xs op50 mb2>
63+
<span i-ph-clock-counter-clockwise-duotone />
64+
Session A
65+
</div>
66+
<div flex="~ items-baseline gap-2" min-w-0 font-mono font-600 :title="previousTitleMeta ? `${previousTitle} (${previousTitleMeta})` : previousTitle">
67+
<span truncate>{{ previousTitle }}</span>
68+
<span v-if="previousTitleMeta" flex-none font-400 op50>({{ previousTitleMeta }})</span>
69+
</div>
70+
<div v-if="previousSubtitle" truncate text-xs op50 mt1 :title="previousSubtitle">
71+
{{ previousSubtitle }}
72+
</div>
73+
</div>
74+
<div flex="~ items-center gap-2 wrap">
75+
<DisplayBadge v-for="badge of previousBadges" :key="badge" :text="badge" />
76+
<span v-if="previousStats.length" flex="~ items-baseline gap-1" text-xs op55>
77+
<span>(</span>
78+
<template v-for="(stat, index) of previousStats" :key="stat.label">
79+
<span v-if="index" op70>|</span>
80+
<span font-mono font-600 :class="stat.tone === 'increase' ? 'text-green-500' : stat.tone === 'decrease' ? 'text-red-500' : 'op85'">{{ stat.value }}</span>
81+
<span>{{ stat.label }}</span>
82+
</template>
83+
<span>)</span>
84+
</span>
85+
<span flex-1 />
86+
<span text-lg font-600>
87+
<CompareDeltaValue :value="previous" :format="format" />
88+
</span>
89+
</div>
90+
</template>
91+
<div v-else h-full min-h-16 flex="~ items-center justify-center" text-sm italic op60>
92+
Not present
93+
</div>
94+
</div>
95+
96+
<div border="l base" p4 min-w-0 flex="~ col gap-3 justify-between" :class="{ op35: !currentTitle }">
97+
<template v-if="currentTitle">
98+
<div min-w-0>
99+
<div flex="~ items-center gap-2" text-xs op50 mb2>
100+
<span i-ph-clock-duotone />
101+
Session B
102+
</div>
103+
<div flex="~ items-baseline gap-2" min-w-0 font-mono font-600 :title="currentTitleMeta ? `${currentTitle} (${currentTitleMeta})` : currentTitle">
104+
<span truncate>{{ currentTitle }}</span>
105+
<span v-if="currentTitleMeta" flex-none font-400 op50>({{ currentTitleMeta }})</span>
106+
</div>
107+
<div v-if="currentSubtitle" truncate text-xs op50 mt1 :title="currentSubtitle">
108+
{{ currentSubtitle }}
109+
</div>
110+
</div>
111+
<div flex="~ items-center gap-2 wrap">
112+
<DisplayBadge v-for="badge of currentBadges" :key="badge" :text="badge" />
113+
<span v-if="currentStats.length" flex="~ items-baseline gap-1" text-xs op55>
114+
<span>(</span>
115+
<template v-for="(stat, index) of currentStats" :key="stat.label">
116+
<span v-if="index" op70>|</span>
117+
<span font-mono font-600 :class="stat.tone === 'increase' ? 'text-green-500' : stat.tone === 'decrease' ? 'text-red-500' : 'op85'">{{ stat.value }}</span>
118+
<span>{{ stat.label }}</span>
119+
</template>
120+
<span>)</span>
121+
</span>
122+
<span flex-1 />
123+
<span text-lg font-600>
124+
<CompareDeltaValue :value="current" :format="format" />
125+
</span>
126+
</div>
127+
</template>
128+
<div v-else h-full min-h-16 flex="~ items-center justify-center" text-sm italic op60>
129+
Not present
130+
</div>
131+
</div>
132+
</div>
133+
</div>
134+
</template>

0 commit comments

Comments
 (0)