@xen-orchestra/web-core 0.20.0 → 0.21.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 (118) hide show
  1. package/lib/assets/all-done.svg +62 -0
  2. package/lib/assets/all-good.svg +113 -0
  3. package/lib/assets/error.svg +57 -372
  4. package/lib/assets/no-data.svg +190 -65
  5. package/lib/assets/not-found.svg +446 -126
  6. package/lib/assets/offline.svg +118 -0
  7. package/lib/assets/under-construction.svg +245 -193
  8. package/lib/assets/zoom.svg +85 -0
  9. package/lib/components/backup-state/VtsBackupState.vue +20 -17
  10. package/lib/components/cell-object/VtsCellObject.vue +4 -1
  11. package/lib/components/console/VtsActionsConsole.vue +7 -4
  12. package/lib/components/console/VtsClipboardConsole.vue +9 -6
  13. package/lib/components/copy-button/VtsCopyButton.vue +7 -14
  14. package/lib/components/dropdown/DropdownTitle.vue +5 -2
  15. package/lib/components/icon/NewVtsIcon.vue +49 -0
  16. package/lib/components/input-group/VtsInputGroup.vue +41 -0
  17. package/lib/components/input-wrapper/VtsInputWrapper.vue +2 -2
  18. package/lib/components/layout/VtsLayoutSidebar.vue +6 -3
  19. package/lib/components/linear-chart/VtsLinearChart.vue +4 -0
  20. package/lib/components/object-icon/VtsObjectIcon.vue +22 -0
  21. package/lib/components/quick-info-card/VtsQuickInfoCard.vue +4 -1
  22. package/lib/components/select/VtsOption.vue +10 -6
  23. package/lib/components/select/VtsSelect.vue +74 -50
  24. package/lib/components/state-hero/VtsAllDoneHero.vue +16 -0
  25. package/lib/components/state-hero/VtsAllGoodHero.vue +16 -0
  26. package/lib/components/state-hero/VtsComingSoonHero.vue +4 -1
  27. package/lib/components/state-hero/VtsErrorNoDataHero.vue +4 -1
  28. package/lib/components/state-hero/VtsLoadingHero.vue +4 -1
  29. package/lib/components/state-hero/VtsNoDataHero.vue +4 -1
  30. package/lib/components/state-hero/VtsNoSelectionHero.vue +4 -1
  31. package/lib/components/state-hero/VtsObjectNotFoundHero.vue +4 -1
  32. package/lib/components/state-hero/VtsOfflineHero.vue +16 -0
  33. package/lib/components/state-hero/VtsPageNotFoundHero.vue +4 -1
  34. package/lib/components/state-hero/VtsStateHero.vue +10 -1
  35. package/lib/components/table/ColumnTitle.vue +2 -2
  36. package/lib/components/task/VtsQuickTaskButton.vue +4 -1
  37. package/lib/components/task/VtsQuickTaskList.vue +5 -2
  38. package/lib/components/task/VtsQuickTaskTabBar.vue +8 -5
  39. package/lib/components/ui/card-numbers/UiCardNumbers.vue +15 -33
  40. package/lib/components/ui/character-limit/UiCharacterLimit.vue +4 -1
  41. package/lib/components/ui/input/UiInput.vue +2 -2
  42. package/lib/components/ui/label/UiLabel.vue +4 -1
  43. package/lib/components/ui/progress-bar/UiProgressBar.vue +5 -2
  44. package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +9 -6
  45. package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +6 -3
  46. package/lib/components/ui/stacked-bar/StackedBarSegment.vue +4 -1
  47. package/lib/components/ui/table-pagination/UiTablePagination.vue +6 -3
  48. package/lib/components/ui/text-area/UiTextarea.vue +4 -1
  49. package/lib/components/ui/top-bottom-table/UiTopBottomTable.vue +6 -3
  50. package/lib/components/ui/tree-item-label/UiTreeItemLabel.vue +4 -1
  51. package/lib/composables/local-time-ago.composable.ts +53 -0
  52. package/lib/composables/locale-time-ago.composable.ts +53 -0
  53. package/lib/icons/fa-icons.ts +164 -0
  54. package/lib/icons/index.ts +15 -0
  55. package/lib/icons/legacy-icons.ts +80 -0
  56. package/lib/icons/object-icons.ts +187 -0
  57. package/lib/layouts/CoreLayout.vue +7 -3
  58. package/lib/locales/cs.json +73 -7
  59. package/lib/locales/de.json +5 -1
  60. package/lib/locales/en.json +33 -3
  61. package/lib/locales/es.json +9 -5
  62. package/lib/locales/fr.json +32 -2
  63. package/lib/locales/it.json +1 -1
  64. package/lib/locales/nl.json +51 -9
  65. package/lib/locales/ru.json +28 -1
  66. package/lib/locales/sv.json +77 -13
  67. package/lib/packages/collection/README.md +23 -18
  68. package/lib/packages/collection/create-collection.ts +22 -21
  69. package/lib/packages/collection/create-item.ts +21 -20
  70. package/lib/packages/collection/create-use-subset.ts +23 -0
  71. package/lib/packages/collection/guess-item-id.ts +26 -16
  72. package/lib/packages/collection/index.ts +4 -0
  73. package/lib/packages/collection/types.ts +65 -37
  74. package/lib/packages/collection/use-collection.ts +68 -18
  75. package/lib/packages/collection/use-flag-registry.ts +38 -17
  76. package/lib/packages/form-select/guess-label.ts +45 -0
  77. package/lib/packages/form-select/guess-value.ts +23 -0
  78. package/lib/packages/form-select/index.ts +6 -0
  79. package/lib/packages/form-select/normalize-search-term.ts +11 -0
  80. package/lib/packages/form-select/types.ts +90 -42
  81. package/lib/packages/form-select/use-form-option-controller.ts +7 -3
  82. package/lib/packages/form-select/use-form-select-controller.ts +38 -27
  83. package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +1 -1
  84. package/lib/packages/form-select/use-form-select.ts +308 -130
  85. package/lib/packages/icon/DisplayIcon.vue +25 -0
  86. package/lib/packages/icon/DisplayIconAny.vue +16 -0
  87. package/lib/packages/icon/DisplayIconSingle.vue +35 -0
  88. package/lib/packages/icon/DisplayIconStack.vue +34 -0
  89. package/lib/packages/icon/README.md +286 -0
  90. package/lib/packages/icon/create-icon-bindings.ts +27 -0
  91. package/lib/packages/icon/define-icon-pack.ts +23 -0
  92. package/lib/packages/icon/define-icon-single.ts +17 -0
  93. package/lib/packages/icon/define-icon-stack.ts +20 -0
  94. package/lib/packages/icon/define-icon.ts +40 -0
  95. package/lib/packages/icon/generate-icon-variants.ts +17 -0
  96. package/lib/packages/icon/index.ts +8 -0
  97. package/lib/packages/icon/is-icon-stack.ts +5 -0
  98. package/lib/packages/icon/merge-icons.ts +25 -0
  99. package/lib/packages/icon/merge-transforms.ts +12 -0
  100. package/lib/packages/icon/normalize-icon.ts +25 -0
  101. package/lib/packages/icon/to-tuple.ts +7 -0
  102. package/lib/packages/icon/types.ts +72 -0
  103. package/lib/packages/job/README.md +2 -2
  104. package/lib/packages/mapper/README.md +166 -0
  105. package/lib/packages/mapper/convert-to-map.ts +5 -0
  106. package/lib/packages/mapper/create-mapper.ts +30 -0
  107. package/lib/packages/mapper/index.ts +4 -0
  108. package/lib/packages/mapper/types.ts +1 -0
  109. package/lib/packages/mapper/use-mapper.ts +31 -0
  110. package/lib/stores/sidebar.store.ts +1 -1
  111. package/lib/types/chart.ts +2 -2
  112. package/lib/types/utility.type.ts +9 -0
  113. package/lib/utils/object.util.ts +16 -0
  114. package/lib/utils/size.util.ts +4 -2
  115. package/package.json +2 -1
  116. package/lib/components/backup-item/VtsBackupItem.vue +0 -47
  117. package/lib/composables/mapper.composable.md +0 -74
  118. package/lib/composables/mapper.composable.ts +0 -18
