@xen-orchestra/web-core 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ <template>
2
+ <div class="vts-progress-bar">
3
+ <UiDataRuler :max="percentageCap" :warning="threshold.payload" />
4
+ <UiProgressBar :accent="threshold.payload.accent ?? 'info'" :fill-width :legend />
5
+ </div>
6
+ </template>
7
+
8
+ <script generic="TSource" lang="ts" setup>
9
+ import UiDataRuler from '@core/components/ui/data-ruler/UiDataRuler.vue'
10
+ import UiProgressBar, { type ProgressBarAccent } from '@core/components/ui/progress-bar/UiProgressBar.vue'
11
+ import { useProgress } from '@core/packages/progress/use-progress.ts'
12
+ import type { ThresholdConfig } from '@core/packages/threshold/type.ts'
13
+ import { useThreshold } from '@core/packages/threshold/use-threshold.ts'
14
+ import { defaultProgressThresholds, useProgressToLegend } from '@core/utils/progress.util.ts'
15
+
16
+ export type ProgressBarLegendType = 'percent' | 'bytes' | 'bytes-with-total' | 'value' | 'value-with-total'
17
+
18
+ export type ProgressBarThresholdPayload = { accent?: ProgressBarAccent; tooltip?: string }
19
+
20
+ export type ProgressBarThresholdConfig = ThresholdConfig<ProgressBarThresholdPayload>
21
+
22
+ const {
23
+ current,
24
+ total,
25
+ label,
26
+ thresholds = defaultProgressThresholds(),
27
+ legendType,
28
+ } = defineProps<{
29
+ current: number
30
+ total: number
31
+ label: string
32
+ legendType?: ProgressBarLegendType
33
+ thresholds?: ProgressBarThresholdConfig
34
+ }>()
35
+
36
+ const progress = useProgress(
37
+ () => current,
38
+ () => total
39
+ )
40
+
41
+ const { percentageCap, percentage, fillWidth } = progress
42
+
43
+ const legend = useProgressToLegend(() => legendType, label, progress)
44
+
45
+ const threshold = useThreshold(percentage, () => thresholds)
46
+ </script>
47
+
48
+ <style lang="postcss" scoped>
49
+ .vts-progress-bar {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: 1.6rem;
53
+ }
54
+ </style>
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <div class="vts-progress-bar-group">
3
+ <UiDataRuler :max="highestPercentageCap" :warning="threshold.payload" />
4
+ <UiProgressBar
5
+ v-for="item of progressItems"
6
+ :key="item.source.id"
7
+ :accent="item.threshold.payload?.accent ?? 'info'"
8
+ :fill-width="item.fillWidth"
9
+ :legend="toLegend(item.source.label, item)"
10
+ />
11
+ </div>
12
+ </template>
13
+
14
+ <script generic="TSource" lang="ts" setup>
15
+ import type {
16
+ ProgressBarLegendType,
17
+ ProgressBarThresholdConfig,
18
+ } from '@core/components/progress-bar/VtsProgressBar.vue'
19
+ import UiDataRuler from '@core/components/ui/data-ruler/UiDataRuler.vue'
20
+ import UiProgressBar from '@core/components/ui/progress-bar/UiProgressBar.vue'
21
+ import { useProgressGroup } from '@core/packages/progress/use-progress-group.ts'
22
+ import { useThreshold } from '@core/packages/threshold/use-threshold.ts'
23
+ import { defaultProgressThresholds, useProgressToLegend } from '@core/utils/progress.util.ts'
24
+ import { computed } from 'vue'
25
+
26
+ export type ProgressBarGroupItem = { id: string; current: number; total: number; label: string }
27
+
28
+ const {
29
+ items,
30
+ thresholds = defaultProgressThresholds(),
31
+ sort = 'desc',
32
+ legendType,
33
+ nItems,
34
+ } = defineProps<{
35
+ items: ProgressBarGroupItem[]
36
+ legendType?: ProgressBarLegendType
37
+ thresholds?: ProgressBarThresholdConfig
38
+ sort?: 'asc' | 'desc' | 'none'
39
+ nItems?: number
40
+ }>()
41
+
42
+ const {
43
+ highestPercentageCap,
44
+ progressItems: rawProgressItems,
45
+ highestPercentage,
46
+ } = useProgressGroup(() => items, {
47
+ sort: () => (sort === 'none' ? undefined : sort),
48
+ })
49
+
50
+ const progressItems = computed(() => {
51
+ const items = rawProgressItems.value.map(item => {
52
+ return {
53
+ ...item,
54
+ threshold: useThreshold(item.percentage, thresholds).value,
55
+ }
56
+ })
57
+
58
+ if (nItems) {
59
+ return items.slice(0, nItems)
60
+ }
61
+
62
+ return items
63
+ })
64
+
65
+ const toLegend = useProgressToLegend(() => legendType)
66
+
67
+ const threshold = useThreshold(highestPercentage, () => thresholds)
68
+ </script>
69
+
70
+ <style lang="postcss" scoped>
71
+ .vts-progress-bar-group {
72
+ display: flex;
73
+ flex-direction: column;
74
+ gap: 1.6rem;
75
+ }
76
+ </style>
@@ -1,24 +1,72 @@
1
1
  <!-- v1 -->
