@xen-orchestra/web-core 0.35.1 → 0.36.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.
Files changed (117) hide show
  1. package/lib/components/console/VtsRemoteConsole.vue +1 -1
  2. package/lib/components/copy-button/VtsCopyButton.vue +1 -1
  3. package/lib/components/layout/VtsLayoutSidebar.vue +1 -1
  4. package/lib/components/quick-info-card/VtsQuickInfoCard.vue +1 -1
  5. package/lib/components/relative-time/VtsRelativeTime.vue +2 -3
  6. package/lib/components/select/VtsSelect.vue +1 -1
  7. package/lib/components/state-hero/VtsStateHero.vue +20 -10
  8. package/lib/components/table/VtsRow.vue +26 -0
  9. package/lib/components/table/VtsTable.vue +99 -42
  10. package/lib/components/table/cells/VtsCollapsedListCell.vue +59 -0
  11. package/lib/components/table/cells/VtsHeaderCell.vue +31 -0
  12. package/lib/components/table/cells/VtsLinkCell.vue +33 -0
  13. package/lib/components/table/cells/VtsNumberCell.vue +21 -0
  14. package/lib/components/{size-progress-cell/VtsSizeProgressCell.vue → table/cells/VtsProgressBarCell.vue} +8 -5
  15. package/lib/components/table/cells/VtsStatusCell.vue +69 -0
  16. package/lib/components/table/cells/VtsTagCell.vue +24 -0
  17. package/lib/components/table/cells/VtsTextCell.vue +32 -0
  18. package/lib/components/table/cells/VtsTruncatedTextCell.vue +64 -0
  19. package/lib/components/task/VtsQuickTaskButton.vue +1 -1
  20. package/lib/components/tree/VtsTreeLine.vue +1 -1
  21. package/lib/components/ui/alert/UiAlert.vue +1 -1
  22. package/lib/components/ui/button/UiButton.vue +4 -2
  23. package/lib/components/ui/button-icon/UiButtonIcon.vue +12 -11
  24. package/lib/components/ui/column-header/UiColumnHeader.vue +22 -0
  25. package/lib/components/ui/info/UiInfo.vue +5 -1
  26. package/lib/components/ui/input/UiInput.vue +3 -2
  27. package/lib/components/ui/link/UiLink.vue +5 -0
  28. package/lib/components/ui/log-entry-viewer/UiLogEntryViewer.vue +1 -1
  29. package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +2 -2
  30. package/lib/components/ui/table-cell/UiTableCell.vue +41 -0
  31. package/lib/components/ui/table-pagination/PaginationButton.vue +1 -1
  32. package/lib/components/ui/toaster/UiToaster.vue +1 -1
  33. package/lib/components/ui/top-bottom-table/UiTopBottomTable.vue +6 -2
  34. package/lib/composables/pagination.composable.ts +16 -1
  35. package/lib/composables/relative-time.composable.ts +8 -61
  36. package/lib/composables/table-state.composable.ts +56 -0
  37. package/lib/composables/tree-filter.composable.ts +2 -2
  38. package/lib/icons/fa-icons.ts +2 -0
  39. package/lib/icons/object-icons.ts +1 -1
  40. package/lib/locales/en.json +23 -0
  41. package/lib/locales/fr.json +23 -0
  42. package/lib/packages/form-select/use-form-select-controller.ts +1 -0
  43. package/lib/packages/table/README.md +53 -308
  44. package/lib/packages/table/define-column.ts +7 -0
  45. package/lib/packages/table/define-columns.ts +104 -50
  46. package/lib/packages/table/index.ts +3 -11
  47. package/lib/packages/table/types.ts +10 -0
  48. package/lib/{composables/tree.composable.md → packages/tree/README.md} +28 -23
  49. package/lib/{composables → packages}/tree/branch-definition.ts +9 -4
  50. package/lib/{composables → packages}/tree/branch.ts +15 -20
  51. package/lib/{composables → packages}/tree/build-nodes.ts +5 -5
  52. package/lib/{composables → packages}/tree/define-branch.ts +8 -4
  53. package/lib/{composables → packages}/tree/define-leaf.ts +8 -3
  54. package/lib/{composables → packages}/tree/define-tree.ts +10 -5
  55. package/lib/{composables → packages}/tree/leaf-definition.ts +1 -1
  56. package/lib/{composables → packages}/tree/leaf.ts +3 -3
  57. package/lib/{composables → packages}/tree/tree-node-base.ts +18 -3
  58. package/lib/{composables → packages}/tree/tree-node-definition-base.ts +4 -2
  59. package/lib/{composables → packages}/tree/types.ts +11 -9
  60. package/lib/{composables/tree.composable.ts → packages/tree/use-tree.ts} +24 -11
  61. package/lib/tables/column-definitions/address-column.ts +4 -0
  62. package/lib/tables/column-definitions/button-column.ts +35 -0
  63. package/lib/tables/column-definitions/button-icon-column.ts +30 -0
  64. package/lib/tables/column-definitions/collapsed-list-column.ts +12 -0
  65. package/lib/tables/column-definitions/date-column.ts +34 -0
  66. package/lib/tables/column-definitions/info-column.ts +12 -0
  67. package/lib/tables/column-definitions/input-column.ts +32 -0
  68. package/lib/tables/column-definitions/link-column.ts +14 -0
  69. package/lib/tables/column-definitions/literal-column.ts +9 -0
  70. package/lib/tables/column-definitions/number-column.ts +10 -0
  71. package/lib/tables/column-definitions/percent-column.ts +15 -0
  72. package/lib/tables/column-definitions/progress-bar-column.ts +10 -0
  73. package/lib/tables/column-definitions/select-column.ts +12 -0
  74. package/lib/tables/column-definitions/select-item-column.ts +8 -0
  75. package/lib/tables/column-definitions/status-column.ts +16 -0
  76. package/lib/tables/column-definitions/tag-column.ts +11 -0
  77. package/lib/tables/column-definitions/text-column.ts +11 -0
  78. package/lib/tables/column-definitions/truncated-text-column.ts +10 -0
  79. package/lib/tables/column-sets/backup-issue-columns.ts +15 -0
  80. package/lib/tables/column-sets/backup-job-columns.ts +23 -0
  81. package/lib/tables/column-sets/backup-job-schedule-columns.ts +21 -0
  82. package/lib/tables/column-sets/backup-log-columns.ts +19 -0
  83. package/lib/tables/column-sets/host-columns.ts +19 -0
  84. package/lib/tables/column-sets/network-columns.ts +22 -0
  85. package/lib/tables/column-sets/new-vm-network-columns.ts +24 -0
  86. package/lib/tables/column-sets/new-vm-sr-columns.ts +33 -0
  87. package/lib/tables/column-sets/patch-columns.ts +13 -0
  88. package/lib/tables/column-sets/pif-columns.ts +23 -0
  89. package/lib/tables/column-sets/server-columns.ts +18 -0
  90. package/lib/tables/column-sets/sr-columns.ts +20 -0
  91. package/lib/tables/column-sets/vdi-columns.ts +21 -0
  92. package/lib/tables/column-sets/vif-columns.ts +23 -0
  93. package/lib/tables/column-sets/vm-columns.ts +21 -0
  94. package/lib/tables/helpers/render-body-cell.ts +4 -0
  95. package/lib/tables/helpers/render-head-cell.ts +6 -0
  96. package/lib/tables/helpers/render-loading-cell.ts +5 -0
  97. package/lib/tables/types.ts +7 -0
  98. package/lib/utils/size.util.ts +5 -9
  99. package/package.json +1 -1
  100. package/lib/components/data-table/VtsDataTable.vue +0 -70
  101. package/lib/components/table/ColumnTitle.vue +0 -152
  102. package/lib/packages/table/apply-extensions.ts +0 -26
  103. package/lib/packages/table/define-renderer/define-table-cell-renderer.ts +0 -27
  104. package/lib/packages/table/define-renderer/define-table-renderer.ts +0 -47
  105. package/lib/packages/table/define-renderer/define-table-row-renderer.ts +0 -29
  106. package/lib/packages/table/define-renderer/define-table-section-renderer.ts +0 -29
  107. package/lib/packages/table/define-table/define-multi-source-table.ts +0 -39
  108. package/lib/packages/table/define-table/define-table.ts +0 -13
  109. package/lib/packages/table/define-table/define-typed-table.ts +0 -18
  110. package/lib/packages/table/transform-sources.ts +0 -13
  111. package/lib/packages/table/types/extensions.ts +0 -16
  112. package/lib/packages/table/types/index.ts +0 -47
  113. package/lib/packages/table/types/table-cell.ts +0 -18
  114. package/lib/packages/table/types/table-row.ts +0 -20
  115. package/lib/packages/table/types/table-section.ts +0 -19
  116. package/lib/packages/table/types/table.ts +0 -28
  117. package/lib/types/button.type.ts +0 -3