@@ -5,8 +5,8 @@
5
5
  <div class="fill" :style="{ width: `${fillWidth}%` }" />
6
6
  </div>
7
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>
8
+ <span>{{ n(0, 'percent') }}</span>
9
+ <span v-for="step in steps" :key="step">{{ n(step, 'percent') }}</span>
10
10
  </div>
11
11
  <VtsLegendList class="legend">
12
12
  <UiLegend :accent :value="Math.round(percentage)" unit="%">{{ legend }}</UiLegend>
@@ -20,6 +20,7 @@ import UiLegend from '@core/components/ui/legend/UiLegend.vue'
20
20
  import { toVariants } from '@core/utils/to-variants.util'
21
21
  import { useClamp, useMax } from '@vueuse/math'
22
22
  import { computed } from 'vue'
23
+ import { useI18n } from 'vue-i18n'
23
24
 
24
25
  const {
25
26
  value: _value,
@@ -32,6 +33,8 @@ const {
32
33
  showSteps?: boolean
33
34
  }>()
34
35
 
36
+ const { n } = useI18n()
37
+
35
38
  const value = useMax(0, () => _value)
36
39
 
37
40
  const percentage = computed(() => (max <= 0 ? 0 : (value.value / max) * 100))
@@ -2,29 +2,29 @@
2
2
  <template>
3
3
  <form class="ui-query-search-bar" @submit.prevent="emit('search', value)">
4
4
  <label v-if="uiStore.isDesktop" :for="id" class="typo-body-regular-small label">
5
- {{ $t('core.query-search-bar.label') }}
5
+ {{ t('core.query-search-bar.label') }}
6
6
  </label>
7
7
  <UiInput
8
8
  :id
9
9
  v-model="value"
10
10
  type="text"
11
11
  accent="brand"
12
- :aria-label="uiStore.isMobile ? $t('core.query-search-bar.label') : undefined"
12
+ :aria-label="uiStore.isMobile ? t('core.query-search-bar.label') : undefined"
13
13
  :icon="uiStore.isDesktop ? faMagnifyingGlass : undefined"
14
- :placeholder="$t('core.query-search-bar.placeholder')"
14
+ :placeholder="t('core.query-search-bar.placeholder')"
15
15
  />
16
16
  <template v-if="uiStore.isDesktop">
17
- <UiButton size="medium" accent="brand" variant="primary" type="submit">{{ $t('core.search') }}</UiButton>
17
+ <UiButton size="medium" accent="brand" variant="primary" type="submit">{{ t('core.search') }}</UiButton>
18
18
  <VtsDivider type="stretch" />
19
19
  <UiButton
20
- v-tooltip="$t('coming-soon')"
20
+ v-tooltip="t('coming-soon')"
21
21
  size="medium"
22
22
  accent="brand"
23
23
  variant="secondary"
24
24
  :left-icon="faFilter"
25
25
  disabled
26
26
  >
27
- {{ $t('core.query-search-bar.use-query-builder') }}
27
+ {{ t('core.query-search-bar.use-query-builder') }}
28
28
  </UiButton>
29
29
  </template>
30
30
  <template v-else>
@@ -44,11 +44,14 @@ import { useUiStore } from '@core/stores/ui.store'
44
44
  import { uniqueId } from '@core/utils/unique-id.util'
45
45
  import { faFilter, faMagnifyingGlass } from '@fortawesome/free-solid-svg-icons'
46
46
  import { ref } from 'vue'
47
+ import { useI18n } from 'vue-i18n'
47
48
 
48
49
  const emit = defineEmits<{
49
50
  search: [value: string]
50
51
  }>()
51
52
 
53
+ const { t } = useI18n()
54
+
52
55
  const id = uniqueId('search-input-')
53
56
 
54
57
  const uiStore = useUiStore()
@@ -13,14 +13,14 @@
13
13
  <UiTag v-if="task.tag" accent="neutral" variant="primary">{{ task.tag }}</UiTag>
14
14
  <div v-if="hasSubTasks" class="subtasks">
15
15
  <VtsIcon :icon="faCircleNotch" accent="current" />
16
- <span class="typo-body-regular-small">{{ $t('tasks.n-subtasks', { n: subTasksCount }) }}</span>
16
+ <span class="typo-body-regular-small">{{ t('tasks.n-subtasks', { n: subTasksCount }) }}</span>
17
17
  </div>
18
18
  </div>
19
19
  <div v-if="task.start" class="line-2 typo-body-regular-small">
20
- {{ $d(task.start, 'datetime_short') }}
20
+ {{ d(task.start, 'datetime_short') }}
21
21
  <template v-if="task.end">
22
22
  <VtsIcon :icon="faArrowRight" accent="current" />
23
- {{ $d(new Date(task.end), 'datetime_short') }}
23
+ {{ d(new Date(task.end), 'datetime_short') }}
24
24
  </template>
25
25
  </div>
26
26
  </div>
@@ -37,6 +37,7 @@ import UiTag from '@core/components/ui/tag/UiTag.vue'
37
37
  import { faAngleDown, faAngleRight, faArrowRight, faCircleNotch } from '@fortawesome/free-solid-svg-icons'
38
38
  import { useToggle } from '@vueuse/core'
39
39
  import { computed } from 'vue'
40
+ import { useI18n } from 'vue-i18n'
40
41
 
41
42
  export type TaskStatus = 'pending' | 'success' | 'failure'
42
43
 
@@ -54,6 +55,8 @@ const props = defineProps<{
54
55
  task: Task
55
56
  }>()
56
57
 
58
+ const { t, d } = useI18n()
59
+
57
60
  const [isExpanded, toggleExpand] = useToggle()
58
61
 
59
62
  const subTasks = computed(() => props.task.subtasks ?? [])
@@ -7,7 +7,7 @@
7
7
  class="stacked-bar-segment typo-caption-small"
8
8
  >
9
9
  <div ref="ellipsisElement" :class="{ hidden }" class="text-ellipsis">
10
- {{ $n(percentage / 100, 'percent') }}
10
+ {{ n(percentage / 100, 'percent') }}
11
11
  </div>
12
12
  </div>
13
13
  </template>
@@ -17,6 +17,7 @@ import { vTooltip } from '@core/directives/tooltip.directive'
17
17
  import { hasEllipsis } from '@core/utils/has-ellipsis.util'
18
18
  import { useResizeObserver } from '@vueuse/core'
19
19
  import { ref } from 'vue'
20
+ import { useI18n } from 'vue-i18n'
20
21
 
21
22
  export type StackedBarSegmentAccent = 'info' | 'success' | 'warning' | 'danger'
22
23
 
@@ -27,6 +28,8 @@ export type StackedBarSegmentProps = {
27
28
 
28
29
  defineProps<StackedBarSegmentProps>()
29
30
 
31
+ const { n } = useI18n()
32
+
30
33
  const hidden = ref(false)
31
34
  const ellipsisElement = ref<HTMLElement | null>(null)
32
35
 
@@ -8,13 +8,13 @@
8
8
  <PaginationButton :disabled="isLastPage" :icon="faAngleDoubleRight" @click="emit('last')" />
9
9
  </div>
10
10
  <span class="typo-body-regular-small label">
11
- {{ $t('core.select.n-object-of', { from, to, total }) }}
11
+ {{ t('core.select.n-object-of', { from, to, total }) }}
12
12
  </span>
13
- <span class="typo-body-regular-small label show">{{ $t('core.pagination.show-by') }}</span>
13
+ <span class="typo-body-regular-small label show">{{ t('core.pagination.show-by') }}</span>
14
14
  <div class="dropdown-wrapper">
15
15
  <select v-model="showBy" class="dropdown typo-body-regular-small">
16
16
  <option v-for="option in [50, 100, 150, 200, -1]" :key="option" :value="option" class="typo-body-bold-small">
17
- {{ option === -1 ? $t('core.pagination.all') : option }}
17
+ {{ option === -1 ? t('core.pagination.all') : option }}
18
18
  </option>
19
19
  </select>
20
20
  <VtsIcon :icon="faAngleDown" accent="current" class="icon" />
@@ -32,6 +32,7 @@ import {
32
32
  faAngleLeft,
33
33
  faAngleRight,
34
34
  } from '@fortawesome/free-solid-svg-icons'
35
+ import { useI18n } from 'vue-i18n'
35
36
 
36
37
  defineProps<{
37
38
  from: number
@@ -49,6 +50,8 @@ const emit = defineEmits<{
49
50
  }>()
50
51
 
51
52
  const showBy = defineModel<number>('showBy', { default: 50 })
53
+
54
+ const { t } = useI18n()
52
55
  </script>
53
56
 
54
57
  <style lang="postcss" scoped>
@@ -7,7 +7,7 @@
7
7
  <textarea v-bind="attrs" :id ref="textarea" v-model="model" :disabled class="textarea" />
8
8
  <UiCharacterLimit v-if="maxCharacters" :count="model.trim().length" :max="maxCharacters" />
9
9
  <UiInfo v-if="isExceedingMaxCharacters" accent="danger">
10
- {{ $t('core.textarea.exceeds-max-characters', { max: maxCharacters }) }}
10
+ {{ t('core.textarea.exceeds-max-characters', { max: maxCharacters }) }}
11
11
  </UiInfo>
12
12
  <UiInfo v-if="slots.info" :accent="accent === 'brand' ? 'info' : accent">
13
13
  <slot name="info" />
@@ -23,6 +23,7 @@ import { toVariants } from '@core/utils/to-variants.util'
23
23
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
24
24
  import { useFocus } from '@vueuse/core'
25
25
  import { computed, useAttrs, useId, useTemplateRef } from 'vue'
26
+ import { useI18n } from 'vue-i18n'
26
27
 
27
28
  defineOptions({
28
29
  inheritAttrs: false,
@@ -49,6 +50,8 @@ const slots = defineSlots<{
49
50
  info?(): any
50
51
  }>()
51
52
 
53
+ const { t } = useI18n()
54
+
52
55
  const attrs = useAttrs()
53
56
 
54
57
  const textAreaElement = useTemplateRef('textarea')
@@ -3,7 +3,7 @@
3
3
  <div class="ui-top-bottom-table">
4
4
  <div class="content">
5
5
  <span class="typo-body-regular-small label">
6
- {{ $t('core.select.n-selected-of', { count: selectedItems, total: totalItems }) }}
6
+ {{ t('core.select.n-selected-of', { count: selectedItems, total: totalItems }) }}
7
7
  </span>
8
8
 
9
9
  <UiButton
@@ -13,7 +13,7 @@
13
13
  variant="tertiary"
14
14
  @click="emit('toggleSelectAll', true)"
15
15
  >
16
- {{ $t('core.select.all') }}
16
+ {{ t('core.select.all') }}
17
17
  </UiButton>
18
18
  <UiButton
19
19
  :disabled="selectedItems === 0"
@@ -22,7 +22,7 @@
22
22
  variant="tertiary"
23
23
  @click="emit('toggleSelectAll', false)"
24
24
  >
25
- {{ $t('core.select.unselect') }}
25
+ {{ t('core.select.unselect') }}
26
26
  </UiButton>
27
27
  </div>
28
28
  <slot />
@@ -31,6 +31,7 @@
31
31
 
32
32
  <script setup lang="ts">
33
33
  import UiButton from '@core/components/ui/button/UiButton.vue'
34
+ import { useI18n } from 'vue-i18n'
34
35
 
35
36
  defineProps<{
36
37
  selectedItems: number
@@ -44,6 +45,8 @@ const emit = defineEmits<{
44
45
  defineSlots<{
45
46
  default(): any
46
47
  }>()
48
+
49
+ const { t } = useI18n()
47
50
  </script>
48
51
 
49
52
  <style scoped lang="postcss">
@@ -16,7 +16,7 @@
16
16
  </template>
17
17
  <UiButtonIcon
18
18
  v-if="hasToggle"
19
- v-tooltip="isExpanded ? $t('core.close') : $t('core.open')"
19
+ v-tooltip="isExpanded ? t('core.close') : t('core.open')"
20
20
  class="toggle"
21
21
  accent="brand"
22
22
  :icon="isExpanded ? faAngleDown : faAngleRight"
@@ -47,6 +47,7 @@ import { IK_TREE_ITEM_EXPANDED, IK_TREE_ITEM_HAS_CHILDREN, IK_TREE_LIST_DEPTH }
47
47
  import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
48
48
  import { faAngleDown, faAngleRight } from '@fortawesome/free-solid-svg-icons'
49
49
  import { inject, ref, useAttrs } from 'vue'
50
+ import { useI18n } from 'vue-i18n'
50
51
  import type { RouteLocationRaw } from 'vue-router'
51
52
 
52
53
  defineOptions({
@@ -69,6 +70,8 @@ defineSlots<{
69
70
  addons?(): any
70
71
  }>()
71
72
 
73
+ const { t } = useI18n()
74
+
72
75
  const attrs = useAttrs()
73
76
 
74
77
  const hasToggle = inject(IK_TREE_ITEM_HAS_CHILDREN, ref(false))
@@ -0,0 +1,53 @@
1
+ import { useTimestamp } from '@vueuse/core'
2
+ import { computed, type MaybeRefOrGetter, toValue } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+
5
+ export enum SECONDS {
6
+ MINUTE = 60,
7
+ HOUR = SECONDS.MINUTE * 60,
8
+ DAY = SECONDS.HOUR * 24,
9
+ WEEK = SECONDS.DAY * 7,
10
+ MONTH = SECONDS.DAY * 30,
11
+ YEAR = SECONDS.DAY * 365,
12
+ }
13
+
14
+ export type TimeThreshold = [number, Intl.RelativeTimeFormatUnit]
15
+
16
+ const timeThresholds: TimeThreshold[] = [
17
+ [SECONDS.MINUTE, 'second'],
18
+ [SECONDS.HOUR, 'minute'],
19
+ [SECONDS.DAY, 'hour'],
20
+ [SECONDS.WEEK, 'day'],
21
+ [SECONDS.MONTH, 'week'],
22
+ [SECONDS.YEAR, 'month'],
23
+ [Infinity, 'year'],
24
+ ]
25
+
26
+ export function useTimeAgo(referenceDate: MaybeRefOrGetter<Date | number | string>) {
27
+ const { locale } = useI18n()
28
+
29
+ const formatter = computed(() => new Intl.RelativeTimeFormat(locale.value, { numeric: 'auto' }))
30
+
31
+ const now = useTimestamp({ interval: 1000 })
32
+
33
+ const referenceTime = computed(() => new Date(toValue(referenceDate)).getTime())
34
+
35
+ const distance = computed(() => {
36
+ const diff = Math.trunc((referenceTime.value - now.value) / 1000)
37
+
38
+ if (Math.abs(diff) < 60) {
39
+ return Math.trunc(diff / 10) * 10 || 0 // Avoid recomputing when the value changes from 0 to -0
40
+ }
41
+
42
+ return timeThresholds.reduce(
43
+ (acc, [threshold]) => (Math.abs(acc) < threshold ? acc : Math.trunc(acc / threshold) * threshold),
44
+ diff
45
+ )
46
+ })
47
+
48
+ return computed(() => {
49
+ const index = timeThresholds.findIndex(([threshold]) => Math.abs(distance.value) < threshold)
50
+ const divisor = index > 0 ? timeThresholds[index - 1][0] : 1
51
+ return formatter.value.format(Math.trunc(distance.value / divisor), timeThresholds[index][1])
52
+ })
53
+ }
@@ -0,0 +1,53 @@
1
+ import { useTimestamp } from '@vueuse/core'
2
+ import { computed, type MaybeRefOrGetter, toValue } from 'vue'
3
+ import { useI18n } from 'vue-i18n'
4
+
5
+ export enum SECONDS {
6
+ MINUTE = 60,
7
+ HOUR = SECONDS.MINUTE * 60,
8
+ DAY = SECONDS.HOUR * 24,
9
+ WEEK = SECONDS.DAY * 7,
10
+ MONTH = SECONDS.DAY * 30,
11
+ YEAR = SECONDS.DAY * 365,
12
+ }
13
+
14
+ export type TimeThreshold = [number, Intl.RelativeTimeFormatUnit]
15
+
16
+ const timeThresholds: TimeThreshold[] = [
17
+ [SECONDS.MINUTE, 'second'],
18
+ [SECONDS.HOUR, 'minute'],
19
+ [SECONDS.DAY, 'hour'],
20
+ [SECONDS.WEEK, 'day'],
21
+ [SECONDS.MONTH, 'week'],
22
+ [SECONDS.YEAR, 'month'],
23
+ [Infinity, 'year'],
24
+ ]
25
+
26
+ export function useTimeAgo(referenceDate: MaybeRefOrGetter<Date | number | string>) {
27
+ const { locale } = useI18n()
28
+
29
+ const formatter = computed(() => new Intl.RelativeTimeFormat(locale.value, { numeric: 'auto' }))
30
+
31
+ const now = useTimestamp({ interval: 1000 })
32
+
33
+ const referenceTime = computed(() => new Date(toValue(referenceDate)).getTime())
34
+
35
+ const distance = computed(() => {
36
+ const diff = Math.trunc((referenceTime.value - now.value) / 1000)
37
+
38
+ if (Math.abs(diff) < 60) {
39
+ return Math.trunc(diff / 10) * 10 || 0 // Avoid recomputing when the value changes from 0 to -0
40
+ }
41
+
42
+ return timeThresholds.reduce(
43
+ (acc, [threshold]) => (Math.abs(acc) < threshold ? acc : Math.trunc(acc / threshold) * threshold),
44
+ diff
45
+ )
46
+ })
47
+
48
+ return computed(() => {
49
+ const index = timeThresholds.findIndex(([threshold]) => Math.abs(distance.value) < threshold)
50
+ const divisor = index > 0 ? timeThresholds[index - 1][0] : 1
51
+ return formatter.value.format(Math.trunc(distance.value / divisor), timeThresholds[index][1])
52
+ })
53
+ }
@@ -0,0 +1,164 @@
1
+ import { defineIconPack } from '@core/packages/icon/define-icon-pack.ts'
2
+ import { faBuilding, faFile, faFolderClosed, faFolderOpen, faSquareCheck } from '@fortawesome/free-regular-svg-icons'
3
+ import {
4
+ faAlignLeft,
5
+ faAngleDoubleLeft,
6
+ faAngleDoubleRight,
7
+ faAngleDown,
8
+ faAngleLeft,
9
+ faAngleRight,
10
+ faAngleUp,
11
+ faArrowDown,
12
+ faArrowRight,
13
+ faArrowRightFromBracket,
14
+ faArrowUp,
15
+ faArrowUpRightFromSquare,
16
+ faAt,
17
+ faBars,
18
+ faBarsProgress,
19
+ faBook,
20
+ faCamera,
21
+ faCaretDown,
22
+ faCaretUp,
23
+ faCheck,
24
+ faCircle,
25
+ faCircleCheck,
26
+ faCircleMinus,
27
+ faCircleNotch,
28
+ faCirclePlay,
29
+ faCircleXmark,
30
+ faCity,
31
+ faClose,
32
+ faCode,
33
+ faComments,
34
+ faCopy,
35
+ faDatabase,
36
+ faDesktop,
37
+ faDisplay,
38
+ faDownLeftAndUpRightToCenter,
39
+ faDownload,
40
+ faEarthAmericas,
41
+ faEdit,
42
+ faEllipsis,
43
+ faEllipsisVertical,
44
+ faExternalLink,
45
+ faEyeSlash,
46
+ faFileCsv,
47
+ faFileExport,
48
+ faFilter,
49
+ faFont,
50
+ faGear,
51
+ faHashtag,
52
+ faHeadset,
53
+ faKeyboard,
54
+ faLayerGroup,
55
+ faList,
56
+ faLock,
57
+ faLockOpen,
58
+ faMagnifyingGlass,
59
+ faMemory,
60
+ faMicrochip,
61
+ faMinus,
62
+ faMoon,
63
+ faNetworkWired,
64
+ faPause,
65
+ faPencil,
66
+ faPlay,
67
+ faPlug,
68
+ faPlus,
69
+ faPowerOff,
70
+ faRemove,
71
+ faRepeat,
72
+ faRotateLeft,
73
+ faRoute,
74
+ faSatellite,
75
+ faServer,
76
+ faSliders,
77
+ faTrash,
78
+ faUpRightAndDownLeftFromCenter,
79
+ faXmark,
80
+ } from '@fortawesome/free-solid-svg-icons'
81
+
82
+ export const faIcons = defineIconPack({
83
+ 'align-left': { icon: faAlignLeft },
84
+ 'angle-double-left': { icon: faAngleDoubleLeft },
85
+ 'angle-double-right': { icon: faAngleDoubleRight },
86
+ 'angle-down': { icon: faAngleDown },
87
+ 'angle-left': { icon: faAngleLeft },
88
+ 'angle-right': { icon: faAngleRight },
89
+ 'angle-up': { icon: faAngleUp },
90
+ 'arrow-down': { icon: faArrowDown },
91
+ 'arrow-right': { icon: faArrowRight },
92
+ 'arrow-right-from-bracket': { icon: faArrowRightFromBracket },
93
+ 'arrow-up': { icon: faArrowUp },
94
+ 'arrow-up-right-from-square': { icon: faArrowUpRightFromSquare },
95
+ at: { icon: faAt },
96
+ bars: { icon: faBars },
97
+ 'bars-progress': { icon: faBarsProgress },
98
+ book: { icon: faBook },
99
+ building: { icon: faBuilding },
100
+ camera: { icon: faCamera },
101
+ 'caret-down': { icon: faCaretDown },
102
+ 'caret-up': { icon: faCaretUp },
103
+ check: { icon: faCheck },
104
+ circle: { icon: faCircle },
105
+ 'circle-check': { icon: faCircleCheck },
106
+ 'circle-minus': { icon: faCircleMinus },
107
+ 'circle-notch': { icon: faCircleNotch },
108
+ 'circle-play': { icon: faCirclePlay },
109
+ 'circle-xmark': { icon: faCircleXmark },
110
+ city: { icon: faCity },
111
+ close: { icon: faClose },
112
+ code: { icon: faCode },
113
+ comments: { icon: faComments },
114
+ copy: { icon: faCopy },
115
+ database: { icon: faDatabase },
116
+ desktop: { icon: faDesktop },
117
+ display: { icon: faDisplay },
118
+ 'down-left-and-up-right-to-center': { icon: faDownLeftAndUpRightToCenter },
119
+ download: { icon: faDownload },
120
+ 'earth-americas': { icon: faEarthAmericas },
121
+ edit: { icon: faEdit },
122
+ ellipsis: { icon: faEllipsis },
123
+ 'ellipsis-vertical': { icon: faEllipsisVertical },
124
+ 'external-link': { icon: faExternalLink },
125
+ 'eye-slash': { icon: faEyeSlash },
126
+ file: { icon: faFile },
127
+ 'file-csv': { icon: faFileCsv },
128
+ 'file-export': { icon: faFileExport },
129
+ filter: { icon: faFilter },
130
+ 'folder-closed': { icon: faFolderClosed },
131
+ 'folder-open': { icon: faFolderOpen },
132
+ font: { icon: faFont },
133
+ gear: { icon: faGear },
134
+ hashtag: { icon: faHashtag },
135
+ headset: { icon: faHeadset },
136
+ keyboard: { icon: faKeyboard },
137
+ 'layer-group': { icon: faLayerGroup },
138
+ list: { icon: faList },
139
+ lock: { icon: faLock },
140
+ 'lock-open': { icon: faLockOpen },
141
+ 'magnifying-glass': { icon: faMagnifyingGlass },
142
+ memory: { icon: faMemory },
143
+ microchip: { icon: faMicrochip },
144
+ minus: { icon: faMinus },
145
+ moon: { icon: faMoon },
146
+ 'network-wired': { icon: faNetworkWired },
147
+ pause: { icon: faPause },
148
+ pencil: { icon: faPencil },
149
+ play: { icon: faPlay },
150
+ plug: { icon: faPlug },
151
+ plus: { icon: faPlus },
152
+ 'power-off': { icon: faPowerOff },
153
+ remove: { icon: faRemove },
154
+ repeat: { icon: faRepeat },
155
+ 'rotate-left': { icon: faRotateLeft },
156
+ route: { icon: faRoute },
157
+ satellite: { icon: faSatellite },
158
+ server: { icon: faServer },
159
+ sliders: { icon: faSliders },
160
+ 'square-check': { icon: faSquareCheck },
161
+ trash: { icon: faTrash },
162
+ 'up-right-and-down-left-from-center': { icon: faUpRightAndDownLeftFromCenter },
163
+ xmark: { icon: faXmark },
164
+ })
@@ -0,0 +1,15 @@
1
+ import { faIcons } from '@core/icons/fa-icons.ts'
2
+ import { legacyIcons } from '@core/icons/legacy-icons.ts'
3
+ import { objectIcons } from '@core/icons/object-icons.ts'
4
+ import { defineIconPack } from '@core/packages/icon/define-icon-pack.ts'
5
+ import type { ICON_SYMBOL } from '@core/packages/icon/types.ts'
6
+
7
+ export const icons = defineIconPack({
8
+ fa: faIcons,
9
+ legacy: legacyIcons,
10
+ object: objectIcons,
11
+ })
12
+
13
+ export type IconName = Exclude<keyof typeof icons, typeof ICON_SYMBOL>
14
+
15
+ export type ObjectIconName = Extract<IconName, `object:${string}`>
@@ -0,0 +1,80 @@
1
+ import { defineIconPack } from '@core/packages/icon/define-icon-pack.ts'
2
+ import { defineIcon } from '@core/packages/icon/define-icon.ts'
3
+ import { createMapper } from '@core/packages/mapper/create-mapper.ts'
4
+ import {
5
+ faCheck,
6
+ faCircle,
7
+ faCircleInfo,
8
+ faExclamation,
9
+ faInfo,
10
+ faMinus,
11
+ faPlay,
12
+ faStar,
13
+ faStop,
14
+ faXmark,
15
+ } from '@fortawesome/free-solid-svg-icons'
16
+
17
+ const getStatusIcon = createMapper(
18
+ {
19
+ info: faInfo,
20
+ success: faCheck,
21
+ warning: faExclamation,
22
+ danger: faXmark,
23
+ muted: faMinus,
24
+ },
25
+ 'muted'
26
+ )
27
+
28
+ const getStatusColor = createMapper(
29
+ {
30
+ info: 'var(--color-info-txt-item)',
31
+ success: 'var(--color-success-txt-item)',
32
+ warning: 'var(--color-warning-txt-item)',
33
+ danger: 'var(--color-danger-txt-item)',
34
+ muted: 'var(--color-neutral-txt-secondary)',
35
+ },
36
+ 'muted'
37
+ )
38
+
39
+ export const legacyIcons = defineIconPack({
40
+ 'circle-progress': defineIcon([['success', 'warning', 'danger']], accent => [
41
+ {
42
+ icon: accent === 'success' ? faCheck : faExclamation,
43
+ },
44
+ ]),
45
+ halted: {
46
+ icon: faStop,
47
+ color: 'var(--color-danger-item-base)',
48
+ },
49
+ info: {
50
+ icon: faCircleInfo,
51
+ color: 'var(--color-info-item-base)',
52
+ },
53
+ 'legend-circle': { icon: faCircle, size: 8 },
54
+ primary: [
55
+ {
56
+ icon: faCircle,
57
+ color: 'var(--color-info-item-base)',
58
+ },
59
+ {
60
+ icon: faStar,
61
+ color: 'var(--color-info-txt-item)',
62
+ size: 8,
63
+ },
64
+ ],
65
+ running: {
66
+ icon: faPlay,
67
+ color: 'var(--color-success-item-base)',
68
+ },
69
+ status: defineIcon([['info', 'success', 'warning', 'danger', 'muted']], accent => [
70
+ {
71
+ icon: faCircle,
72
+ color: accent === 'muted' ? 'var(--color-neutral-background-disabled)' : `var(--color-${accent}-item-base)`,
73
+ },
74
+ {
75
+ icon: getStatusIcon(accent),
76
+ color: getStatusColor(accent),
77
+ size: 8,
78
+ },
79
+ ]),
80
+ })