@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.
- package/lib/assets/all-done.svg +62 -0
- package/lib/assets/all-good.svg +113 -0
- package/lib/assets/error.svg +57 -372
- package/lib/assets/no-data.svg +190 -65
- package/lib/assets/not-found.svg +446 -126
- package/lib/assets/offline.svg +118 -0
- package/lib/assets/under-construction.svg +245 -193
- package/lib/assets/zoom.svg +85 -0
- package/lib/components/backup-state/VtsBackupState.vue +20 -17
- package/lib/components/cell-object/VtsCellObject.vue +4 -1
- package/lib/components/console/VtsActionsConsole.vue +7 -4
- package/lib/components/console/VtsClipboardConsole.vue +9 -6
- package/lib/components/copy-button/VtsCopyButton.vue +7 -14
- package/lib/components/dropdown/DropdownTitle.vue +5 -2
- package/lib/components/icon/NewVtsIcon.vue +49 -0
- package/lib/components/input-group/VtsInputGroup.vue +41 -0
- package/lib/components/input-wrapper/VtsInputWrapper.vue +2 -2
- package/lib/components/layout/VtsLayoutSidebar.vue +6 -3
- package/lib/components/linear-chart/VtsLinearChart.vue +4 -0
- package/lib/components/object-icon/VtsObjectIcon.vue +22 -0
- package/lib/components/quick-info-card/VtsQuickInfoCard.vue +4 -1
- package/lib/components/select/VtsOption.vue +10 -6
- package/lib/components/select/VtsSelect.vue +74 -50
- package/lib/components/state-hero/VtsAllDoneHero.vue +16 -0
- package/lib/components/state-hero/VtsAllGoodHero.vue +16 -0
- package/lib/components/state-hero/VtsComingSoonHero.vue +4 -1
- package/lib/components/state-hero/VtsErrorNoDataHero.vue +4 -1
- package/lib/components/state-hero/VtsLoadingHero.vue +4 -1
- package/lib/components/state-hero/VtsNoDataHero.vue +4 -1
- package/lib/components/state-hero/VtsNoSelectionHero.vue +4 -1
- package/lib/components/state-hero/VtsObjectNotFoundHero.vue +4 -1
- package/lib/components/state-hero/VtsOfflineHero.vue +16 -0
- package/lib/components/state-hero/VtsPageNotFoundHero.vue +4 -1
- package/lib/components/state-hero/VtsStateHero.vue +10 -1
- package/lib/components/table/ColumnTitle.vue +2 -2
- package/lib/components/task/VtsQuickTaskButton.vue +4 -1
- package/lib/components/task/VtsQuickTaskList.vue +5 -2
- package/lib/components/task/VtsQuickTaskTabBar.vue +8 -5
- package/lib/components/ui/card-numbers/UiCardNumbers.vue +15 -33
- package/lib/components/ui/character-limit/UiCharacterLimit.vue +4 -1
- package/lib/components/ui/input/UiInput.vue +2 -2
- package/lib/components/ui/label/UiLabel.vue +4 -1
- package/lib/components/ui/progress-bar/UiProgressBar.vue +5 -2
- package/lib/components/ui/query-search-bar/UiQuerySearchBar.vue +9 -6
- package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +6 -3
- package/lib/components/ui/stacked-bar/StackedBarSegment.vue +4 -1
- package/lib/components/ui/table-pagination/UiTablePagination.vue +6 -3
- package/lib/components/ui/text-area/UiTextarea.vue +4 -1
- package/lib/components/ui/top-bottom-table/UiTopBottomTable.vue +6 -3
- package/lib/components/ui/tree-item-label/UiTreeItemLabel.vue +4 -1
- package/lib/composables/local-time-ago.composable.ts +53 -0
- package/lib/composables/locale-time-ago.composable.ts +53 -0
- package/lib/icons/fa-icons.ts +164 -0
- package/lib/icons/index.ts +15 -0
- package/lib/icons/legacy-icons.ts +80 -0
- package/lib/icons/object-icons.ts +187 -0
- package/lib/layouts/CoreLayout.vue +7 -3
- package/lib/locales/cs.json +73 -7
- package/lib/locales/de.json +5 -1
- package/lib/locales/en.json +33 -3
- package/lib/locales/es.json +9 -5
- package/lib/locales/fr.json +32 -2
- package/lib/locales/it.json +1 -1
- package/lib/locales/nl.json +51 -9
- package/lib/locales/ru.json +28 -1
- package/lib/locales/sv.json +77 -13
- package/lib/packages/collection/README.md +23 -18
- package/lib/packages/collection/create-collection.ts +22 -21
- package/lib/packages/collection/create-item.ts +21 -20
- package/lib/packages/collection/create-use-subset.ts +23 -0
- package/lib/packages/collection/guess-item-id.ts +26 -16
- package/lib/packages/collection/index.ts +4 -0
- package/lib/packages/collection/types.ts +65 -37
- package/lib/packages/collection/use-collection.ts +68 -18
- package/lib/packages/collection/use-flag-registry.ts +38 -17
- package/lib/packages/form-select/guess-label.ts +45 -0
- package/lib/packages/form-select/guess-value.ts +23 -0
- package/lib/packages/form-select/index.ts +6 -0
- package/lib/packages/form-select/normalize-search-term.ts +11 -0
- package/lib/packages/form-select/types.ts +90 -42
- package/lib/packages/form-select/use-form-option-controller.ts +7 -3
- package/lib/packages/form-select/use-form-select-controller.ts +38 -27
- package/lib/packages/form-select/use-form-select-keyboard-navigation.ts +1 -1
- package/lib/packages/form-select/use-form-select.ts +308 -130
- package/lib/packages/icon/DisplayIcon.vue +25 -0
- package/lib/packages/icon/DisplayIconAny.vue +16 -0
- package/lib/packages/icon/DisplayIconSingle.vue +35 -0
- package/lib/packages/icon/DisplayIconStack.vue +34 -0
- package/lib/packages/icon/README.md +286 -0
- package/lib/packages/icon/create-icon-bindings.ts +27 -0
- package/lib/packages/icon/define-icon-pack.ts +23 -0
- package/lib/packages/icon/define-icon-single.ts +17 -0
- package/lib/packages/icon/define-icon-stack.ts +20 -0
- package/lib/packages/icon/define-icon.ts +40 -0
- package/lib/packages/icon/generate-icon-variants.ts +17 -0
- package/lib/packages/icon/index.ts +8 -0
- package/lib/packages/icon/is-icon-stack.ts +5 -0
- package/lib/packages/icon/merge-icons.ts +25 -0
- package/lib/packages/icon/merge-transforms.ts +12 -0
- package/lib/packages/icon/normalize-icon.ts +25 -0
- package/lib/packages/icon/to-tuple.ts +7 -0
- package/lib/packages/icon/types.ts +72 -0
- package/lib/packages/job/README.md +2 -2
- package/lib/packages/mapper/README.md +166 -0
- package/lib/packages/mapper/convert-to-map.ts +5 -0
- package/lib/packages/mapper/create-mapper.ts +30 -0
- package/lib/packages/mapper/index.ts +4 -0
- package/lib/packages/mapper/types.ts +1 -0
- package/lib/packages/mapper/use-mapper.ts +31 -0
- package/lib/stores/sidebar.store.ts +1 -1
- package/lib/types/chart.ts +2 -2
- package/lib/types/utility.type.ts +9 -0
- package/lib/utils/object.util.ts +16 -0
- package/lib/utils/size.util.ts +4 -2
- package/package.json +2 -1
- package/lib/components/backup-item/VtsBackupItem.vue +0 -47
- package/lib/composables/mapper.composable.md +0 -74
- 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>{{
|
|
9
|
-
<span v-for="step in steps" :key="step">{{
|
|
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
|
-
{{
|
|
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 ?
|
|
12
|
+
:aria-label="uiStore.isMobile ? t('core.query-search-bar.label') : undefined"
|
|
13
13
|
:icon="uiStore.isDesktop ? faMagnifyingGlass : undefined"
|
|
14
|
-
: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">{{
|
|
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="
|
|
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
|
-
{{
|
|
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">{{
|
|
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
|
-
{{
|
|
20
|
+
{{ d(task.start, 'datetime_short') }}
|
|
21
21
|
<template v-if="task.end">
|
|
22
22
|
<VtsIcon :icon="faArrowRight" accent="current" />
|
|
23
|
-
{{
|
|
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
|
-
{{
|
|
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
|
-
{{
|
|
11
|
+
{{ t('core.select.n-object-of', { from, to, total }) }}
|
|
12
12
|
</span>
|
|
13
|
-
<span class="typo-body-regular-small label show">{{
|
|
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 ?
|
|
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
|
-
{{
|
|
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
|
-
{{
|
|
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
|
-
{{
|
|
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
|
-
{{
|
|
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 ?
|
|
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
|
+
})
|