2
2
  <template>
3
3
  <div class="ui-data-ruler typo-body-regular-small">
4
- <span>{{ t('n-percent', 0) }}</span>
5
- <span>{{ t('n-percent', 50) }}</span>
6
- <span>{{ t('n-percent', 100) }}</span>
4
+ <span>{{ n(0, 'percent') }}</span>
5
+ <span>{{ n(max / 200, 'percent') }}</span>
6
+ <span class="max">
7
+ <VtsIcon
8
+ v-if="warning?.accent || warning?.tooltip"
9
+ v-tooltip="warning.tooltip ?? false"
10
+ :class="warning.accent ?? 'warning'"
11
+ class="icon"
12
+ name="fa:exclamation-circle"
13
+ size="small"
14
+ />
15
+ {{ n(max / 100, 'percent') }}</span
16
+ >
7
17
  </div>
8
18
  </template>
9
19
 
10
20
  <script lang="ts" setup>
21
+ import VtsIcon from '@core/components/icon/VtsIcon.vue'
22
+ import { vTooltip } from '@core/directives/tooltip.directive.ts'
11
23
  import { useI18n } from 'vue-i18n'
12
24
 
13
- const { t } = useI18n()
25
+ const { max = 100 } = defineProps<{
26
+ max?: number
27
+ warning?: {
28
+ accent?: 'info' | 'warning' | 'danger'
29
+ tooltip?: string
30
+ }
31
+ }>()
32
+
33
+ const { n } = useI18n()
14
34
  </script>
15
35
 
16
36
  <style lang="postcss" scoped>
17
37
  .ui-data-ruler {
18
- display: flex;
38
+ display: grid;
39
+ grid-template-columns: 1fr auto 1fr;
19
40
  align-items: center;
20
- justify-content: space-between;
21
41
  gap: 1rem;
22
42
  color: var(--color-neutral-txt-secondary);
43
+
44
+ .icon {
45
+ &.warning {
46
+ color: var(--color-warning-item-base);
47
+ }
48
+
49
+ &.danger {
50
+ color: var(--color-danger-item-base);
51
+ }
52
+ }
53
+
54
+ .max {
55
+ display: flex;
56
+ align-items: center;
57
+ gap: 0.8rem;
58
+ }
59
+
60
+ & > span:first-child {
61
+ justify-self: start;
62
+ }
63
+
64
+ & > span:nth-child(2) {
65
+ justify-self: center;
66
+ }
67
+
68
+ & > span:last-child {
69
+ justify-self: end;
70
+ }
23
71
  }
24
72
  </style>
@@ -1,19 +1,12 @@
1
1
  <!-- v2 -->
2
2
  <template>
3
- <div class="ui-progress-bar" :class="className">
3
+ <div :class="className" class="ui-progress-bar">
4
4
  <div class="progress-bar">
5
- <div class="fill" :style="{ width: `${fillWidth}%` }" />
5
+ <div :style="{ width: fillWidth }" class="fill" />
6
6
  </div>
7
- <div v-if="shouldShowSteps" class="steps typo-body-regular-small">
8
- <span>{{ n(0, 'percent') }}</span>
9
- <span v-for="step in steps" :key="step">{{ n(step, 'percent') }}</span>
10
- </div>
11
- <VtsLegendList class="legend">
12
- <UiLegend v-if="displayMode === 'percent'" :accent :value="Math.round(percentage)" unit="%">
13
- {{ legend }}
14
- </UiLegend>
15
- <UiLegend v-else :accent :value="legendValue">
16
- {{ legend }}
7
+ <VtsLegendList v-if="legend" class="legend">
8
+ <UiLegend :accent :value="legend.value">
9
+ {{ legend.label }}
17
10
  </UiLegend>
18
11
  </VtsLegendList>
19
12
  </div>