@@ -0,0 +1,64 @@
1
+ <template>
2
+ <UiTableCell>
3
+ <div class="content">
4
+ <span class="text">
5
+ {{ truncatedContent }}
6
+ </span>
7
+
8
+ <UiButton
9
+ v-if="shouldTruncate"
10
+ accent="brand"
11
+ size="small"
12
+ variant="tertiary"
13
+ class="typo-body-regular-small button"
14
+ @click="toggleExpanded()"
15
+ >
16
+ {{ isExpanded ? t('show-less') : t('show-more') }}
17
+ </UiButton>
18
+ </div>
19
+ </UiTableCell>
20
+ </template>
21
+
22
+ <script setup lang="ts">
23
+ import UiButton from '@core/components/ui/button/UiButton.vue'
24
+ import UiTableCell from '@core/components/ui/table-cell/UiTableCell.vue'
25
+ import { useToggle } from '@vueuse/shared'
26
+ import { computed } from 'vue'
27
+ import { useI18n } from 'vue-i18n'
28
+
29
+ const { content, limit } = defineProps<{
30
+ content: string
31
+ limit?: number
32
+ }>()
33
+
34
+ const DEFAULT_TRUNCATE_LIMIT = 90
35
+
36
+ const TRUNCATE_TOLERANCE = 20
37
+
38
+ const { t } = useI18n()
39
+
40
+ const truncateLimit = computed(() => limit ?? DEFAULT_TRUNCATE_LIMIT)
41
+
42
+ const shouldTruncate = computed(() => content.length > truncateLimit.value + TRUNCATE_TOLERANCE)
43
+
44
+ const [isExpanded, toggleExpanded] = useToggle(false)
45
+
46
+ const truncatedContent = computed(() => {
47
+ if (!shouldTruncate.value || isExpanded.value) {
48
+ return content
49
+ }
50
+
51
+ return `${content.slice(0, truncateLimit.value)}…`
52
+ })
53
+ </script>
54
+
55
+ <style lang="postcss" scoped>
56
+ .text {
57
+ overflow-wrap: anywhere;
58
+ }
59
+
60
+ .button {
61
+ float: right;
62
+ margin-top: -0.25rem;
63
+ }
64
+ </style>
@@ -5,7 +5,7 @@
5
5
  accent="brand"
