@xen-orchestra/web-core 0.31.1 → 0.33.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 (75) hide show
  1. package/lib/assets/css/_colors.pcss +8 -0
  2. package/lib/components/button-group/VtsButtonGroup.vue +5 -1
  3. package/lib/components/menu/MenuList.vue +1 -2
  4. package/lib/components/menu/MenuTrigger.vue +5 -11
  5. package/lib/components/modal/VtsModal.vue +82 -0
  6. package/lib/components/modal/VtsModalButton.vue +36 -0
  7. package/lib/components/modal/VtsModalCancelButton.vue +37 -0
  8. package/lib/components/modal/VtsModalConfirmButton.vue +21 -0
  9. package/lib/components/modal/VtsModalList.vue +34 -0
  10. package/lib/components/object-icon/VtsObjectIcon.vue +3 -8
  11. package/lib/components/status/VtsStatus.vue +66 -0
  12. package/lib/components/task/VtsQuickTaskList.vue +17 -5
  13. package/lib/components/tree/VtsTreeItem.vue +2 -2
  14. package/lib/components/ui/breadcrumb/UiBreadcrumb.vue +79 -0
  15. package/lib/components/ui/button/UiButton.vue +13 -67
  16. package/lib/components/ui/modal/UiModal.vue +164 -0
  17. package/lib/components/ui/quick-task-item/UiQuickTaskItem.vue +2 -2
  18. package/lib/composables/context.composable.ts +3 -5
  19. package/lib/composables/link-component.composable.ts +3 -2
  20. package/lib/composables/pagination.composable.ts +3 -2
  21. package/lib/composables/tree-filter.composable.ts +5 -3
  22. package/lib/icons/fa-icons.ts +13 -1
  23. package/lib/icons/index.ts +17 -0
  24. package/lib/locales/cs.json +60 -2
  25. package/lib/locales/de.json +40 -2
  26. package/lib/locales/en.json +27 -1
  27. package/lib/locales/es.json +51 -5
  28. package/lib/locales/fa.json +10 -10
  29. package/lib/locales/fr.json +28 -2
  30. package/lib/locales/it.json +4 -0
  31. package/lib/locales/nl.json +64 -14
  32. package/lib/locales/pt_BR.json +3 -3
  33. package/lib/locales/ru.json +41 -2
  34. package/lib/locales/sv.json +55 -1
  35. package/lib/locales/uk.json +4 -4
  36. package/lib/packages/collection/use-collection.ts +3 -2
  37. package/lib/packages/form-select/use-form-option-controller.ts +3 -2
  38. package/lib/packages/form-select/use-form-select.ts +8 -7
  39. package/lib/packages/menu/action.ts +4 -3
  40. package/lib/packages/menu/link.ts +5 -4
  41. package/lib/packages/menu/router-link.ts +3 -2
  42. package/lib/packages/menu/toggle-target.ts +3 -2
  43. package/lib/packages/modal/ModalProvider.vue +17 -0
  44. package/lib/packages/modal/README.md +253 -0
  45. package/lib/packages/modal/create-modal-opener.ts +103 -0
  46. package/lib/packages/modal/modal.store.ts +22 -0
  47. package/lib/packages/modal/types.ts +92 -0
  48. package/lib/packages/modal/use-modal.ts +53 -0
  49. package/lib/packages/progress/use-progress.ts +4 -3
  50. package/lib/packages/table/README.md +336 -0
  51. package/lib/packages/table/apply-extensions.ts +26 -0
  52. package/lib/packages/table/define-columns.ts +62 -0
  53. package/lib/packages/table/define-renderer/define-table-cell-renderer.ts +27 -0
  54. package/lib/packages/table/define-renderer/define-table-renderer.ts +47 -0
  55. package/lib/packages/table/define-renderer/define-table-row-renderer.ts +29 -0
  56. package/lib/packages/table/define-renderer/define-table-section-renderer.ts +29 -0
  57. package/lib/packages/table/define-table/define-multi-source-table.ts +39 -0
  58. package/lib/packages/table/define-table/define-table.ts +13 -0
  59. package/lib/packages/table/define-table/define-typed-table.ts +18 -0
  60. package/lib/packages/table/index.ts +11 -0
  61. package/lib/packages/table/transform-sources.ts +13 -0
  62. package/lib/packages/table/types/extensions.ts +16 -0
  63. package/lib/packages/table/types/index.ts +47 -0
  64. package/lib/packages/table/types/table-cell.ts +18 -0
  65. package/lib/packages/table/types/table-row.ts +20 -0
  66. package/lib/packages/table/types/table-section.ts +19 -0
  67. package/lib/packages/table/types/table.ts +28 -0
  68. package/lib/packages/threshold/use-threshold.ts +4 -3
  69. package/lib/types/vue-virtual-scroller.d.ts +101 -0
  70. package/lib/utils/injection-keys.util.ts +3 -0
  71. package/lib/utils/progress.util.ts +2 -1
  72. package/lib/utils/to-computed.util.ts +15 -0
  73. package/package.json +3 -2
  74. package/lib/components/backup-state/VtsBackupState.vue +0 -37
  75. package/lib/components/connection-status/VtsConnectionStatus.vue +0 -36
