@xen-orchestra/web-core 0.17.0 → 0.19.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 (38) hide show
  1. package/lib/components/icon/VtsIcon.vue +9 -1
  2. package/lib/components/input-wrapper/VtsInputWrapper.vue +1 -0
  3. package/lib/components/{charts/LinearChart.md → linear-chart/VtsLinearChart.md} +3 -3
  4. package/lib/components/{charts/LinearChart.vue → linear-chart/VtsLinearChart.vue} +12 -11
  5. package/lib/components/quick-info-card/VtsQuickInfoCard.vue +29 -0
  6. package/lib/components/quick-info-column/VtsQuickInfoColumn.vue +13 -0
  7. package/lib/components/quick-info-row/VtsQuickInfoRow.vue +40 -0
  8. package/lib/components/ui/alert/UiAlert.vue +105 -0
  9. package/lib/components/ui/card/UiCard.vue +12 -0
  10. package/lib/components/ui/circle-progress-bar/UiCircleProgressBar.vue +212 -0
  11. package/lib/components/ui/info/UiInfo.vue +5 -3
  12. package/lib/components/ui/input/UiInput.vue +126 -109
  13. package/lib/components/ui/radio-button/UiRadioButton.vue +1 -1
  14. package/lib/components/ui/toaster/UiToaster.vue +6 -2
  15. package/lib/composables/chart-theme.composable.ts +5 -5
  16. package/lib/composables/relative-time.composable.md +18 -0
  17. package/lib/composables/relative-time.composable.ts +66 -0
  18. package/lib/i18n.ts +16 -0
  19. package/lib/locales/cs.json +98 -18
  20. package/lib/locales/de.json +233 -24
  21. package/lib/locales/en.json +58 -2
  22. package/lib/locales/es.json +94 -14
  23. package/lib/locales/fa.json +259 -9
  24. package/lib/locales/fr.json +58 -2
  25. package/lib/locales/it.json +316 -0
  26. package/lib/locales/nl.json +503 -0
  27. package/lib/locales/ru.json +181 -0
  28. package/lib/locales/sv.json +92 -10
  29. package/lib/locales/uk.json +1 -0
  30. package/lib/packages/collection/README.md +167 -0
  31. package/lib/packages/collection/build-item.ts +45 -0
  32. package/lib/packages/collection/create-collection.ts +60 -0
  33. package/lib/packages/collection/index.ts +5 -0
  34. package/lib/packages/collection/types.ts +29 -0
  35. package/lib/packages/collection/use-collection.ts +15 -0
  36. package/lib/packages/collection/use-flag-registry.ts +47 -0
  37. package/lib/utils/time.util.ts +18 -0
  38. package/package.json +1 -1
