@xen-orchestra/web-core 0.26.0 → 0.27.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/components/ui/collapsible-list/UiCollapsibleList.vue +73 -0
- package/lib/components/ui/{quoteCode/UiQuoteCode.vue → log-entry-viewer/UiLogEntryViewer.vue} +54 -21
- package/lib/components/ui/table-pagination/UiTablePagination.vue +19 -79
- package/lib/composables/default-tab.composable.md +42 -0
- package/lib/composables/default-tab.composable.ts +26 -0
- package/lib/composables/link-component.composable.ts +3 -2
- package/lib/locales/cs.json +0 -1
- package/lib/locales/de.json +0 -1
- package/lib/locales/en.json +3 -1
- package/lib/locales/es.json +0 -1
- package/lib/locales/fa.json +0 -1
- package/lib/locales/fr.json +3 -1
- package/lib/locales/nl.json +0 -1
- package/lib/locales/sv.json +0 -1
- package/lib/packages/form-select/types.ts +3 -0
- package/lib/packages/form-select/use-form-option-controller.ts +5 -6
- package/lib/packages/form-select/use-form-select-controller.ts +1 -0
- package/lib/packages/form-select/use-form-select.ts +153 -109
- package/lib/packages/remote-resource/README.md +115 -0
- package/lib/packages/remote-resource/define-remote-resource.ts +294 -0
- package/lib/packages/remote-resource/types.ts +28 -0
- package/lib/types/utility.type.ts +1 -1
- package/lib/utils/date-sorter.utils.ts +12 -0
- package/package.json +2 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<!-- v1 -->
|
|
2
|
+
<template>
|
|
3
|
+
<div class="ui-collapsible-list" :class="{ expanded: isExpanded }">
|
|
4
|
+
<component :is="tag" class="container">
|
|
5
|
+
<slot />
|
|
6
|
+
</component>
|
|
7
|
+
<div v-if="hasMoreItems" class="footer">
|
|
8
|
+
<span v-if="!isExpanded" class="typo-body-regular-small">{{ t('n-more', { n: remainingItems }) }}</span>
|
|
9
|
+
<UiButton size="small" accent="brand" variant="tertiary" @click="isExpanded = !isExpanded">
|
|
10
|
+
{{ isExpanded ? t('see-less') : t('see-all') }}
|
|
11
|
+
</UiButton>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script lang="ts" setup>
|
|
17
|
+
import UiButton from '@core/components/ui/button/UiButton.vue'
|
|
18
|
+
import { useStyleTag } from '@vueuse/core'
|
|
19
|
+
import { useMax } from '@vueuse/math'
|
|
20
|
+
import { computed, ref } from 'vue'
|
|
21
|
+
import { useI18n } from 'vue-i18n'
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
tag,
|
|
25
|
+
totalItems,
|
|
26
|
+
visibleItems = 5,
|
|
27
|
+
} = defineProps<{
|
|
28
|
+
tag: string
|
|
29
|
+
totalItems: number
|
|
30
|
+
visibleItems?: number
|
|
31
|
+
}>()
|
|
32
|
+
|
|
33
|
+
defineSlots<{
|
|
34
|
+
default(): any
|
|
35
|
+
}>()
|
|
36
|
+
|
|
37
|
+
const { t } = useI18n()
|
|
38
|
+
|
|
39
|
+
const remainingItems = useMax(() => totalItems - visibleItems, 0)
|
|
40
|
+
|
|
41
|
+
const hasMoreItems = computed(() => remainingItems.value > 0)
|
|
42
|
+
|
|
43
|
+
const isExpanded = ref(false)
|
|
44
|
+
|
|
45
|
+
const style = computed(
|
|
46
|
+
() => `.ui-collapsible-list:not(.expanded) > .container *:nth-child(n+${visibleItems + 1}) { display: none }`
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
useStyleTag(style)
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<style lang="postcss" scoped>
|
|
53
|
+
.ui-collapsible-list {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
gap: 0.8rem;
|
|
57
|
+
align-items: start;
|
|
58
|
+
|
|
59
|
+
.container {
|
|
60
|
+
display: flex;
|
|
61
|
+
flex-direction: column;
|
|
62
|
+
gap: 0.8rem;
|
|
63
|
+
width: 100%;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.footer {
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 1.2rem;
|
|
69
|
+
align-items: center;
|
|
70
|
+
color: var(--color-neutral-txt-secondary);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
</style>
|
package/lib/components/ui/{quoteCode/UiQuoteCode.vue → log-entry-viewer/UiLogEntryViewer.vue}
RENAMED
|
@@ -1,44 +1,47 @@
|
|
|
1
|
+
<!-- v2 -->
|
|
1
2
|
<template>
|
|
2
|
-
<div class="ui-
|
|
3
|
+
<div class="ui-log-entry-viewer" :class="className">
|
|
3
4
|
<div class="label-container">
|
|
4
5
|
<div :class="fontClasses.labelClass" class="label">
|
|
5
6
|
{{ label }}
|
|
6
7
|
</div>
|
|
7
|
-
<div
|
|
8
|
-
<VtsCopyButton
|
|
9
|
-
<
|
|
8
|
+
<div class="actions">
|
|
9
|
+
<VtsCopyButton :value="content" />
|
|
10
|
+
<UiButtonIcon :icon="faArrowUpRightFromSquare" size="medium" accent="brand" @click="openRawValueInNewTab()" />
|
|
10
11
|
</div>
|
|
11
12
|
</div>
|
|
12
|
-
<code
|
|
13
|
-
|
|
13
|
+
<code :class="fontClasses.codeClass" class="code-container">
|
|
14
|
+
{{ content }}
|
|
14
15
|
</code>
|
|
15
16
|
</div>
|
|
16
17
|
</template>
|
|
17
18
|
|
|
18
19
|
<script setup lang="ts">
|
|
19
20
|
import VtsCopyButton from '@core/components/copy-button/VtsCopyButton.vue'
|
|
21
|
+
import UiButtonIcon from '@core/components/ui/button-icon/UiButtonIcon.vue'
|
|
20
22
|
import { useMapper } from '@core/packages/mapper'
|
|
21
23
|
import { toVariants } from '@core/utils/to-variants.util.ts'
|
|
22
|
-
import {
|
|
24
|
+
import { faArrowUpRightFromSquare } from '@fortawesome/free-solid-svg-icons'
|
|
25
|
+
import { computed, watch } from 'vue'
|
|
23
26
|
|
|
24
|
-
type
|
|
25
|
-
type
|
|
27
|
+
type LogEntryViewerAccent = 'info' | 'warning' | 'danger'
|
|
28
|
+
type LogEntryViewerSize = 'small' | 'medium'
|
|
26
29
|
|
|
27
|
-
const {
|
|
30
|
+
const {
|
|
31
|
+
size,
|
|
32
|
+
accent,
|
|
33
|
+
content: rawContent = '',
|
|
34
|
+
} = defineProps<{
|
|
28
35
|
label: string
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
content: string | object | undefined
|
|
37
|
+
size: LogEntryViewerSize
|
|
38
|
+
accent: LogEntryViewerAccent
|
|
32
39
|
}>()
|
|
33
40
|
|
|
34
|
-
|
|
41
|
+
defineSlots<{
|
|
35
42
|
default(): any
|
|
36
|
-
actions?(): any
|
|
37
43
|
}>()
|
|
38
44
|
|
|
39
|
-
const codeElement = useTemplateRef('code-element')
|
|
40
|
-
const codeTextValue = computed(() => codeElement.value?.textContent)
|
|
41
|
-
|
|
42
45
|
const mapping = {
|
|
43
46
|
small: {
|
|
44
47
|
labelClass: 'typo-body-regular-small',
|
|
@@ -57,10 +60,32 @@ const className = computed(() =>
|
|
|
57
60
|
accent,
|
|
58
61
|
})
|
|
59
62
|
)
|
|
63
|
+
|
|
64
|
+
const content = computed(() => {
|
|
65
|
+
if (typeof rawContent === 'object') {
|
|
66
|
+
return JSON.stringify(rawContent, null, 2)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return rawContent
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const pre = document.createElement('pre')
|
|
73
|
+
|
|
74
|
+
watch(
|
|
75
|
+
() => content,
|
|
76
|
+
content => {
|
|
77
|
+
pre.textContent = content.value
|
|
78
|
+
},
|
|
79
|
+
{ immediate: true }
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
function openRawValueInNewTab() {
|
|
83
|
+
window.open('', '_blank')?.document.body.appendChild(pre)
|
|
84
|
+
}
|
|
60
85
|
</script>
|
|
61
86
|
|
|
62
87
|
<style lang="postcss" scoped>
|
|
63
|
-
.ui-
|
|
88
|
+
.ui-log-entry-viewer {
|
|
64
89
|
display: flex;
|
|
65
90
|
flex-direction: column;
|
|
66
91
|
gap: 0.4rem;
|
|
@@ -88,11 +113,19 @@ const className = computed(() =>
|
|
|
88
113
|
border-inline-start: 0.2rem solid;
|
|
89
114
|
white-space: pre-wrap;
|
|
90
115
|
word-break: break-word;
|
|
116
|
+
max-height: 18rem;
|
|
117
|
+
overflow: auto;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
&.accent--info {
|
|
121
|
+
.code-container {
|
|
122
|
+
border-inline-start-color: var(--color-info-item-base);
|
|
123
|
+
}
|
|
91
124
|
}
|
|
92
125
|
|
|
93
|
-
&.accent--
|
|
126
|
+
&.accent--warning {
|
|
94
127
|
.code-container {
|
|
95
|
-
border-inline-start-color: var(--color-
|
|
128
|
+
border-inline-start-color: var(--color-warning-item-base);
|
|
96
129
|
}
|
|
97
130
|
}
|
|
98
131
|
|
|
@@ -11,27 +11,15 @@
|
|
|
11
11
|
{{ t('core.select.n-object-of', { from, to, total }) }}
|
|
12
12
|
</span>
|
|
13
13
|
<span class="typo-body-regular-small label show">{{ t('core.pagination.show-by') }}</span>
|
|
14
|
-
<
|
|
15
|
-
<select v-model="showBy" class="dropdown typo-body-regular-small">
|
|
16
|
-
<option v-for="option in [12, 24, 48, -1]" :key="option" :value="option" class="typo-body-bold-small">
|
|
17
|
-
{{ option === -1 ? t('core.pagination.all') : option }}
|
|
18
|
-
</option>
|
|
19
|
-
</select>
|
|
20
|
-
<VtsIcon :icon="faAngleDown" accent="current" class="icon" />
|
|
21
|
-
</div>
|
|
14
|
+
<VtsSelect :id="showBySelectId" accent="brand" class="typo-body-regular-small show-by-select" />
|
|
22
15
|
</div>
|
|
23
16
|
</template>
|
|
24
17
|
|
|
25
18
|
<script lang="ts" setup>
|
|
26
|
-
import
|
|
19
|
+
import VtsSelect from '@core/components/select/VtsSelect.vue'
|
|
27
20
|
import PaginationButton from '@core/components/ui/table-pagination/PaginationButton.vue'
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
faAngleDoubleRight,
|
|
31
|
-
faAngleDown,
|
|
32
|
-
faAngleLeft,
|
|
33
|
-
faAngleRight,
|
|
34
|
-
} from '@fortawesome/free-solid-svg-icons'
|
|
21
|
+
import { useFormSelect } from '@core/packages/form-select'
|
|
22
|
+
import { faAngleDoubleLeft, faAngleDoubleRight, faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons'
|
|
35
23
|
import { useI18n } from 'vue-i18n'
|
|
36
24
|
|
|
37
25
|
defineProps<{
|
|
@@ -52,6 +40,13 @@ const emit = defineEmits<{
|
|
|
52
40
|
const showBy = defineModel<number>('showBy', { default: 24 })
|
|
53
41
|
|
|
54
42
|
const { t } = useI18n()
|
|
43
|
+
|
|
44
|
+
const { id: showBySelectId } = useFormSelect([12, 24, 48, -1], {
|
|
45
|
+
model: showBy,
|
|
46
|
+
option: {
|
|
47
|
+
label: option => (option === -1 ? t('core.pagination.all') : option.toString()),
|
|
48
|
+
},
|
|
49
|
+
})
|
|
55
50
|
</script>
|
|
56
51
|
|
|
57
52
|
<style lang="postcss" scoped>
|
|
@@ -74,73 +69,18 @@ const { t } = useI18n()
|
|
|
74
69
|
margin-right: 0.8rem;
|
|
75
70
|
}
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
|
|
72
|
+
/* Workaround: we don't have "small" select yet */
|
|
73
|
+
.show-by-select {
|
|
74
|
+
width: 7rem;
|
|
79
75
|
|
|
80
|
-
.
|
|
81
|
-
cursor: pointer;
|
|
82
|
-
padding: 0.2rem 0.6rem;
|
|
76
|
+
&:deep(.ui-input) {
|
|
83
77
|
height: 3rem;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
border-radius: 0.4rem;
|
|
87
|
-
color: var(--color-brand-txt-base);
|
|
88
|
-
border: 0.1rem solid var(--color-neutral-border);
|
|
89
|
-
background-color: var(--color-neutral-background-primary);
|
|
90
|
-
|
|
91
|
-
&:hover {
|
|
92
|
-
border-color: var(--color-brand-item-hover);
|
|
93
|
-
background-color: var(--color-brand-background-hover);
|
|
94
|
-
color: var(--color-brand-txt-hover);
|
|
95
|
-
|
|
96
|
-
+ .icon {
|
|
97
|
-
color: var(--color-brand-txt-hover);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
&:disabled {
|
|
102
|
-
cursor: not-allowed;
|
|
103
|
-
background-color: var(--color-neutral-background-disabled);
|
|
104
|
-
color: var(--color-neutral-txt-secondary);
|
|
105
|
-
border-color: transparent;
|
|
106
|
-
|
|
107
|
-
+ .icon {
|
|
108
|
-
color: var(--color-neutral-txt-secondary);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
&:active {
|
|
113
|
-
background-color: var(--color-brand-background-active);
|
|
114
|
-
border-color: var(--color-brand-item-active);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
&:focus-visible {
|
|
118
|
-
outline: 0.1rem solid var(--color-brand-item-base);
|
|
119
|
-
border: 0.1rem solid var(--color-brand-item-base);
|
|
120
|
-
color: var(--color-brand-txt-base);
|
|
121
|
-
background-color: var(--color-brand-background-selected);
|
|
122
|
-
|
|
123
|
-
+ .icon {
|
|
124
|
-
color: var(--color-brand-txt-base);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
option {
|
|
129
|
-
background-color: var(--color-neutral-background-primary);
|
|
130
|
-
border: 0.1rem solid var(--color-neutral-border);
|
|
131
|
-
border-radius: 0.4rem;
|
|
132
|
-
color: var(--color-neutral-txt-primary);
|
|
133
|
-
}
|
|
78
|
+
padding-inline: 0.8rem;
|
|
79
|
+
gap: 0.8rem;
|
|
134
80
|
}
|
|
135
81
|
|
|
136
|
-
.
|
|
137
|
-
|
|
138
|
-
top: 50%;
|
|
139
|
-
right: 0.8rem;
|
|
140
|
-
transform: translateY(-50%);
|
|
141
|
-
pointer-events: none;
|
|
142
|
-
font-size: 1rem;
|
|
143
|
-
color: var(--color-brand-txt-base);
|
|
82
|
+
&:deep(.input) {
|
|
83
|
+
font-size: 1.4rem;
|
|
144
84
|
}
|
|
145
85
|
}
|
|
146
86
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# `useDefaultTab` Composable
|
|
2
|
+
|
|
3
|
+
This composable manages automatic navigation to default tabs and remembers the last visited tab for a given router dispatcher.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
The composable watches route changes and:
|
|
8
|
+
|
|
9
|
+
- Automatically redirects to the remembered or default tab when navigating to the base dispatcher route
|
|
10
|
+
- Stores the current tab in localStorage for future visits
|
|
11
|
+
- Uses the naming convention `{dispatcherRouteName}/{tabName}` for tab route names
|
|
12
|
+
|
|
13
|
+
## Parameters
|
|
14
|
+
|
|
15
|
+
| Name | Type | Required | Description |
|
|
16
|
+
| --------------------- | -------- | :------: | ----------------------------------------------- |
|
|
17
|
+
| `dispatcherRouteName` | `string` | ✓ | Dispatcher page route name (e.g., '/pool/<id>') |
|
|
18
|
+
| `defaultTab` | `string` | ✓ | Default tab name (e.g., 'dashboard') |
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// In the dispatcher page component (e.g., `pages/pool/[id].vue`)
|
|
24
|
+
useDefaultTab('pool/[id]', 'dashboard')
|
|
25
|
+
|
|
26
|
+
// User first navigates to /pool/123
|
|
27
|
+
// → Automatic redirect to /pool/123/dashboard (default tab)
|
|
28
|
+
|
|
29
|
+
// User then navigates to /pool/123/stats
|
|
30
|
+
// → 'stats' is stored as the last visited tab for this route
|
|
31
|
+
|
|
32
|
+
// Finally, user navigates to /pool/456
|
|
33
|
+
// → Automatic redirect to /pool/456/stats
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Storage
|
|
37
|
+
|
|
38
|
+
Tab preferences are stored in localStorage under the key `default-tabs` with the structure:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
Map<string, string> // dispatcherRouteName -> lastVisitedTab
|
|
42
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useLocalStorage } from '@vueuse/core'
|
|
2
|
+
import { watch } from 'vue'
|
|
3
|
+
import { type RouteLocationRaw, type RouteRecordName, useRoute, useRouter } from 'vue-router'
|
|
4
|
+
|
|
5
|
+
export function useDefaultTab(dispatcherRouteName: RouteRecordName & string, defaultTab: string) {
|
|
6
|
+
const storage = useLocalStorage('default-tabs', new Map<string, string>())
|
|
7
|
+
const router = useRouter()
|
|
8
|
+
const route = useRoute()
|
|
9
|
+
|
|
10
|
+
watch(
|
|
11
|
+
() => route.name as string,
|
|
12
|
+
name => {
|
|
13
|
+
if (name === dispatcherRouteName) {
|
|
14
|
+
const tabName = storage.value.get(dispatcherRouteName) ?? defaultTab
|
|
15
|
+
void router.replace({ name: `${dispatcherRouteName}/${tabName}` } as RouteLocationRaw)
|
|
16
|
+
} else if (!name.startsWith(dispatcherRouteName)) {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const tab = name.slice(dispatcherRouteName.length).split('/')[1] ?? defaultTab
|
|
21
|
+
|
|
22
|
+
storage.value.set(dispatcherRouteName, tab)
|
|
23
|
+
},
|
|
24
|
+
{ immediate: true }
|
|
25
|
+
)
|
|
26
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { MaybeRefOrGetter } from 'vue'
|
|
2
2
|
import { computed, toValue } from 'vue'
|
|
3
|
-
import type {
|
|
3
|
+
import type { RouteLocationAsPathGeneric, RouteLocationAsRelativeGeneric, RouteLocationAsString } from 'vue-router'
|
|
4
4
|
|
|
5
5
|
export type LinkOptions = {
|
|
6
|
-
to
|
|
6
|
+
// Using RouteLocationRaw makes the build fail ("Expression produces a union type that is too complex to represent")
|
|
7
|
+
to?: RouteLocationAsString | RouteLocationAsRelativeGeneric | RouteLocationAsPathGeneric
|
|
7
8
|
href?: string
|
|
8
9
|
target?: '_blank' | '_self'
|
|
9
10
|
disabled?: boolean
|
package/lib/locales/cs.json
CHANGED
|
@@ -550,7 +550,6 @@
|
|
|
550
550
|
"uuid": "UUID",
|
|
551
551
|
"vcpus": "virt. procesor | virt. procesor | virt. procesory",
|
|
552
552
|
"vcpus-assigned": "Přiděleno virt. procesorů",
|
|
553
|
-
"vcpus-used": "využito virt. procesorů",
|
|
554
553
|
"vdi-throughput": "propustnost VDI",
|
|
555
554
|
"vdis": "Virt. disk | Virt. disky | Virt. disků",
|
|
556
555
|
"version": "Verze",
|
package/lib/locales/de.json
CHANGED
package/lib/locales/en.json
CHANGED
|
@@ -347,6 +347,7 @@
|
|
|
347
347
|
"n-hosts": "1 host | {n} hosts",
|
|
348
348
|
"n-hosts-awaiting-patch": "{n} host is awaiting this patch | {n} hosts are awaiting this patch",
|
|
349
349
|
"n-missing": "{n} missing",
|
|
350
|
+
"n-more": "{n} more",
|
|
350
351
|
"n-percent": "{n}%",
|
|
351
352
|
"n-vms": "1 VM | {n} VMs",
|
|
352
353
|
"name": "Name",
|
|
@@ -471,6 +472,7 @@
|
|
|
471
472
|
"scheduler-granularity": "Scheduler granularity",
|
|
472
473
|
"secure-boot": "Secure boot",
|
|
473
474
|
"see-all": "See all",
|
|
475
|
+
"see-less": "See less",
|
|
474
476
|
"select-compression": "Select a compression",
|
|
475
477
|
"select-destination-host": "Select a destination host",
|
|
476
478
|
"select-host": "Select host",
|
|
@@ -561,13 +563,13 @@
|
|
|
561
563
|
"unreachable-hosts": "Unreachable hosts",
|
|
562
564
|
"unreachable-hosts-reload-page": "Done, reload the page",
|
|
563
565
|
"up-to-date": "Up-to-date",
|
|
566
|
+
"update": "Update",
|
|
564
567
|
"used": "Used",
|
|
565
568
|
"user-config": "User config",
|
|
566
569
|
"username": "Username",
|
|
567
570
|
"uuid": "UUID",
|
|
568
571
|
"vcpus": "vCPU | vCPU | vCPUs",
|
|
569
572
|
"vcpus-assigned": "vCPUs assigned",
|
|
570
|
-
"vcpus-used": "vCPUs used",
|
|
571
573
|
"vdi-throughput": "VDI throughput",
|
|
572
574
|
"vdis": "VDI | VDI | VDIs",
|
|
573
575
|
"version": "Version",
|
package/lib/locales/es.json
CHANGED
package/lib/locales/fa.json
CHANGED
|
@@ -462,7 +462,6 @@
|
|
|
462
462
|
"user-config": "تنظیمات کاربر",
|
|
463
463
|
"uuid": "UUID",
|
|
464
464
|
"vcpus": "CPUمجازی | CPUمجازی | CPUهای مجازی",
|
|
465
|
-
"vcpus-used": "CPUهای مجازی استفاده شده",
|
|
466
465
|
"vdis": "دسکتاپ مجازی | دسکتاپ مجازی | دسکتاپ های مجازی",
|
|
467
466
|
"version": "نسخه",
|
|
468
467
|
"vif": "VIF",
|
package/lib/locales/fr.json
CHANGED
|
@@ -347,6 +347,7 @@
|
|
|
347
347
|
"n-hosts": "1 hôte | {n} hôtes",
|
|
348
348
|
"n-hosts-awaiting-patch": "{n} hôte attend ce patch | {n} hôtes attendent ce patch",
|
|
349
349
|
"n-missing": "{n} manquant | {n} manquants",
|
|
350
|
+
"n-more": "{n} de plus",
|
|
350
351
|
"n-percent": "{n} %",
|
|
351
352
|
"n-vms": "1 VM | {n} VMs",
|
|
352
353
|
"name": "Nom",
|
|
@@ -471,6 +472,7 @@
|
|
|
471
472
|
"scheduler-granularity": "Granularité de la planification",
|
|
472
473
|
"secure-boot": "Démarrage sécurisé",
|
|
473
474
|
"see-all": "Voir tout",
|
|
475
|
+
"see-less": "Voir moins",
|
|
474
476
|
"select-compression": "Sélectionnez une compression",
|
|
475
477
|
"select-destination-host": "Sélectionnez un hôte de destination",
|
|
476
478
|
"select-host": "Sélectionnez un hôte",
|
|
@@ -561,13 +563,13 @@
|
|
|
561
563
|
"unreachable-hosts": "Hôtes inaccessibles",
|
|
562
564
|
"unreachable-hosts-reload-page": "C'est fait. Rafraîchir la page",
|
|
563
565
|
"up-to-date": "À jour",
|
|
566
|
+
"update": "Mettre à jour",
|
|
564
567
|
"used": "Utilisé",
|
|
565
568
|
"user-config": "Configuration utilisateur",
|
|
566
569
|
"username": "Nom d'utilisateur",
|
|
567
570
|
"uuid": "UUID",
|
|
568
571
|
"vcpus": "vCPU | vCPU | vCPUs",
|
|
569
572
|
"vcpus-assigned": "vCPUs assignés",
|
|
570
|
-
"vcpus-used": "vCPUs utilisés",
|
|
571
573
|
"vdi-throughput": "Débit du VDI",
|
|
572
574
|
"vdis": "VDI | VDI | VDIs",
|
|
573
575
|
"version": "Version",
|
package/lib/locales/nl.json
CHANGED
package/lib/locales/sv.json
CHANGED
|
@@ -2,6 +2,8 @@ import type { CollectionItem, CollectionItemProperties } from '@core/packages/co
|
|
|
2
2
|
import type { KeyOfByValue } from '@core/types/utility.type.ts'
|
|
3
3
|
import type { ComputedRef, InjectionKey, Reactive, Ref, UnwrapRef } from 'vue'
|
|
4
4
|
|
|
5
|
+
export const EMPTY_OPTION = Symbol('EMPTY_OPTION')
|
|
6
|
+
|
|
5
7
|
export type FormSelectId<
|
|
6
8
|
TCustomProperties extends CollectionItemProperties = CollectionItemProperties,
|
|
7
9
|
TSource = unknown,
|
|
@@ -48,6 +50,7 @@ export type FormSelectController = Reactive<{
|
|
|
48
50
|
isDisabled: ComputedRef<boolean>
|
|
49
51
|
isMultiple: ComputedRef<boolean>
|
|
50
52
|
isNavigatingWithKeyboard: Ref<boolean>
|
|
53
|
+
isOpen: Ref<boolean>
|
|
51
54
|
closeDropdown(keepFocus: boolean): void
|
|
52
55
|
focusSearchOrTrigger(): void
|
|
53
56
|
}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { unrefElement, useEventListener
|
|
2
|
-
import { computed, inject, type MaybeRefOrGetter, ref, toValue } from 'vue'
|
|
1
|
+
import { unrefElement, useEventListener } from '@vueuse/core'
|
|
2
|
+
import { computed, inject, type MaybeRefOrGetter, ref, toValue, watchEffect } from 'vue'
|
|
3
3
|
import { type FormOption, IK_FORM_SELECT_CONTROLLER } from './types.ts'
|
|
4
4
|
|
|
5
5
|
export function useFormOptionController<TOption extends FormOption>(_option: MaybeRefOrGetter<TOption>) {
|
|
@@ -13,12 +13,11 @@ export function useFormOptionController<TOption extends FormOption>(_option: May
|
|
|
13
13
|
|
|
14
14
|
const elementRef = ref<HTMLDivElement>()
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
(
|
|
18
|
-
() => {
|
|
16
|
+
watchEffect(() => {
|
|
17
|
+
if (controller.isOpen && option.value.flags.active) {
|
|
19
18
|
unrefElement(elementRef)?.scrollIntoView({ block: 'nearest' })
|
|
20
19
|
}
|
|
21
|
-
)
|
|
20
|
+
})
|
|
22
21
|
|
|
23
22
|
useEventListener(elementRef, 'click', event => {
|
|
24
23
|
if (controller.isDisabled) {
|