6
6
  :dot="hasNewTask"
7
7
  icon="fa:bars-progress"
8
- size="large"
8
+ size="medium"
9
9
  @click="isPanelOpen = true"
10
10
  />
11
11
  <Teleport v-if="isPanelOpen" to="body">
@@ -14,7 +14,7 @@ defineProps<{
14
14
 
15
15
  <style lang="postcss" scoped>
16
16
  .vts-tree-line {
17
- flex: 0 1 1em;
17
+ flex: 0 1 1.5em;
18
18
  align-self: stretch;
19
19
  display: flex;
20
20
  align-items: center;
@@ -16,7 +16,7 @@
16
16
  class="close-button"
17
17
  icon="fa:xmark"
18
18
  accent="brand"
19
- size="medium"
19
+ size="small"
20
20
  @click="emit('close')"
21
21
  />
22
22
  </div>
@@ -18,7 +18,7 @@ export type ButtonVariant = 'primary' | 'secondary' | 'tertiary'
18
18
  export type ButtonAccent = 'brand' | 'warning' | 'danger'
19
19
  export type ButtonSize = 'small' | 'medium'
20
20
 
21
- const { accent, variant, size, disabled, busy, lockIcon } = defineProps<{
21
+ export type ButtonProps = {
22
22
  variant: ButtonVariant
23
23
  accent: ButtonAccent
24
24
  size: ButtonSize
@@ -26,7 +26,9 @@ const { accent, variant, size, disabled, busy, lockIcon } = defineProps<{
26
26
  disabled?: boolean
27
27
  lockIcon?: boolean
28
28
  leftIcon?: IconName
29
- }>()
29
+ }
30
+
31
+ const { accent, variant, size, disabled, busy, lockIcon } = defineProps<ButtonProps>()
30
32
 
31
33
  defineSlots<{
32
34
  default(): any
@@ -2,7 +2,7 @@
2
2
  <!-- TODO: Add complex icon -->
3
3
  <template>
4
4
  <button :class="classNames" :disabled class="ui-button-icon" type="button">
5
- <VtsIcon :name="icon" size="medium" />
5
+ <VtsIcon :name="icon" size="current" />
6
6
  <span v-if="dot" class="dot" />
7
7
  </button>
8
8
  </template>
@@ -13,8 +13,9 @@ import type { IconName } from '@core/icons'
13
13
  import { toVariants } from '@core/utils/to-variants.util'
14
14
  import { computed } from 'vue'
15
15
 
16
- type ButtonIconAccent = 'brand' | 'warning' | 'danger'
17
- type ButtonSize = 'small' | 'medium' | 'large'
16
+ export type ButtonIconAccent = 'brand' | 'warning' | 'danger'
17
+
18
+ export type ButtonIconSize = 'small' | 'medium' | 'large'
18
19
 
19
20
  const {
20
21
  accent,
@@ -24,7 +25,7 @@ const {
24
25
  targetScale = 1,
25
26
  } = defineProps<{
26
27
  icon: IconName
27
- size: ButtonSize
28
+ size: ButtonIconSize
28
29
  accent: ButtonIconAccent
29
30
  disabled?: boolean
30
31
  selected?: boolean
@@ -191,9 +192,9 @@ const classNames = computed(() => {
191
192
 
192
193
  &.size--small {
193
194
  & {
194
- width: 1.6rem;
195
- height: 1.6rem;
196
- font-size: 1.2rem;
195
+ width: 2.4rem;
196
+ height: 2.4rem;
197
+ font-size: 1.6rem;
197
198
  }
198
199
 
199
200
  .dot {
@@ -206,9 +207,9 @@ const classNames = computed(() => {
206
207
 
207
208
  &.size--medium {
208
209
  & {
209
- width: 2.4rem;
210
- height: 2.4rem;
211
- font-size: 1.6rem;
210
+ width: 3.2rem;
211
+ height: 3.2rem;
212
+ font-size: 2rem;
212
213
  }
213
214
 
214
215
  .dot {
@@ -223,7 +224,7 @@ const classNames = computed(() => {
223
224
  & {
224
225
  width: 4rem;
225
226
  height: 4rem;
226
- font-size: 2.4rem;
227
+ font-size: 3.2rem;
227
228
  }
228
229
 
229
230
  .dot {
@@ -0,0 +1,22 @@
1
+ <!-- WIP -->
2
+ <template>
3
+ <th class="ui-column-header typo-caption" scope="col">
4
+ <slot />
5
+ </th>
6
+ </template>
7
+
8
+ <style lang="postcss" scoped>
9
+ .ui-column-header {
10
+ max-width: 30rem;
11
+ padding: 0.8rem 1.2rem;
12
+ border-block-start: 0.1rem solid var(--color-neutral-border);
13
+ border-inline-end: 0.1rem solid var(--color-neutral-border);
14
+ text-align: start;
15
+ color: var(--color-brand-txt-base);
16
+ background-color: var(--color-neutral-background-primary);
17
+
18
+ &:last-child {
19
+ border-inline-end: none;
20
+ }
21
+ }
22
+ </style>
@@ -2,7 +2,7 @@
2
2
  <template>
3
3
  <div class="ui-info">
4
4
  <VtsIcon class="icon" :name="icon" size="medium" />
5
- <p v-tooltip="!wrap" class="typo-body-regular-small" :class="{ 'text-ellipsis': !wrap }">
5
+ <p v-tooltip="!wrap" class="typo-body-regular-small label" :class="{ 'text-ellipsis': !wrap }">
6
6
  <slot />
7
7
  </p>
8
8
  </div>
@@ -47,5 +47,9 @@ const icon = useMapper<InfoAccent, IconName>(
47
47
  .icon {
48
48
  font-size: 1.6rem;
49
49
  }
50
+
51
+ .label:empty {
52
+ display: none;
53
+ }
50
54
  }
51
55
  </style>
@@ -35,8 +35,9 @@ import { IK_INPUT_WRAPPER_CONTROLLER } from '@core/utils/injection-keys.util'
35
35
  import { toVariants } from '@core/utils/to-variants.util'
36
36
  import { inject, ref, useAttrs, watchEffect } from 'vue'
37
37
 
38
- type InputAccent = 'brand' | 'warning' | 'danger'
39
- type InputType = 'text' | 'number' | 'password' | 'search'
38
+ export type InputAccent = 'brand' | 'warning' | 'danger'
39
+
40
+ export type InputType = 'text' | 'number' | 'password' | 'search'
40
41
 
41
42
  defineOptions({
42
43
  inheritAttrs: false,
@@ -62,6 +62,11 @@ const classes = computed(() => [typoClasses[props.size], { disabled: isDisabled.
62
62
  cursor: not-allowed;
63
63
  }
64
64
 
65
+ &:not([href]) {
66
+ text-decoration: none;
67
+ cursor: default;
68
+ }
69
+
65
70
  .external-icon {
66
71
  font-size: 0.75em;
67
72
  }
@@ -9,7 +9,7 @@
9
9
  <VtsCopyButton :value="content" />
10
10
  <UiButtonIcon
11
11
  icon="fa:arrow-up-right-from-square"
12
- size="medium"
12
+ size="small"
13
13
  accent="brand"
14
14
  @click="openRawValueInNewTab()"
15
15
  />
@@ -32,8 +32,8 @@
32
32
 
33
33
  <!-- Mobile icons: search + filter -->
34
34
  <template v-else>
35
- <UiButtonIcon accent="brand" size="medium" type="submit" icon="fa:magnifying-glass" class="action-button" />
36
- <UiButtonIcon accent="brand" size="medium" disabled icon="fa:filter" class="action-button" />
35
+ <UiButtonIcon accent="brand" size="small" type="submit" icon="fa:magnifying-glass" class="action-button" />
36
+ <UiButtonIcon accent="brand" size="small" disabled icon="fa:filter" class="action-button" />
37
37
  </template>
38
38
  </form>
39
39
  </template>
@@ -0,0 +1,41 @@
1
+ <!-- WIP -->
2
+ <template>
3
+ <td class="ui-table-cell typo-body-regular" :class="align">
4
+ <slot />
5
+ </td>
6
+ </template>
7
+
8
+ <script lang="ts" setup>
9
+ export type TableCellAlign = 'start' | 'center' | 'end'
10
+
11
+ const { align = 'start' } = defineProps<{
12
+ align?: TableCellAlign
13
+ }>()
14
+ </script>
15
+
16
+ <style lang="postcss" scoped>
17
+ .ui-table-cell {
18
+ padding: 1.2rem;
19
+ border: 0.1rem solid var(--color-neutral-border);
20
+ border-inline-start: none;
21
+ color: var(--color-neutral-txt-primary);
22
+ background-color: var(--ui-table-cell-background-color, var(--color-neutral-background-primary));
23
+ max-width: 50rem;
24
+
25
+ &:last-child {
26
+ border-inline-end: none;
27
+ }
28
+
29
+ &.start {
30
+ text-align: start;
31
+ }
32
+
33
+ &.center {
34
+ text-align: center;
35
+ }
36
+
37
+ &.end {
38
+ text-align: end;
39
+ }
40
+ }
41
+ </style>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <UiButtonIcon :disabled accent="brand" class="pagination-button" size="medium" :icon />
2
+ <UiButtonIcon :disabled accent="brand" class="pagination-button" size="small" :icon />
3
3
  </template>
4
4
 
5
5
  <script setup lang="ts">
@@ -11,7 +11,7 @@
11
11
  <slot name="description" />
12
12
  </div>
13
13
  </div>
14
- <UiButtonIcon class="close-icon" icon="fa:xmark" accent="brand" size="medium" @click="emit('close')" />
14
+ <UiButtonIcon class="close-icon" icon="fa:xmark" accent="brand" size="small" @click="emit('close')" />
15
15
  </div>
16
16
  <div v-if="slots.actions" class="actions">
17
17
  <slot name="actions" />
@@ -1,7 +1,7 @@
1
1
  <!-- v2 -->
2
2
  <template>
3
- <div class="ui-top-bottom-table">
4
- <div class="content">
3
+ <div class="ui-top-bottom-table" :class="{ 'no-content': totalItems === 0 }">
4
+ <div v-if="totalItems > 0" class="content">
5
5
  <span class="typo-body-regular-small label">
6
6
  {{ t('core.select.n-selected-of', { count: selectedItems, total: totalItems }) }}
7
7
  </span>
@@ -63,5 +63,9 @@ const { t } = useI18n()
63
63
  .label {
64
64
  color: var(--color-neutral-txt-secondary);
65
65
  }
66
+
67
+ &.no-content {
68
+ justify-content: flex-end;
69
+ }
66
70
  }
67
71
  </style>
@@ -5,6 +5,21 @@ import { toComputed } from '@core/utils/to-computed.util'
5
5
  import { clamp, useLocalStorage } from '@vueuse/core'
6
6
  import { computed, type MaybeRefOrGetter } from 'vue'
7
7
 
8
+ export type PaginationBindings = {
9
+ size: TablePaginationSize
10
+ showBy: number
11
+ 'onUpdate:showBy': (value: number) => void
12
+ from: number
13
+ to: number
14
+ total: number
15
+ isFirstPage: boolean
16
+ isLastPage: boolean
17
+ onFirst: () => void
18
+ onLast: () => void
19
+ onNext: () => void
20
+ onPrevious: () => void
21
+ }
22
+
8
23
  export function usePagination<T>(id: string, _records: MaybeRefOrGetter<T[]>) {
9
24
  const records = toComputed(_records)
10
25
 
@@ -65,7 +80,7 @@ export function usePagination<T>(id: string, _records: MaybeRefOrGetter<T[]>) {
65
80
 
66
81
  const uiStore = useUiStore()
67
82
 
68
- const paginationBindings = computed(() => ({
83
+ const paginationBindings = computed<PaginationBindings>(() => ({
69
84
  size: (uiStore.isMobile ? 'small' : 'medium') as TablePaginationSize,
70
85
  showBy: showBy.value,
71
86
  'onUpdate:showBy': (value: number) => (showBy.value = value),
@@ -1,66 +1,13 @@
1
- import type { MaybeRef } from '@vueuse/core'
2
- import { computed, unref } from 'vue'
1
+ import { formatTimeAgoIntl } from '@vueuse/core'
2
+ import { computed, toValue, type MaybeRefOrGetter } from 'vue'
3
3
  import { useI18n } from 'vue-i18n'
4
4
 
5
- export default function useRelativeTime(fromDate: MaybeRef<Date>, toDate: MaybeRef<Date>) {
6
- const { t } = useI18n()
7
- const fromTime = computed(() => unref(fromDate).getTime())
8
- const toTime = computed(() => unref(toDate).getTime())
9
- const isPast = computed(() => toTime.value > fromTime.value)
10
- const diff = computed(() => Math.abs(toTime.value - fromTime.value))
11
-
12
- return computed(() => {
13
- if (diff.value < 10000) {
14
- return t('relative-time.now')
15
- }
16
-
17
- const years = Math.floor(diff.value / 31556952000)
18
- let timeLeft = diff.value % 31556952000
19
-
20
- const months = Math.floor(timeLeft / 2629746000)
21
- timeLeft = timeLeft % 2629746000
22
-
23
- const days = Math.floor(timeLeft / 86400000)
24
- timeLeft = timeLeft % 86400000
25
-
26
- const hours = Math.floor(timeLeft / 3600000)
27
- timeLeft = timeLeft % 3600000
28
-
29
- const minutes = Math.floor(timeLeft / 60000)
30
- timeLeft = timeLeft % 60000
31
- const seconds = Math.floor(timeLeft / 1000)
32
-
33
- const parts = []
34
-
35
- if (years > 0) {
36
- parts.push(t('relative-time.year', { n: years }))
37
- }
38
-
39
- if (months > 0) {
40
- const n = days >= 15 ? months + 1 : months
41
- parts.push(t('relative-time.month', { n }))
42
- }
43
-
44
- if (years === 0 && months === 0) {
45
- if (days > 0) {
46
- parts.push(t('relative-time.day', { n: days }))
47
- }
48
-
49
- if (days <= 1 && hours > 0) {
50
- parts.push(t('relative-time.hour', { n: hours }))
51
- }
52
-
53
- if (days === 0 && minutes > 0) {
54
- parts.push(t('relative-time.minute', { n: minutes }))
55
- }
5
+ export function getRelativeTime(date: MaybeRefOrGetter<Date>, locale: MaybeRefOrGetter<string>) {
6
+ return formatTimeAgoIntl(toValue(date), { locale: toValue(locale) })
7
+ }
56
8
 
57
- if (minutes === 0 && seconds > 0) {
58
- parts.push(t('relative-time.second', { n: seconds }))
59
- }
60
- }
9
+ export default function useRelativeTime(date: MaybeRefOrGetter<Date>) {
10
+ const { locale } = useI18n()
61
11
 
62
- return t(isPast.value ? 'relative-time.past' : 'relative-time.future', {
63
- str: parts.join(' '),
64
- })
65
- })
12
+ return computed(() => getRelativeTime(date, locale))
66
13
  }
@@ -0,0 +1,56 @@
1
+ import type { StateHeroType } from '@core/components/state-hero/VtsStateHero.vue'
2
+ import type { TableState } from '@core/components/table/VtsTable.vue'
3
+ import { computed, toValue, type MaybeRefOrGetter } from 'vue'
4
+ import { useI18n } from 'vue-i18n'
5
+
6
+ type TableStateInput = undefined | boolean | string | TableState
7
+
8
+ export function useTableState(config: {
9
+ busy?: MaybeRefOrGetter<TableStateInput>
10
+ error?: MaybeRefOrGetter<TableStateInput>
11
+ empty?: MaybeRefOrGetter<TableStateInput>
12
+ }) {
13
+ const { t } = useI18n()
14
+
15
+ const DEFAULT_MESSAGES: { [K in StateHeroType]?: string } = {
16
+ 'no-data': t('no-data'),
17
+ 'no-result': t('no-result'),
18
+ error: t('error-no-data'),
19
+ }
20
+
21
+ function handleStateInput(
22
+ defaultType: StateHeroType,
23
+ inputRaw: MaybeRefOrGetter<TableStateInput>
24
+ ): TableState | undefined {
25
+ const input = toValue(inputRaw)
26
+
27
+ if (input === undefined) {
28
+ return undefined
29
+ }
30
+
31
+ if (typeof input === 'object') {
32
+ return { message: DEFAULT_MESSAGES[input.type], ...input }
33
+ }
34
+
35
+ if (typeof input === 'string') {
36
+ return { type: defaultType, message: input }
37
+ }
38
+
39
+ if (input) {
40
+ return {
41
+ type: defaultType,
42
+ message: DEFAULT_MESSAGES[defaultType],
43
+ }
44
+ }
45
+
46
+ return undefined
47
+ }
48
+
49
+ return computed(() => {
50
+ return (
51
+ handleStateInput('busy', config.busy) ??
52
+ handleStateInput('error', config.error) ??
53
+ handleStateInput('no-data', config.empty)
54
+ )
55
+ })
56
+ }
@@ -1,4 +1,4 @@
1
- import type { TreeNodeBase } from '@core/composables/tree/tree-node-base'
1
+ import type { TreeNodeBase } from '@core/packages/tree/tree-node-base'
2
2
  import { refDebounced } from '@vueuse/shared'
3
3
  import { computed, ref } from 'vue'
4
4
 
@@ -12,5 +12,5 @@ export function useTreeFilter() {
12
12
  const predicate = (node: TreeNodeBase) =>
13
13
  hasFilter.value ? node.label.toLocaleLowerCase().includes(debouncedFilter.value.toLocaleLowerCase()) : undefined
14
14
 
15
- return { filter, predicate, isSearching }
15
+ return { filter, predicate, isSearching, hasFilter }
16
16
  }
@@ -58,6 +58,7 @@ import {
58
58
  faExclamation,
59
59
  faExclamationCircle,
60
60
  faExternalLink,
61
+ faEye,
61
62
  faEyeSlash,
62
63
  faFileCsv,
63
64
  faFileExport,
@@ -166,6 +167,7 @@ export const faIcons = defineIconPack({
166
167
  exclamation: { icon: faExclamation },
167
168
  'exclamation-circle': { icon: faExclamationCircle },
168
169
  'external-link': { icon: faExternalLink },
170
+ eye: { icon: faEye },
169
171
  'eye-slash': { icon: faEyeSlash },
170
172
  file: { icon: faFile },
171
173
  'file-csv': { icon: faFileCsv },
@@ -198,7 +198,7 @@ export const objectIcons = defineIconPack({
198
198
  },
199
199
  { icon: getStatusIcon(state) },
200
200
  ]),
201
- network: defineIcon([['connected', 'disconnected']], state => [
201
+ network: defineIcon([['connected', 'partially-connected', 'disconnected']], state => [
202
202
  {
203
203
  icon: faNetworkWired,
204
204
  color: getMainColor(state),