@@ -22,61 +15,20 @@
22
15
  <script lang="ts" setup>
23
16
  import VtsLegendList from '@core/components/legend-list/VtsLegendList.vue'
24
17
  import UiLegend from '@core/components/ui/legend/UiLegend.vue'
25
- import { formatSizeRaw } from '@core/utils/size.util.ts'
26
18
  import { toVariants } from '@core/utils/to-variants.util'
27
- import { useClamp, useMax } from '@vueuse/math'
28
19
  import { computed } from 'vue'
29
- import { useI18n } from 'vue-i18n'
30
-
31
- interface Props {
32
- legend: string
33
- value: number
34
- showSteps?: boolean
35
- }
36
- interface PercentageProps {
37
- max?: number
38
- displayMode: 'percent'
39
- }
40
- interface ValueProps {
41
- max: number
42
- displayMode: 'value'
43
- }
44
-
45
- const { value: _value, max: _max, showSteps, displayMode } = defineProps<Props & (PercentageProps | ValueProps)>()
46
-
47
- const { n } = useI18n()
48
20
 
49
- const value = useMax(0, () => _value)
21
+ export type ProgressBarAccent = 'info' | 'warning' | 'danger'
50
22
 
51
- const max = computed(() => _max ?? 100)
23
+ export type ProgressBarLegend = { label: string; value?: string | number }
52
24
 
53
- const percentage = computed(() => (max.value <= 0 ? 0 : (value.value / max.value) * 100))
54
- const maxPercentage = computed(() => Math.ceil(percentage.value / 100) * 100)
55
- const fillWidth = useClamp(() => (percentage.value / maxPercentage.value) * 100 || 0, 0, 100)
56
- const shouldShowSteps = computed(() => showSteps || percentage.value > 100)
57
- const steps = useMax(1, () => Math.floor(maxPercentage.value / 100))
58
-
59
- const accent = computed(() => {
60
- if (percentage.value >= 90) {
61
- return 'danger'
62
- }
25
+ const { accent, legend } = defineProps<{
26
+ accent: ProgressBarAccent
27
+ fillWidth: string
28
+ legend?: ProgressBarLegend
29
+ }>()
63
30
 
64
- if (percentage.value >= 80) {
65
- return 'warning'
66
- }
67
-
68
- return 'info'
69
- })
70
-
71
- const className = computed(() => toVariants({ accent: accent.value }))
72
-
73
- const formattedValue = computed(() => formatSizeRaw(value.value, 1))
74
-
75
- const formattedMax = computed(() => formatSizeRaw(max.value, 0))
76
-
77
- const legendValue = computed(() => {
78
- return `${formattedValue.value.value} ${formattedValue.value.prefix} / ${formattedMax.value.value} ${formattedMax.value.prefix}`
79
- })
31
+ const className = computed(() => toVariants({ accent }))
80
32
  </script>
81
33
 
82
34
  <style lang="postcss" scoped>