@@ -12,6 +12,10 @@
12
12
 
13
13
  --color-neutral-border: #d0d0d5;
14
14
 
15
+ /* OPACITY */
16
+
17
+ --color-opacity-primary: #1a1b3833;
18
+
15
19
  /* BRAND */
16
20
 
17
21
  --color-brand-txt-base: #6b63bf;
@@ -107,6 +111,10 @@
107
111
 
108
112
  --color-neutral-border: #363647;
109
113
 
114
+ /* OPACITY */
115
+
116
+ --color-opacity-primary: #1a1b3899;
117
+
110
118
  /* BRAND */
111
119
 
112
120
  --color-brand-txt-base: #9b92ff;
@@ -28,7 +28,11 @@ const slots = defineSlots<{
28
28
  display: flex;
29
29
  justify-content: center;
30
30
  align-items: center;
31
- gap: 2.4rem;
31
+ gap: 1.6rem;
32
+
33
+ @media (--mobile) {
34
+ flex-direction: column-reverse;
35
+ }
32
36
  }
33
37
  }
34
38
  </style>
@@ -88,12 +88,11 @@ const open = (event: MouseEvent) => {
88
88
  .menu-list {
89
89
  display: inline-flex;
90
90
  flex-direction: column;
91
- padding: 0.4rem;
92
91
  cursor: default;
93
92
  color: var(--color-neutral-txt-primary);
94
93
  border-radius: 0.4rem;
95
94
  background-color: var(--color-neutral-background-primary);
96
- gap: 0.2rem;
95
+ z-index: 1010;
97
96
 
98
97
  &.horizontal {
99
98
  flex-direction: row;
@@ -20,17 +20,11 @@ defineProps<{
20
20
 
21
21
  <style lang="postcss" scoped>
22
22
  .menu-trigger {
23
- font-size: 1.6rem;
24
- font-weight: 400;
25
23
  display: flex;
26
24
  align-items: center;
27
- height: 4.4rem;
28
- padding-right: 1.5rem;
29
- padding-left: 1.5rem;
30
- white-space: nowrap;
31
- border-radius: 0.8rem;
32
- gap: 1rem;
33
- background-color: var(--color-neutral-background-primary);
25
+ padding-inline: 1.6rem;
26
+ gap: 0.8rem;
27
+ height: 4.5rem;
34
28
 
35
29
  &.disabled {
36
30
  color: var(--color-neutral-txt-secondary);
@@ -40,12 +34,12 @@ defineProps<{
40
34
  cursor: pointer;
41
35
 
42
36
  &:hover {
43
- background-color: var(--color-brand-background-selected);
37
+ background-color: var(--color-brand-background-hover);
44
38
  }
45
39
 
46
40
  &:active,
47
41
  &.active {
48
- background-color: var(--color-brand-background-hover);
42
+ background-color: var(--color-brand-background-active);
49
43
  }
50
44
  }
51
45
  }
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <UiModal :icon :accent v-on="{ dismiss: handleDismiss, submit: handleSubmit }">
3
+ <template v-if="slots.title" #title>
4
+ <slot name="title" />
5
+ </template>
6
+
7
+ <template #content>
8
+ <slot name="content" />
9
+ </template>
10
+
11
+ <template v-if="slots.buttons" #buttons>
12
+ <slot name="buttons" />
13
+ </template>
14
+ </UiModal>
15
+ </template>
16
+
17
+ <script lang="ts" setup>
18
+ import UiModal, { type ModalAccent } from '@core/components/ui/modal/UiModal.vue'
19
+ import type { IconName } from '@core/icons'
20
+ import { IK_MODAL } from '@core/packages/modal/types.ts'
21
+ import { IK_MODAL_ACCENT } from '@core/utils/injection-keys.util.ts'
22
+ import { useMagicKeys, whenever } from '@vueuse/core'
23
+ import { computed, inject, provide } from 'vue'
24
+
25
+ const { accent, dismissible, onConfirm, onDismiss, current } = defineProps<{
26
+ accent: ModalAccent
27
+ icon?: IconName
28
+ dismissible?: boolean
29
+ onConfirm?: () => void
30
+ onDismiss?: () => void
31
+ current?: boolean
32
+ }>()
33
+
34
+ const emit = defineEmits<{
35
+ confirm: []
36
+ dismiss: []
37
+ }>()
38
+
39
+ const slots = defineSlots<{
40
+ content(): any
41
+ buttons?(): any
42
+ title?(): any
43
+ }>()
44
+
45
+ const modal = inject(IK_MODAL)
46
+
47
+ provide(
48
+ IK_MODAL_ACCENT,
49
+ computed(() => accent)
50
+ )
51
+
52
+ const handleDismiss = computed(() => {
53
+ if (!dismissible) {
54
+ return undefined
55
+ }
56
+
57
+ if (onDismiss) {
58
+ return () => emit('dismiss')
59
+ }
60
+
61
+ return () => modal?.value.onCancel()
62
+ })
63
+
64
+ function handleSubmit(event: SubmitEvent) {
65
+ event.preventDefault()
66
+
67
+ if (onConfirm) {
68
+ emit('confirm')
69
+ return
70
+ }
71
+
72
+ modal?.value.onConfirm()
73
+ }
74
+
75
+ const { escape } = useMagicKeys()
76
+
77
+ whenever(escape, () => {
78
+ if (dismissible && current) {
79
+ handleDismiss.value?.()
80
+ }
81
+ })
82
+ </script>
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <UiButton :accent="buttonAccent" :busy="modal?.isBusy.value" :variant size="medium">
3
+ <slot />
4
+ </UiButton>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ import UiButton, { type ButtonVariant } from '@core/components/ui/button/UiButton.vue'
9
+ import { useMapper } from '@core/packages/mapper'
10
+ import { IK_MODAL } from '@core/packages/modal/types.ts'
11
+ import { IK_MODAL_ACCENT } from '@core/utils/injection-keys.util.ts'
12
+ import { inject } from 'vue'
13
+
14
+ defineProps<{
15
+ variant: ButtonVariant
16
+ }>()
17
+
18
+ defineSlots<{
19
+ default(): any
20
+ }>()
21
+
22
+ const modal = inject(IK_MODAL)
23
+
24
+ const modalAccent = inject(IK_MODAL_ACCENT)
25
+
26
+ const buttonAccent = useMapper(
27
+ () => modalAccent?.value,
28
+ {
29
+ success: 'brand',
30
+ info: 'brand',
31
+ warning: 'warning',
32
+ danger: 'danger',
33
+ },
34
+ 'info'
35
+ )
36
+ </script>
@@ -0,0 +1,37 @@
1
+ <template>
2
+ <VtsModalButton variant="secondary" @click="handleClick">
3
+ <slot>{{ t('cancel') }}</slot>
4
+ </VtsModalButton>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ import VtsModalButton from '@core/components/modal/VtsModalButton.vue'
9
+ import { IK_MODAL } from '@core/packages/modal/types.ts'
10
+ import { inject } from 'vue'
11
+ import { useI18n } from 'vue-i18n'
12
+
13
+ const { onClick } = defineProps<{
14
+ onClick?: () => void
15
+ }>()
16
+
17
+ const emit = defineEmits<{
18
+ click: []
19
+ }>()
20
+
21
+ defineSlots<{
22
+ default?(): any
23
+ }>()
24
+
25
+ const { t } = useI18n()
26
+
27
+ const modal = inject(IK_MODAL)
28
+
29
+ function handleClick() {
30
+ if (onClick) {
31
+ emit('click')
32
+ return
33
+ }
34
+
35
+ modal?.value.onCancel()
36
+ }
37
+ </script>
@@ -0,0 +1,21 @@
1
+ <template>
2
+ <VtsModalButton variant="primary" :type="onClick ? 'button' : 'submit'" @click="emit('click')">
3
+ <slot />
4
+ </VtsModalButton>
5
+ </template>
6
+
7
+ <script lang="ts" setup>
8
+ import VtsModalButton from '@core/components/modal/VtsModalButton.vue'
9
+
10
+ const { onClick } = defineProps<{
11
+ onClick?: () => void
12
+ }>()
13
+
14
+ const emit = defineEmits<{
15
+ click: []
16
+ }>()
17
+
18
+ defineSlots<{
19
+ default(): any
20
+ }>()
21
+ </script>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <div v-if="modalStore.modals.length > 0" class="vts-modal-list">
3
+ <ModalProvider v-for="(modal, index) of modalStore.modals" :key="modal.id" :modal>
4
+ <component
5
+ :is="modal.component"
6
+ class="modal-component"
7
+ v-bind="modal.props"
8
+ :current="index === modalStore.modals.length - 1"
9
+ @confirm="modal.onConfirm"
10
+ @cancel="modal.onCancel"
11
+ />
12
+ </ModalProvider>
13
+ </div>
14
+ </template>
15
+
16
+ <script lang="ts" setup>
17
+ import { useModalStore } from '@core/packages/modal/modal.store.ts'
18
+ import ModalProvider from '@core/packages/modal/ModalProvider.vue'
19
+
20
+ const modalStore = useModalStore()
21
+ </script>
22
+
23
+ <style lang="postcss" scoped>
24
+ .vts-modal-list {
25
+ position: fixed;
26
+ inset: 0;
27
+ background-color: var(--color-opacity-primary);
28
+ z-index: 1010;
29
+
30
+ .modal-component:not(:last-child) {
31
+ filter: brightness(0.8);
32
+ }
33
+ }
34
+ </style>
@@ -2,19 +2,14 @@
2
2
  <VtsIcon :name="iconName" :size />
3
3
  </template>
4
4
 
5
- <script generic="TType extends ObjectIconType" lang="ts" setup>
5
+ <script generic="TType extends ObjectType, TState extends ObjectState<TType>" lang="ts" setup>
6
6
  import VtsIcon, { type IconSize } from '@core/components/icon/VtsIcon.vue'
7
- import type { ObjectIconName } from '@core/icons'
7
+ import type { ObjectIconName, ObjectState, ObjectType } from '@core/icons'
8
8
  import { computed } from 'vue'
9
9
 
10
- export type ObjectIconType = ObjectIconName extends `object:${infer TType}:${string}` ? TType : never
11
-
12
- export type ObjectIconState<TType extends ObjectIconType> =
13
- Extract<ObjectIconName, `object:${TType}:${string}`> extends `object:${TType}:${infer TState}` ? TState : never
14
-
15
10
  const { type, state } = defineProps<{
16
11
  type: TType
17
- state: ObjectIconState<TType>
12
+ state: TState
18
13
  size: IconSize
19
14
  }>()
20
15
 
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <UiInfo v-tooltip="iconOnly ? currentStatus.text : false" class="vts-status" :accent="currentStatus.accent">
3
+ <template v-if="!iconOnly">{{ currentStatus.text }}</template>
4
+ </UiInfo>
5
+ </template>
6
+
7
+ <script setup lang="ts">
8
+ import UiInfo, { type InfoAccent } from '@core/components/ui/info/UiInfo.vue'
9
+ import { vTooltip } from '@core/directives/tooltip.directive'
10
+ import { useMapper } from '@core/packages/mapper'
11
+ import { useI18n } from 'vue-i18n'
12
+
13
+ export type Status =
14
+ | 'connecting'
15
+ | 'connected'
16
+ | 'disconnected'
17
+ | 'partially-connected'
18
+ | 'disconnected-from-physical-device'
19
+ | 'physically-disconnected'
20
+ | 'unable-to-connect-to-the-pool'
21
+ | 'success'
22
+ | 'skipped'
23
+ | 'interrupted'
24
+ | 'failure'
25
+ | 'pending'
26
+ | 'enabled'
27
+ | 'disabled'
28
+ | true
29
+ | false
30
+
31
+ const { status } = defineProps<{
32
+ status: Status
33
+ iconOnly?: boolean
34
+ }>()
35
+
36
+ const { t } = useI18n()
37
+
38
+ const currentStatus = useMapper<Status, { text: string; accent: InfoAccent }>(
39
+ () => status,
40
+ () => [
41
+ ['connecting', { text: t('connecting'), accent: 'info' }],
42
+ ['connected', { text: t('connected'), accent: 'success' }],
43
+ ['disconnected', { text: t('disconnected'), accent: 'danger' }],
44
+ ['partially-connected', { text: t('partially-connected'), accent: 'warning' }],
45
+ ['disconnected-from-physical-device', { text: t('disconnected-from-physical-device'), accent: 'warning' }],
46
+ ['physically-disconnected', { text: t('disconnected-from-physical-device'), accent: 'danger' }],
47
+ ['unable-to-connect-to-the-pool', { text: t('unable-to-connect-to-the-pool'), accent: 'danger' }],
48
+ ['success', { text: t('success'), accent: 'success' }],
49
+ ['skipped', { text: t('skipped'), accent: 'warning' }],
50
+ ['interrupted', { text: t('interrupted'), accent: 'danger' }],
51
+ ['failure', { text: t('failure'), accent: 'danger' }],
52
+ ['pending', { text: t('in-progress'), accent: 'info' }],
53
+ ['enabled', { text: t('enabled'), accent: 'success' }],
54
+ ['disabled', { text: t('disabled'), accent: 'muted' }],
55
+ [true, { text: t('enabled'), accent: 'success' }],
56
+ [false, { text: t('disabled'), accent: 'muted' }],
57
+ ],
58
+ false
59
+ )
60
+ </script>
61
+
62
+ <style lang="postcss" scoped>
63
+ .vts-status {
64
+ align-items: center;
65
+ }
66
+ </style>
@@ -1,15 +1,26 @@
1
1
  <template>
2
- <ul :class="{ sublist }" class="vts-quick-task-list">
2
+ <DynamicScroller
3
+ v-if="!loading && tasks.length > 0"
4
+ :items="tasks"
5
+ :min-item-size="83"
6
+ class="vts-quick-task-list"
7
+ list-tag="ul"
8
+ item-tag="li"
9
+ >
10
+ <template #default="{ item: task, active }">
11
+ <DynamicScrollerItem :item="task" :active :size-dependencies="[task.subtasks]">
12
+ <UiQuickTaskItem :task />
13
+ </DynamicScrollerItem>
14
+ </template>
15
+ </DynamicScroller>
16
+ <ul v-else class="vts-quick-task-list">
3
17
  <li v-if="loading">
4
18
  <div class="loading">
5
19
  <UiLoader />
6
20
  <div>{{ t('loading') }}</div>
7
21
  </div>
8
22
  </li>
9
- <template v-else>
10
- <li v-if="tasks.length === 0" class="typo-body-bold">{{ t('tasks.no-tasks') }}</li>
11
- <UiQuickTaskItem v-for="task of tasks" :key="task.id" :task />
12
- </template>
23
+ <li v-else-if="tasks.length === 0" class="typo-body-bold">{{ t('tasks.no-tasks') }}</li>
13
24
  </ul>
14
25
  </template>
15
26
 
@@ -17,6 +28,7 @@
17
28
  import UiLoader from '@core/components/ui/loader/UiLoader.vue'
18
29
  import UiQuickTaskItem, { type Task } from '@core/components/ui/quick-task-item/UiQuickTaskItem.vue'
19
30
  import { useI18n } from 'vue-i18n'
31
+ import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
20
32
 
21
33
  defineProps<{
22
34
  tasks: Task[]
@@ -1,8 +1,8 @@
1
1
  <template>
2
- <li class="vts-tree-item" @click="handleClick()">
2
+ <div class="vts-tree-item" @click="handleClick()">
3
3
  <slot />
4
4
  <slot v-if="expanded" name="sublist" />
5
- </li>
5
+ </div>
6
6
  </template>
7
7
 
8
8
  <script lang="ts" setup>
@@ -0,0 +1,79 @@
1
+ <!-- v2 -->
2
+ <template>
3
+ <nav class="ui-breadcrumb" :class="className" :aria-label="t('aria.breadcrumb.label')">
4
+ <ol>
5
+ <li v-for="child in slots.default()" :key="child.ctx.uid">
6
+ <component :is="child" />
7
+ </li>
8
+ </ol>
9
+ </nav>
10
+ </template>
11
+
12
+ <script lang="ts" setup>
13
+ import { useMapper } from '@core/packages/mapper'
14
+ import { toVariants } from '@core/utils/to-variants.util.ts'
15
+ import { computed } from 'vue'
16
+ import { useI18n } from 'vue-i18n'
17
+
18
+ const { size } = defineProps<{
19
+ size: 'small' | 'medium'
20
+ }>()
21
+
22
+ const slots = defineSlots<{
23
+ default(): any
24
+ }>()
25
+
26
+ const { t } = useI18n()
27
+
28
+ const fontWeight = useMapper(
29
+ () => size,
30
+ {
31
+ small: 'typo-body-bold-small',
32
+ medium: 'typo-body-bold',
33
+ },
34
+ 'medium'
35
+ )
36
+
37
+ const className = computed(() => [toVariants({ size }), fontWeight.value])
38
+ </script>
39
+
40
+ <style lang="postcss" scoped>
41
+ .ui-breadcrumb {
42
+ ol {
43
+ display: flex;
44
+ align-items: center;
45
+ gap: 0.8rem;
46
+ flex-wrap: wrap;
47
+ }
48
+
49
+ li {
50
+ display: flex;
51
+ align-items: center;
52
+ color: var(--color-neutral-txt-primary);
53
+ }
54
+
55
+ li:not(:last-child)::after {
56
+ content: '';
57
+ display: inline-block;
58
+ margin-inline: 0.8rem 0.4rem;
59
+ border-inline-end: 0.2rem solid var(--color-neutral-txt-secondary);
60
+ border-block-start: 0.2rem solid var(--color-neutral-txt-secondary);
61
+ transform: rotate(45deg);
62
+ vertical-align: middle;
63
+ }
64
+
65
+ &.size--small {
66
+ li:not(:last-child)::after {
67
+ width: 0.7rem;
68
+ height: 0.7rem;
69
+ }
70
+ }
71
+
72
+ &.size--medium {
73
+ li:not(:last-child)::after {
74
+ width: 0.9rem;
75
+ height: 0.9rem;
76
+ }
77
+ }
78
+ }
79
+ </style>