@@ -0,0 +1,167 @@
1
+ # `useCollection` composable
2
+
3
+ The `useCollection` composable helps you manage a collection of items with flags (boolean states) and computed properties. It provides a type-safe way to filter, flag, and manipulate items in a collection.
4
+
5
+ ## Usage
6
+
7
+ ```typescript
8
+ const { items, useSubset, useFlag } = useCollection(sources, {
9
+ identifier: source => source.id,
10
+ flags: ['selected', 'active', { highlighted: { multiple: false } }],
11
+ properties: source => ({
12
+ isAvailable: computed(() => source.status === 'available'),
13
+ fullName: computed(() => `${source.firstName} ${source.lastName}`),
14
+ }),
15
+ })
16
+ ```
17
+
18
+ ## Core Concepts
19
+
20
+ - **Collection Item**: An object with a unique identifier, a reference to its source object, flags, computed properties, and methods to manipulate flags
21
+ - **Flags**: Boolean states attached to items (like 'selected', 'active', 'highlighted')
22
+ - **Properties**: Computed values derived from the source object
23
+
24
+ ## `useCollection` parameters
25
+
26
+ | Name | Type | Required | Description |
27
+ | --------- | --------------------------------- | :------: | ---------------------------------------------------- |
28
+ | `sources` | `MaybeRefOrGetter<TSource[]>` | ✓ | Array of source objects for the collection |
29
+ | `options` | `CollectionOptions<TSource, TId>` | ✓ | Configuration options for the collection (see below) |
30
+
31
+ ### `options` object
32
+
33
+ | Name | Type | Required | Description |
34
+ | ------------ | -------------------------------------------------- | :------: | -------------------------------------------------------- |
35
+ | `identifier` | `(source: TSource) => TId` | ✓ | Function to extract a unique identifier from each source |
36
+ | `flags` | `FlagsConfig<TFlag>` | | Flags that can be applied to items in the collection |
37
+ | `properties` | `(source: TSource) => Record<string, ComputedRef>` | | Function that returns computed properties for each item |
38
+
39
+ ### `FlagsConfig` type
40
+
41
+ ```typescript
42
+ type FlagsConfig<TFlag extends string> = TFlag[] | { [K in TFlag]: { multiple?: boolean } }
43
+ ```
44
+
45
+ Values for `multiple`:
46
+
47
+ - `true` (default): multiple items can have the same flag.
48
+ - `false`: only one item can have the flag at a time (exclusive selection). Setting the flag on one item will automatically unset it on all other items.
49
+
50
+ ## Return Value
51
+
52
+ | Name | Type | Description |
53
+ | ----------- | ------------------------------------------- | -------------------------------------------------------- |
54
+ | `items` | `ComputedRef<CollectionItem[]>` | Array of collection items with flags and properties |
55
+ | `useSubset` | `(filter: (item) => boolean) => Collection` | Creates a new collection that's a subset of the original |
56
+ | `useFlag` | `(flag: TFlag) => UseFlag` | Utilities for working with a specific flag |
57
+ | `count` | `ComputedRef<number>` | Number of items in the collection |
58
+
59
+ ### `CollectionItem` object
60
+
61
+ | Name | Type | Description |
62
+ | ------------ | ------------------------------ | --------------------------------------------------------- |
63
+ | `id` | `TId` | Unique identifier for the item (string, number or symbol) |
64
+ | `source` | `TSource` | The original source object |
65
+ | `flags` | `Record<TFlag, boolean>` | Object containing the state of all flags for this item |
66
+ | `properties` | `TProperties` | Object containing all computed properties for this item |
67
+ | `toggleFlag` | `(flag, forcedValue?) => void` | Method to toggle a flag on this item |
68
+
69
+ ### Return value of `useFlag(flag)`
70
+
71
+ | Name | Type | Description |
72
+ | ----------- | ------------------------------- | ------------------------------------------------------ |
73
+ | `items` | `ComputedRef<CollectionItem[]>` | Array of items that have this flag set |
74
+ | `ids` | `ComputedRef<TId[]>` | Array of IDs of items that have this flag set |
75
+ | `count` | `ComputedRef<number>` | Number of items that have this flag set |
76
+ | `areAllOn` | `ComputedRef<boolean>` | Whether all items in the collection have this flag set |
77
+ | `areSomeOn` | `ComputedRef<boolean>` | Whether at least one item has this flag set |
78
+ | `areNoneOn` | `ComputedRef<boolean>` | Whether no items have this flag set |
79
+ | `toggle` | `(id, forcedValue?) => void` | Toggle this flag on a specific item |
80
+ | `toggleAll` | `(forcedValue?) => void` | Toggle this flag on all items in the collection |
81
+
82
+ ## Flag Operations
83
+
84
+ The composable provides several ways to work with flags:
85
+
86
+ ### Setting and Toggling Flags
87
+
88
+ You can work with flags directly on collection items:
89
+
90
+ ```typescript
91
+ // Set flag for a single item
92
+ item.flags.selected = true
93
+
94
+ // Toggle the 'selected' flag on an item
95
+ // Same as item.flags.selected = !item.flags.selected
96
+ item.toggleFlag('selected')
97
+
98
+ // Force the 'selected' flag to a specific value
99
+ // Same as item.flags.selected = true
100
+ item.toggleFlag('selected', true)
101
+ ```
102
+
103
+ You can also use the utilities provided by `useFlag`:
104
+
105
+ ```typescript
106
+ // Get utilities for the 'selected' flag
107
+ const { toggle: toggleSelected, toggleAll: toggleAllSelected } = useFlag('selected')
108
+
109
+ // Toggle flag for a specific item
110
+ toggleSelected(itemId)
111
+
112
+ // Force flag to true for a specific item
113
+ toggleSelected(itemId, true)
114
+
115
+ // Toggle flag on all items
116
+ toggleAllSelected()
117
+
118
+ // Force all items to have the flag
119
+ toggleAllSelected(true)
120
+ ```
121
+
122
+ ### Checking Flags
123
+
124
+ You can check if an item has a flag:
125
+
126
+ ```typescript
127
+ // Check if an item has a specific flag
128
+ const isSelected = item.flags.selected
129
+ ```
130
+
131
+ ## Example
132
+
133
+ ```typescript
134
+ const {
135
+ items: users,
136
+ useSubset,
137
+ count,
138
+ } = useCollection(rawUsers, {
139
+ identifier: user => user.id,
140
+ flags: ['selected'],
141
+ properties: user => ({
142
+ fullName: computed(() => `${user.firstName} ${user.lastName} (${user.group})`),
143
+ }),
144
+ })
145
+
146
+ const { items: admins, useFlag: useAdminFlag, count: adminCount } = useSubset(item => item.source.group === 'admin')
147
+
148
+ const {
149
+ items: selectedAdmins,
150
+ areAllOn: areAllAdminSelected,
151
+ toggleAll: toggleAllAdminSelection,
152
+ count: selectedAdminCount,
153
+ toggle: toggleAdminSelection,
154
+ } = useAdminFlag('selected')
155
+ ```
156
+
157
+ It's also possible to create a subset of subset:
158
+
159
+ ```typescript
160
+ const { items: users, useSubset } = useCollection(rawUsers, {
161
+ /* ... */
162
+ })
163
+
164
+ const { items: admins, useSubset: useAdminSubset } = useSubset(item => item.source.group === 'admin')
165
+
166
+ const { items: activeAdmins } = useAdminSubset(item => item.source.status === 'active')
167
+ ```
@@ -0,0 +1,45 @@
1
+ import type { CollectionItem, CollectionOptions, FlagRegistry } from '@core/packages/collection'
2
+ import { unref, type UnwrapRef } from 'vue'
3
+
4
+ export function buildItem<
5
+ TSource,
6
+ TId extends PropertyKey,
7
+ TFlag extends string,
8
+ TProperties extends Record<string, unknown>,
9
+ >(
10
+ source: TSource,
11
+ options: CollectionOptions<TSource, TId, TFlag, TProperties>,
12
+ flagRegistry: FlagRegistry<TFlag>
13
+ ): CollectionItem<TSource, TId, TFlag, UnwrapRef<TProperties>> {
14
+ const id = options.identifier(source)
15
+ const properties = options.properties?.(source)
16
+
17
+ return {
18
+ id,
19
+ source,
20
+ toggleFlag(flag: TFlag, forcedValue?: boolean) {
21
+ flagRegistry.toggleFlag(id, flag, forcedValue)
22
+ },
23
+ flags: new Proxy({} as Record<TFlag, boolean>, {
24
+ has(target, flag: TFlag) {
25
+ return flagRegistry.isFlagDefined(flag)
26
+ },
27
+ get(target, flag: TFlag) {
28
+ return flagRegistry.isFlagged(id, flag)
29
+ },
30
+ set(target, flag: TFlag, value) {
31
+ flagRegistry.toggleFlag(id, flag, value)
32
+
33
+ return true
34
+ },
35
+ }),
36
+ properties: new Proxy({} as UnwrapRef<TProperties>, {
37
+ has(target, prop: Extract<keyof TProperties, string>) {
38
+ return properties !== undefined && prop in properties
39
+ },
40
+ get(target, prop: Extract<keyof TProperties, string>) {
41
+ return unref(properties?.[prop])
42
+ },
43
+ }),
44
+ }
45
+ }
@@ -0,0 +1,60 @@
1
+ import type { CollectionItem, FlagRegistry } from '@core/packages/collection'
2
+ import { useArrayFilter, useArrayMap } from '@vueuse/core'
3
+ import { computed, type ComputedRef } from 'vue'
4
+
5
+ export function createCollection<
6
+ TSource,
7
+ TId extends PropertyKey,
8
+ TFlag extends string,
9
+ TProperties extends Record<string, any>,
10
+ >(items: ComputedRef<CollectionItem<TSource, TId, TFlag, TProperties>[]>, flagRegistry: FlagRegistry<TFlag>) {
11
+ function useFlag(flag: TFlag) {
12
+ const flaggedItems = useArrayFilter(items, item => item.flags[flag])
13
+
14
+ const ids = useArrayMap(flaggedItems, item => item.id)
15
+
16
+ const count = computed(() => flaggedItems.value.length)
17
+
18
+ const areAllOn = computed(() => items.value.length === count.value)
19
+
20
+ const areSomeOn = computed(() => count.value > 0)
21
+
22
+ const areNoneOn = computed(() => count.value === 0)
23
+
24
+ function toggle(id: TId, forcedValue?: boolean) {
25
+ flagRegistry.toggleFlag(id, flag, forcedValue)
26
+ }
27
+
28
+ function toggleAll(forcedValue = !areAllOn.value) {
29
+ for (const item of items.value) {
30
+ flagRegistry.toggleFlag(item.id, flag, forcedValue)
31
+ }
32
+ }
33
+
34
+ return {
35
+ items: flaggedItems,
36
+ ids,
37
+ count,
38
+ areAllOn,
39
+ areSomeOn,
40
+ areNoneOn,
41
+ toggle,
42
+ toggleAll,
43
+ }
44
+ }
45
+
46
+ function useSubset(filter: (item: CollectionItem<TSource, TId, TFlag, TProperties>) => boolean) {
47
+ const filteredItems = useArrayFilter(items, item => filter(item))
48
+
49
+ return createCollection(filteredItems, flagRegistry)
50
+ }
51
+
52
+ const count = computed(() => items.value.length)
53
+
54
+ return {
55
+ items,
56
+ count,
57
+ useFlag,
58
+ useSubset,
59
+ }
60
+ }
@@ -0,0 +1,5 @@
1
+ export * from './build-item.ts'
2
+ export * from './create-collection.ts'
3
+ export * from './types.ts'
4
+ export * from './use-collection.ts'
5
+ export * from './use-flag-registry.ts'
@@ -0,0 +1,29 @@
1
+ import type { useFlagRegistry } from '@core/packages/collection'
2
+
3
+ export type FlagsConfig<TFlag extends string> = TFlag[] | { [K in TFlag]: { multiple?: boolean } }
4
+
5
+ export type CollectionOptions<
6
+ TSource,
7
+ TId extends PropertyKey,
8
+ TFlag extends string,
9
+ TProperties extends Record<string, unknown>,
10
+ > = {
11
+ identifier: (source: TSource) => TId
12
+ properties?: (source: TSource) => TProperties
13
+ flags?: FlagsConfig<TFlag>
14
+ }
15
+
16
+ export type CollectionItem<
17
+ TSource,
18
+ TId extends PropertyKey,
19
+ TFlag extends string,
20
+ TProperties extends Record<string, any>,
21
+ > = {
22
+ id: TId
23
+ source: TSource
24
+ flags: Record<TFlag, boolean>
25
+ properties: TProperties
26
+ toggleFlag: (flag: TFlag, forcedValue?: boolean) => void
27
+ }
28
+
29
+ export type FlagRegistry<TFlag extends string> = ReturnType<typeof useFlagRegistry<TFlag>>
@@ -0,0 +1,15 @@
1
+ import { buildItem, type CollectionOptions, createCollection, useFlagRegistry } from '@core/packages/collection'
2
+ import { computed, type MaybeRefOrGetter, toValue } from 'vue'
3
+
4
+ export function useCollection<
5
+ TSource,
6
+ TId extends PropertyKey,
7
+ TFlag extends string,
8
+ TProperties extends Record<string, unknown>,
9
+ >(sources: MaybeRefOrGetter<TSource[]>, options: CollectionOptions<TSource, TId, TFlag, TProperties>) {
10
+ const flagRegistry = useFlagRegistry<TFlag>(options.flags)
11
+
12
+ const items = computed(() => toValue(sources).map(source => buildItem(source, options, flagRegistry)))
13
+
14
+ return createCollection(items, flagRegistry)
15
+ }
@@ -0,0 +1,47 @@
1
+ import type { FlagsConfig } from '@core/packages/collection/types.ts'
2
+ import { reactive } from 'vue'
3
+
4
+ export function useFlagRegistry<TFlag extends string>(_flags: FlagsConfig<TFlag> = []) {
5
+ const registry = reactive(new Map()) as Map<TFlag, Set<PropertyKey> | undefined>
6
+
7
+ const flags = Array.isArray(_flags) ? Object.fromEntries(_flags.map(flag => [flag, { multiple: true }])) : _flags
8
+
9
+ function isFlagDefined(flag: TFlag) {
10
+ return flags[flag] !== undefined
11
+ }
12
+
13
+ function isFlagged(id: PropertyKey, flag: TFlag) {
14
+ return registry.get(flag)?.has(id) ?? false
15
+ }
16
+
17
+ function toggleFlag(id: PropertyKey, flag: TFlag, forcedValue = !isFlagged(id, flag)) {
18
+ if (!registry.has(flag)) {
19
+ registry.set(flag, new Set())
20
+ }
21
+
22
+ if (forcedValue) {
23
+ if (!isMultipleAllowed(flag)) {
24
+ clearFlag(flag)
25
+ }
26
+ registry.get(flag)!.add(id)
27
+ } else {
28
+ registry.get(flag)!.delete(id)
29
+ }
30
+ }
31
+
32
+ function clearFlag(flag: TFlag) {
33
+ registry.set(flag, new Set())
34
+ }
35
+
36
+ function isMultipleAllowed(flag: TFlag) {
37
+ return flags[flag]?.multiple ?? false
38
+ }
39
+
40
+ return {
41
+ isFlagged,
42
+ isFlagDefined,
43
+ toggleFlag,
44
+ clearFlag,
45
+ isMultipleAllowed,
46
+ }
47
+ }
@@ -0,0 +1,18 @@
1
+ import { utcParse } from 'd3-time-format'
2
+
3
+ export function parseDateTime(dateTime: Date | string | number): number {
4
+ if (typeof dateTime === 'number') {
5
+ return dateTime
6
+ }
7
+
8
+ if (dateTime instanceof Date) {
9
+ return dateTime.getTime()
10
+ }
11
+
12
+ dateTime = dateTime.replace(/(-|\.\d{3})/g, '') // Allow toISOString() date-time format
13
+ const date = utcParse('%Y%m%dT%H:%M:%SZ')(dateTime)
14
+ if (date === null) {
15
+ throw new RangeError(`unable to parse XAPI datetime ${JSON.stringify(dateTime)}`)
16
+ }
17
+ return date.getTime()
18
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xen-orchestra/web-core",
3
3
  "type": "module",
4
- "version": "0.17.0",
4
+ "version": "0.19.0",
5
5
  "private": false,
6
6
  "exports": {
7
7
  "./*": {