@@ -87,30 +39,22 @@ const legendValue = computed(() => {
87
39
 
88
40
  .progress-bar {
89
41
  width: 100%;
90
- height: 1.2rem;
91
42
  border-radius: 0.4rem;
92
43
  overflow: hidden;
93
44
  background-color: var(--color-neutral-background-disabled);
94
45
 
95
46
  .fill {
96
47
  width: 0;
97
- height: 100%;
48
+ height: 1.2rem;
98
49
  transition: width 0.25s ease-in-out;
99
50
  }
100
51
  }
101
52
 
102
- .steps {
103
- color: var(--color-neutral-txt-secondary);
104
- display: flex;
105
- justify-content: space-between;
106
- }
107
-
108
53
  .legend {
109
54
  margin-inline-start: auto;
110
55
  }
111
56
 
112
57
  /* ACCENT */
113
-
114
58
  &.accent--info {
115
59
  .fill {
116
60
  background-color: var(--color-info-item-base);
@@ -260,6 +260,7 @@
260
260
  "hyper-threading": "Hyper threading (SMT)",
261
261
  "id": "ID",
262
262
  "in-last-three-jobs": "In their last three jobs",
263
+ "in-progress": "In progress",
263
264
  "install-settings": "Install settings",
264
265
  "interfaces": "Interface | Interface | Interfaces",
265
266
  "interrupted": "Interrupted",
@@ -260,6 +260,7 @@
260
260
  "hyper-threading": "Hyper-threading (SMT)",
261
261
  "id": "ID",
262
262
  "in-last-three-jobs": "Dans leurs trois derniers jobs",
263
+ "in-progress": "En cours",
263
264
  "install-settings": "Paramètres d'installation",
264
265
  "interfaces": "Interface | Interface | Interfaces",
265
266
  "interrupted": "Interrompu",
@@ -37,7 +37,7 @@ export function useProgressGroup<TSource>(
37
37
  )
38
38
 
39
39
  const highestPercentageCap = computed(() =>
40
- progressItems.value.reduce((highestCap, item) => Math.max(highestCap, item.percentageCap.value), 0)
40
+ progressItems.value.reduce((highestCap, item) => Math.max(highestCap, item.percentageCap.value), 100)
41
41
  )
42
42
 
43
43
  const normalizedProgressItems = useArrayMap(progressItems, item => {
@@ -0,0 +1,72 @@
1
+ import type {
2
+ ProgressBarLegendType,
3
+ ProgressBarThresholdPayload,
4
+ } from '@core/components/progress-bar/VtsProgressBar.vue'
5
+ import type { ProgressBarLegend } from '@core/components/ui/progress-bar/UiProgressBar.vue'
6
+ import type { Progress } from '@core/packages/progress/types.ts'
7
+ import type { ThresholdConfig } from '@core/packages/threshold/type.ts'
8
+ import { formatSize } from '@core/utils/size.util.ts'
9
+ import { computed, type ComputedRef, type MaybeRefOrGetter, type Reactive, toValue } from 'vue'
10
+ import { useI18n } from 'vue-i18n'
11
+
12
+ export function defaultProgressThresholds(tooltip?: string): ThresholdConfig<ProgressBarThresholdPayload> {
13
+ return {
14
+ 80: { accent: 'warning', tooltip },
15
+ 90: { accent: 'danger', tooltip },
16
+ default: {},
17
+ }
18
+ }
19
+
20
+ export function cpuProgressThresholds(tooltip?: string): ThresholdConfig<ProgressBarThresholdPayload> {
21
+ return {
22
+ 100: { accent: 'warning', tooltip },
23
+ 300: { accent: 'danger', tooltip },
24
+ default: {},
25
+ }
26
+ }
27
+
28
+ export function useProgressToLegend(
29
+ rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>
30
+ ): (label: string, progress: Progress | Reactive<Progress>) => ProgressBarLegend | undefined
31
+
32
+ export function useProgressToLegend(
33
+ rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>,
34
+ label: string,
35
+ progress: Progress | Reactive<Progress>
36
+ ): ComputedRef<ProgressBarLegend | undefined>
37
+
38
+ export function useProgressToLegend(
39
+ rawType: MaybeRefOrGetter<ProgressBarLegendType | undefined>,
40
+ label?: string,
41
+ progress?: Progress | Reactive<Progress>
42
+ ) {
43
+ const { n } = useI18n()
44
+
45
+ const type = computed(() => toValue(rawType))
46
+
47
+ function toLegend(label: string, progress: Progress | Reactive<Progress>): ProgressBarLegend | undefined {
48
+ switch (type.value) {
49
+ case 'percent':
50
+ return { label, value: n(toValue(progress.percentage) / 100, { maximumFractionDigits: 1, style: 'percent' }) }
51
+ case 'bytes':
52
+ return { label, value: formatSize(toValue(progress.current), 0) }
53
+ case 'bytes-with-total':
54
+ return {
55
+ label,
56
+ value: `${formatSize(toValue(progress.current), 0)} / ${formatSize(toValue(progress.total), 1)}`,
57
+ }
58
+ case 'value':
59
+ return { label, value: n(toValue(progress.current)) }
60
+ case 'value-with-total':
61
+ return { label, value: `${n(toValue(progress.current))} / ${n(toValue(progress.total))}` }
62
+ default:
63
+ return undefined
64
+ }
65
+ }
66
+
67
+ if (label && progress) {
68
+ return computed(() => toLegend(label, progress))
69
+ }
70
+
71
+ return toLegend
72
+ }
@@ -39,3 +39,9 @@ export const formatSizeParse = (size: number | undefined, unit?: string) => {
39
39
 
40
40
  return result
41
41
  }
42
+
43
+ export function formatSize(bytes: number, decimals: number): string {
44
+ const { value, prefix } = formatSizeRaw(bytes, decimals)
45
+
46
+ return `${value} ${prefix}`
47
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xen-orchestra/web-core",
3
3
  "type": "module",
4
- "version": "0.28.0",
4
+ "version": "0.29.0",
5
5
  "private": false,
6
6
  "exports": {
7
7
  "./*": {