@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
|
@@ -6,9 +6,9 @@ The `useCollection` composable helps you manage a collection of items with flags
|
|
|
6
6
|
|
|
7
7
|
```typescript
|
|
8
8
|
const { items, useSubset, useFlag } = useCollection(sources, {
|
|
9
|
+
itemId: source => source.theId, // Required only if TSource doesn't have an `id` property
|
|
9
10
|
flags: ['selected', 'active', { highlighted: { multiple: false } }],
|
|
10
11
|
properties: source => ({
|
|
11
|
-
id: source.theId, // Required if TSource doesn't have an `id` property
|
|
12
12
|
isAvailable: source.status === 'available',
|
|
13
13
|
fullName: `${source.firstName} ${source.lastName}`,
|
|
14
14
|
}),
|
|
@@ -18,8 +18,8 @@ const { items, useSubset, useFlag } = useCollection(sources, {
|
|
|
18
18
|
## Core Concepts
|
|
19
19
|
|
|
20
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**:
|
|
22
|
-
- **Properties**: Additional custom values
|
|
21
|
+
- **Flags**: Arbitrary `boolean` states attached to items (like `selected`, `active`, `highlighted`, etc.)
|
|
22
|
+
- **Properties**: Additional custom values attached to items, computed from the source object (like `fullName`, `isAvailable`, etc.)
|
|
23
23
|
|
|
24
24
|
## `useCollection` parameters
|
|
25
25
|
|
|
@@ -30,21 +30,22 @@ const { items, useSubset, useFlag } = useCollection(sources, {
|
|
|
30
30
|
|
|
31
31
|
### `options` object
|
|
32
32
|
|
|
33
|
-
| Name | Type | Required | Description
|
|
34
|
-
| ------------ | ---------------------------------------------- | :------: |
|
|
35
|
-
| `
|
|
36
|
-
| `
|
|
33
|
+
| Name | Type | Required | Description |
|
|
34
|
+
| ------------ | ---------------------------------------------- | :------: | ----------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `itemId` | `keyof TSource \| ((source: TSource) => TId)` | ~ | Function to retrieve the item ID or property of TSource (if not provided, `TSource.id` is used) |
|
|
36
|
+
| `flags` | `FlagsConfig<TFlag>` | | Flags that can be applied to items in the collection |
|
|
37
|
+
| `properties` | `(source: TSource) => Record<string, unknown>` | | Function that returns additional properties for each item (see below) |
|
|
37
38
|
|
|
38
39
|
### Item ID
|
|
39
40
|
|
|
40
41
|
The item ID will be retrieved automatically from `TSource.id`
|
|
41
42
|
|
|
42
|
-
If `TSource` doesn't provide an `id`, then `options.
|
|
43
|
+
If `TSource` doesn't provide an `id`, then `options.itemId` will be required.
|
|
43
44
|
|
|
44
45
|
### `FlagsConfig` type
|
|
45
46
|
|
|
46
47
|
```typescript
|
|
47
|
-
type FlagsConfig
|
|
48
|
+
type FlagsConfig = string[] | Record<string, { multiple?: MaybeRef<boolean> }>
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
Values for `multiple`:
|
|
@@ -63,13 +64,13 @@ Values for `multiple`:
|
|
|
63
64
|
|
|
64
65
|
### `CollectionItem` object
|
|
65
66
|
|
|
66
|
-
| Name | Type
|
|
67
|
-
| ------------ |
|
|
68
|
-
| `id` | `TId`
|
|
69
|
-
| `source` | `TSource`
|
|
70
|
-
| `flags` | `Record<TFlag, boolean>`
|
|
71
|
-
| `properties` | `TProperties`
|
|
72
|
-
| `toggleFlag` | `(flag,
|
|
67
|
+
| Name | Type | Description |
|
|
68
|
+
| ------------ | ---------------------------------- | --------------------------------------------------------- |
|
|
69
|
+
| `id` | `TId` | Unique identifier for the item (string, number or symbol) |
|
|
70
|
+
| `source` | `TSource` | The original source object |
|
|
71
|
+
| `flags` | `Record<TFlag, boolean>` | Object containing the state of all flags for this item |
|
|
72
|
+
| `properties` | `TProperties` | Object containing all computed properties for this item |
|
|
73
|
+
| `toggleFlag` | `(flag, shouldBeFlagged?) => void` | Method to toggle a flag on this item |
|
|
73
74
|
|
|
74
75
|
### UseFlagReturn object
|
|
75
76
|
|
|
@@ -81,8 +82,8 @@ Values for `multiple`:
|
|
|
81
82
|
| `areAllOn` | `ComputedRef<boolean>` | Whether all items in the collection have this flag set |
|
|
82
83
|
| `areSomeOn` | `ComputedRef<boolean>` | Whether at least one item has this flag set |
|
|
83
84
|
| `areNoneOn` | `ComputedRef<boolean>` | Whether no items have this flag set |
|
|
84
|
-
| `toggle` | `(id,
|
|
85
|
-
| `toggleAll` | `(
|
|
85
|
+
| `toggle` | `(id, shouldBeFlagged?) => void` | Toggle this flag on a specific item |
|
|
86
|
+
| `toggleAll` | `(shouldBeFlagged?) => void` | Toggle this flag on all items in the collection |
|
|
86
87
|
| `useSubset` | `(filter: (item) => boolean) => Collection` | Creates a sub collection matching the filter |
|
|
87
88
|
|
|
88
89
|
## Flag Operations
|
|
@@ -169,4 +170,8 @@ const { items: users, useSubset } = useCollection(rawUsers, {
|
|
|
169
170
|
const { items: admins, useSubset: useAdminSubset } = useSubset(item => item.source.group === 'admin')
|
|
170
171
|
|
|
171
172
|
const { items: activeAdmins } = useAdminSubset(item => item.source.status === 'active')
|
|
173
|
+
|
|
174
|
+
// This would be equivalent to:
|
|
175
|
+
|
|
176
|
+
const { items: activeAdmins } = useSubset(item => item.source.group === 'admin' && item.source.status === 'active')
|
|
172
177
|
```
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
+
import { createUseSubset } from '@core/packages/collection/create-use-subset.ts'
|
|
1
2
|
import type {
|
|
2
3
|
Collection,
|
|
3
|
-
CollectionConfigProperties,
|
|
4
4
|
CollectionItem,
|
|
5
|
+
CollectionItemId,
|
|
6
|
+
CollectionItemProperties,
|
|
5
7
|
FlagRegistry,
|
|
6
|
-
GuessItemId,
|
|
7
8
|
UseFlagReturn,
|
|
8
|
-
} from '
|
|
9
|
+
} from './types.ts'
|
|
9
10
|
import { useArrayFilter, useArrayMap } from '@vueuse/core'
|
|
10
11
|
import { computed, type ComputedRef } from 'vue'
|
|
11
12
|
|
|
12
|
-
export function createCollection<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export function createCollection<
|
|
14
|
+
TSource,
|
|
15
|
+
TFlag extends string,
|
|
16
|
+
TProperties extends CollectionItemProperties,
|
|
17
|
+
TId extends CollectionItemId,
|
|
18
|
+
$TItem extends CollectionItem<TSource, TFlag, TProperties, TId>,
|
|
19
|
+
>(items: ComputedRef<$TItem[]>, flagRegistry: FlagRegistry<TId, TFlag>): Collection<TSource, TFlag, TProperties, TId> {
|
|
20
|
+
function useFlag(flag: TFlag): UseFlagReturn<TSource, TFlag, TProperties, TId> {
|
|
17
21
|
flagRegistry.assertFlag(flag)
|
|
18
22
|
|
|
19
23
|
const flaggedItems = useArrayFilter(items, item => item.flags[flag])
|
|
@@ -28,21 +32,17 @@ export function createCollection<TSource, TFlag extends string, TProperties exte
|
|
|
28
32
|
|
|
29
33
|
const areNoneOn = computed(() => count.value === 0)
|
|
30
34
|
|
|
31
|
-
function toggle(id:
|
|
32
|
-
flagRegistry.toggleFlag(id, flag,
|
|
35
|
+
function toggle(id: TId, shouldBeFlagged?: boolean) {
|
|
36
|
+
flagRegistry.toggleFlag(id, flag, shouldBeFlagged)
|
|
33
37
|
}
|
|
34
38
|
|
|
35
|
-
function toggleAll(
|
|
39
|
+
function toggleAll(shouldBeFlagged = !areAllOn.value) {
|
|
36
40
|
for (const item of items.value) {
|
|
37
|
-
flagRegistry.toggleFlag(item.id, flag,
|
|
41
|
+
flagRegistry.toggleFlag(item.id, flag, shouldBeFlagged)
|
|
38
42
|
}
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
filter: (item: CollectionItem<TSource, TFlag, TProperties>) => boolean
|
|
43
|
-
): Collection<TSource, TFlag, TProperties> {
|
|
44
|
-
return createCollection(useArrayFilter(flaggedItems, filter), flagRegistry)
|
|
45
|
-
}
|
|
45
|
+
const useSubset = createUseSubset<TSource, TFlag, TProperties, TId>(flaggedItems, flagRegistry)
|
|
46
46
|
|
|
47
47
|
return {
|
|
48
48
|
items: flaggedItems,
|
|
@@ -57,18 +57,19 @@ export function createCollection<TSource, TFlag extends string, TProperties exte
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
): Collection<TSource, TFlag, TProperties> {
|
|
63
|
-
return createCollection(useArrayFilter(items, filter), flagRegistry)
|
|
60
|
+
function toggleFlag(id: TId, flag: TFlag, shouldBeFlagged?: boolean) {
|
|
61
|
+
flagRegistry.toggleFlag(id, flag, shouldBeFlagged)
|
|
64
62
|
}
|
|
65
63
|
|
|
64
|
+
const useSubset = createUseSubset<TSource, TFlag, TProperties, TId>(items, flagRegistry)
|
|
65
|
+
|
|
66
66
|
const count = computed(() => items.value.length)
|
|
67
67
|
|
|
68
68
|
return {
|
|
69
69
|
items,
|
|
70
70
|
count,
|
|
71
71
|
useFlag,
|
|
72
|
+
toggleFlag,
|
|
72
73
|
useSubset,
|
|
73
74
|
}
|
|
74
75
|
}
|
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { CollectionConfigProperties, CollectionItem, FlagRegistry } from '@core/packages/collection/types.ts'
|
|
1
|
+
import type { CollectionItem, CollectionItemId, CollectionItemProperties, FlagRegistry } from './types.ts'
|
|
3
2
|
import { reactive } from 'vue'
|
|
4
3
|
|
|
5
|
-
export function createItem<
|
|
4
|
+
export function createItem<
|
|
5
|
+
TSource,
|
|
6
|
+
TFlag extends string,
|
|
7
|
+
TProperties extends CollectionItemProperties,
|
|
8
|
+
TId extends CollectionItemId,
|
|
9
|
+
>(
|
|
10
|
+
id: TId,
|
|
6
11
|
source: TSource,
|
|
7
|
-
|
|
8
|
-
flagRegistry: FlagRegistry<TFlag>
|
|
9
|
-
): CollectionItem<TSource, TFlag, TProperties> {
|
|
10
|
-
const properties = reactive(getProperties?.(source) ?? ({} as TProperties))
|
|
11
|
-
|
|
12
|
-
const id = guessItemId(source, properties)
|
|
13
|
-
|
|
12
|
+
properties: TProperties,
|
|
13
|
+
flagRegistry: FlagRegistry<TId, TFlag>
|
|
14
|
+
): CollectionItem<TSource, TFlag, TProperties, TId> {
|
|
14
15
|
return {
|
|
15
16
|
id,
|
|
16
17
|
source,
|
|
17
|
-
toggleFlag(flag: TFlag,
|
|
18
|
-
flagRegistry.toggleFlag(id, flag,
|
|
18
|
+
toggleFlag(flag: TFlag, shouldBeFlagged?: boolean) {
|
|
19
|
+
flagRegistry.toggleFlag(id, flag, shouldBeFlagged)
|
|
19
20
|
},
|
|
20
21
|
flags: new Proxy({} as Record<TFlag, boolean>, {
|
|
21
|
-
has(_, flag
|
|
22
|
-
return flagRegistry.isFlagDefined(flag)
|
|
22
|
+
has(_, flag) {
|
|
23
|
+
return flagRegistry.isFlagDefined(flag as TFlag)
|
|
23
24
|
},
|
|
24
|
-
get(_, flag
|
|
25
|
-
if (
|
|
25
|
+
get(_, flag) {
|
|
26
|
+
if (typeof flag === 'symbol' || flag.startsWith('__v_')) {
|
|
26
27
|
return undefined
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
return flagRegistry.isFlagged(id, flag)
|
|
30
|
+
return flagRegistry.isFlagged(id, flag as TFlag)
|
|
30
31
|
},
|
|
31
|
-
set(_, flag
|
|
32
|
-
flagRegistry.toggleFlag(id, flag, value)
|
|
32
|
+
set(_, flag, value: boolean) {
|
|
33
|
+
flagRegistry.toggleFlag(id, flag as TFlag, value)
|
|
33
34
|
|
|
34
35
|
return true
|
|
35
36
|
},
|
|
36
37
|
}),
|
|
37
|
-
properties,
|
|
38
|
+
properties: reactive(properties),
|
|
38
39
|
}
|
|
39
40
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createCollection } from '@core/packages/collection/create-collection.ts'
|
|
2
|
+
import type {
|
|
3
|
+
Collection,
|
|
4
|
+
CollectionItem,
|
|
5
|
+
CollectionItemId,
|
|
6
|
+
CollectionItemProperties,
|
|
7
|
+
FlagRegistry,
|
|
8
|
+
} from '@core/packages/collection/types.ts'
|
|
9
|
+
import type { ArrayFilterPredicate } from '@core/types/utility.type.ts'
|
|
10
|
+
import { useArrayFilter } from '@vueuse/core'
|
|
11
|
+
import type { ComputedRef } from 'vue'
|
|
12
|
+
|
|
13
|
+
export function createUseSubset<
|
|
14
|
+
TSource,
|
|
15
|
+
TFlag extends string,
|
|
16
|
+
TProperties extends CollectionItemProperties,
|
|
17
|
+
TId extends CollectionItemId,
|
|
18
|
+
$TItem extends CollectionItem<TSource, TFlag, TProperties, TId> = CollectionItem<TSource, TFlag, TProperties, TId>,
|
|
19
|
+
>(items: ComputedRef<$TItem[]>, flagRegistry: FlagRegistry<TId, TFlag>) {
|
|
20
|
+
return function useSubset(filter: ArrayFilterPredicate<$TItem>): Collection<TSource, TFlag, TProperties, TId> {
|
|
21
|
+
return createCollection(useArrayFilter(items, filter), flagRegistry)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,26 +1,36 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { hasObjectProperty } from '@core/utils/object.util.ts'
|
|
2
|
+
import type { CollectionItemId, GetItemId } from './types.ts'
|
|
2
3
|
|
|
3
|
-
function
|
|
4
|
-
const
|
|
4
|
+
export function guessItemId<TSource>(source: TSource, getter: GetItemId<TSource>): CollectionItemId {
|
|
5
|
+
const id = extractItemId(source, getter)
|
|
5
6
|
|
|
6
|
-
if (
|
|
7
|
-
|
|
7
|
+
if (isCollectionId(id)) {
|
|
8
|
+
return id
|
|
8
9
|
}
|
|
10
|
+
|
|
11
|
+
throw new Error(`Unable to guess id from source: ${JSON.stringify(source)}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isCollectionId(value: unknown): value is CollectionItemId {
|
|
15
|
+
return typeof value === 'string' || typeof value === 'number'
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
function extractItemId<TSource>(source: TSource, getter: GetItemId<TSource>) {
|
|
19
|
+
if (getter === undefined) {
|
|
20
|
+
if (hasObjectProperty(source, 'id')) {
|
|
21
|
+
return source.id
|
|
22
|
+
}
|
|
16
23
|
|
|
17
|
-
|
|
18
|
-
id = properties.id
|
|
19
|
-
} else if (typeof source === 'object' && source !== null && 'id' in source) {
|
|
20
|
-
id = source.id
|
|
24
|
+
return source
|
|
21
25
|
}
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
if (typeof getter === 'function') {
|
|
28
|
+
return getter(source)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (hasObjectProperty(source, getter)) {
|
|
32
|
+
return source[getter]
|
|
33
|
+
}
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
throw new Error(`Property "${String(getter)}" not found in source: ${JSON.stringify(source)}`)
|
|
26
36
|
}
|
|
@@ -1,57 +1,85 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export type CollectionConfigProperties = Record<string, unknown> & { id?: unknown }
|
|
4
|
-
|
|
5
|
-
export type CollectionConfigFlags<TFlag extends string> = TFlag[] | Record<TFlag, { multiple?: boolean }>
|
|
1
|
+
import type { ArrayFilterPredicate, KeyOfByValue } from '@core/types/utility.type.ts'
|
|
2
|
+
import type { ComputedRef, MaybeRefOrGetter, Reactive } from 'vue'
|
|
6
3
|
|
|
7
4
|
export type CollectionItem<
|
|
8
|
-
TSource,
|
|
9
|
-
TFlag extends string =
|
|
10
|
-
TProperties extends
|
|
5
|
+
TSource = unknown,
|
|
6
|
+
TFlag extends string = string,
|
|
7
|
+
TProperties extends CollectionItemProperties = CollectionItemProperties,
|
|
8
|
+
TId extends CollectionItemId = PickSourceId<TSource, 'id'>,
|
|
11
9
|
> = {
|
|
12
|
-
id:
|
|
10
|
+
id: TId
|
|
13
11
|
source: TSource
|
|
14
12
|
flags: Record<TFlag, boolean>
|
|
15
13
|
properties: Reactive<TProperties>
|
|
16
|
-
toggleFlag: (flag: TFlag,
|
|
14
|
+
toggleFlag: (flag: TFlag, shouldBeFlagged?: boolean) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type Collection<
|
|
18
|
+
TSource = unknown,
|
|
19
|
+
TFlag extends string = string,
|
|
20
|
+
TProperties extends CollectionItemProperties = CollectionItemProperties,
|
|
21
|
+
TId extends CollectionItemId = PickSourceId<TSource, 'id'>,
|
|
22
|
+
$TItem extends CollectionItem<TSource, TFlag, TProperties, TId> = CollectionItem<TSource, TFlag, TProperties, TId>,
|
|
23
|
+
> = {
|
|
24
|
+
items: ComputedRef<$TItem[]>
|
|
25
|
+
count: ComputedRef<number>
|
|
26
|
+
useFlag: (flag: TFlag) => UseFlagReturn<TSource, TFlag, TProperties, TId>
|
|
27
|
+
toggleFlag: (id: TId, flag: TFlag, shouldBeFlagged?: boolean) => void
|
|
28
|
+
useSubset: (filter: ArrayFilterPredicate<$TItem>) => Collection<TSource, TFlag, TProperties, TId>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type CollectionItemProperties = Record<PropertyKey, unknown>
|
|
32
|
+
|
|
33
|
+
export type CollectionItemId = string | number
|
|
34
|
+
|
|
35
|
+
export type PickSourceId<TSource, TKey> = TKey extends keyof TSource
|
|
36
|
+
? TSource[TKey] extends CollectionItemId
|
|
37
|
+
? TSource[TKey]
|
|
38
|
+
: never
|
|
39
|
+
: never
|
|
40
|
+
|
|
41
|
+
export type ExtractSourceId<TSource, TGetId extends GetItemId<TSource>> = TGetId extends keyof TSource
|
|
42
|
+
? PickSourceId<TSource, TGetId>
|
|
43
|
+
: TGetId extends (source: TSource) => infer R
|
|
44
|
+
? R
|
|
45
|
+
: TSource extends CollectionItemId
|
|
46
|
+
? TSource
|
|
47
|
+
: PickSourceId<TSource, 'id'>
|
|
48
|
+
|
|
49
|
+
export type FlagConfig = {
|
|
50
|
+
multiple?: MaybeRefOrGetter<boolean>
|
|
17
51
|
}
|
|
18
52
|
|
|
19
|
-
export type
|
|
20
|
-
|
|
53
|
+
export type CollectionConfigFlags<TFlag extends string> = TFlag[] | Record<TFlag, FlagConfig>
|
|
54
|
+
|
|
55
|
+
export type FlagRegistry<TId extends CollectionItemId, TFlag extends string> = {
|
|
56
|
+
isFlagged: (id: TId, flag: TFlag) => boolean
|
|
21
57
|
isFlagDefined: (flag: TFlag) => boolean
|
|
22
|
-
toggleFlag: (id:
|
|
58
|
+
toggleFlag: (id: TId, flag: TFlag, shouldBeFlagged?: boolean) => void
|
|
23
59
|
clearFlag: (flag: TFlag) => void
|
|
24
60
|
isMultipleAllowed: (flag: TFlag) => boolean
|
|
25
61
|
assertFlag: (flag: TFlag) => void
|
|
26
62
|
}
|
|
27
63
|
|
|
28
|
-
export type UseFlagReturn<
|
|
29
|
-
|
|
30
|
-
|
|
64
|
+
export type UseFlagReturn<
|
|
65
|
+
TSource,
|
|
66
|
+
TFlag extends string,
|
|
67
|
+
TProperties extends CollectionItemProperties,
|
|
68
|
+
TId extends CollectionItemId,
|
|
69
|
+
$TItem extends CollectionItem<TSource, TFlag, TProperties, TId> = CollectionItem<TSource, TFlag, TProperties, TId>,
|
|
70
|
+
> = {
|
|
71
|
+
items: ComputedRef<$TItem[]>
|
|
72
|
+
ids: ComputedRef<TId[]>
|
|
31
73
|
count: ComputedRef<number>
|
|
32
74
|
areAllOn: ComputedRef<boolean>
|
|
33
75
|
areSomeOn: ComputedRef<boolean>
|
|
34
76
|
areNoneOn: ComputedRef<boolean>
|
|
35
|
-
toggle: (id:
|
|
36
|
-
toggleAll: (
|
|
37
|
-
useSubset: (
|
|
38
|
-
filter: (item: CollectionItem<TSource, TFlag, TProperties>) => boolean
|
|
39
|
-
) => Collection<TSource, TFlag, TProperties>
|
|
77
|
+
toggle: (id: TId, shouldBeFlagged?: boolean) => void
|
|
78
|
+
toggleAll: (shouldBeFlagged?: boolean) => void
|
|
79
|
+
useSubset: (filter: ArrayFilterPredicate<$TItem>) => Collection<TSource, TFlag, TProperties, TId>
|
|
40
80
|
}
|
|
41
81
|
|
|
42
|
-
export type
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
useSubset: (
|
|
47
|
-
filter: (item: CollectionItem<TSource, TFlag, TProperties>) => boolean
|
|
48
|
-
) => Collection<TSource, TFlag, TProperties>
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type AssertId<TId> = TId extends PropertyKey ? TId : never
|
|
52
|
-
|
|
53
|
-
export type GuessItemId<TSource, TProperties> = TProperties extends { id: infer TId }
|
|
54
|
-
? AssertId<TId>
|
|
55
|
-
: TSource extends { id: infer TId }
|
|
56
|
-
? AssertId<TId>
|
|
57
|
-
: never
|
|
82
|
+
export type GetItemId<TSource> =
|
|
83
|
+
| undefined
|
|
84
|
+
| KeyOfByValue<TSource, CollectionItemId>
|
|
85
|
+
| ((source: TSource) => CollectionItemId)
|
|
@@ -1,47 +1,97 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
1
|
+
import { guessItemId } from '@core/packages/collection/guess-item-id.ts'
|
|
2
|
+
import type { EmptyObject } from '@core/types/utility.type.ts'
|
|
3
|
+
import type {
|
|
4
|
+
Collection,
|
|
5
|
+
CollectionConfigFlags,
|
|
6
|
+
CollectionItemId,
|
|
7
|
+
CollectionItemProperties,
|
|
8
|
+
ExtractSourceId,
|
|
9
|
+
GetItemId,
|
|
10
|
+
} from './types.ts'
|
|
5
11
|
import { computed, type MaybeRefOrGetter, toValue } from 'vue'
|
|
12
|
+
import { createCollection } from './create-collection.ts'
|
|
13
|
+
import { createItem } from './create-item.ts'
|
|
14
|
+
import { useFlagRegistry } from './use-flag-registry.ts'
|
|
15
|
+
|
|
16
|
+
// Overload #1: Source is CollectionItemId
|
|
17
|
+
|
|
18
|
+
export function useCollection<
|
|
19
|
+
TSource extends CollectionItemId,
|
|
20
|
+
TFlag extends string = never,
|
|
21
|
+
TProperties extends CollectionItemProperties = EmptyObject,
|
|
22
|
+
TGetId extends ((source: TSource) => CollectionItemId) | undefined = undefined,
|
|
23
|
+
$TId extends CollectionItemId = ExtractSourceId<TSource, TGetId>,
|
|
24
|
+
>(
|
|
25
|
+
source: MaybeRefOrGetter<TSource[]>,
|
|
26
|
+
config?: {
|
|
27
|
+
itemId?: TGetId | ((source: TSource) => CollectionItemId)
|
|
28
|
+
flags?: CollectionConfigFlags<TFlag>
|
|
29
|
+
properties?: (source: TSource) => TProperties
|
|
30
|
+
}
|
|
31
|
+
): Collection<TSource, TFlag, TProperties, $TId>
|
|
32
|
+
|
|
33
|
+
// Overload #2: Source is an object with id
|
|
6
34
|
|
|
7
35
|
export function useCollection<
|
|
8
|
-
TSource extends { id:
|
|
36
|
+
TSource extends { id: CollectionItemId },
|
|
9
37
|
TFlag extends string = never,
|
|
10
|
-
TProperties extends
|
|
38
|
+
TProperties extends CollectionItemProperties = EmptyObject,
|
|
39
|
+
TGetId extends GetItemId<TSource> = undefined,
|
|
40
|
+
$TId extends CollectionItemId = ExtractSourceId<TSource, TGetId>,
|
|
11
41
|
>(
|
|
12
|
-
|
|
42
|
+
source: MaybeRefOrGetter<TSource[]>,
|
|
13
43
|
config?: {
|
|
44
|
+
itemId?: TGetId | ((source: TSource) => CollectionItemId)
|
|
14
45
|
flags?: CollectionConfigFlags<TFlag>
|
|
15
46
|
properties?: (source: TSource) => TProperties
|
|
16
47
|
}
|
|
17
|
-
): Collection<TSource, TFlag, TProperties>
|
|
48
|
+
): Collection<TSource, TFlag, TProperties, $TId>
|
|
49
|
+
|
|
50
|
+
// Overload #3: Any other case
|
|
18
51
|
|
|
19
52
|
export function useCollection<
|
|
20
53
|
TSource,
|
|
21
54
|
TFlag extends string = never,
|
|
22
|
-
TProperties extends
|
|
55
|
+
TProperties extends CollectionItemProperties = EmptyObject,
|
|
56
|
+
TGetId extends GetItemId<TSource> = never,
|
|
57
|
+
$TId extends CollectionItemId = ExtractSourceId<TSource, TGetId>,
|
|
23
58
|
>(
|
|
24
|
-
|
|
59
|
+
source: MaybeRefOrGetter<TSource[]>,
|
|
25
60
|
config: {
|
|
61
|
+
itemId: TGetId | ((source: TSource) => CollectionItemId)
|
|
26
62
|
flags?: CollectionConfigFlags<TFlag>
|
|
27
|
-
properties
|
|
63
|
+
properties?: (source: TSource) => TProperties
|
|
28
64
|
}
|
|
29
|
-
): Collection<TSource, TFlag, TProperties>
|
|
65
|
+
): Collection<TSource, TFlag, TProperties, $TId>
|
|
66
|
+
|
|
67
|
+
// Implementation
|
|
30
68
|
|
|
31
69
|
export function useCollection<
|
|
32
70
|
TSource,
|
|
33
|
-
TFlag extends string
|
|
34
|
-
TProperties extends
|
|
71
|
+
TFlag extends string,
|
|
72
|
+
TProperties extends CollectionItemProperties,
|
|
73
|
+
TGetId extends GetItemId<TSource>,
|
|
74
|
+
$TId extends CollectionItemId,
|
|
35
75
|
>(
|
|
36
|
-
|
|
76
|
+
_sources: MaybeRefOrGetter<TSource[]>,
|
|
37
77
|
config?: {
|
|
78
|
+
itemId?: TGetId
|
|
38
79
|
flags?: CollectionConfigFlags<TFlag>
|
|
39
80
|
properties?: (source: TSource) => TProperties
|
|
40
81
|
}
|
|
41
|
-
): Collection<TSource, TFlag, TProperties> {
|
|
42
|
-
const flagRegistry = useFlagRegistry(config?.flags)
|
|
82
|
+
): Collection<TSource, TFlag, TProperties, $TId> {
|
|
83
|
+
const flagRegistry = useFlagRegistry<TFlag, $TId>(config?.flags)
|
|
84
|
+
|
|
85
|
+
const sources = computed(() => toValue(_sources))
|
|
86
|
+
|
|
87
|
+
const items = computed(() =>
|
|
88
|
+
sources.value.map(source => {
|
|
89
|
+
const id = guessItemId(source, config?.itemId) as $TId
|
|
90
|
+
const properties = config?.properties?.(source) ?? ({} as TProperties)
|
|
43
91
|
|
|
44
|
-
|
|
92
|
+
return createItem<TSource, TFlag, TProperties, $TId>(id, source, properties, flagRegistry)
|
|
93
|
+
})
|
|
94
|
+
)
|
|
45
95
|
|
|
46
96
|
return createCollection(items, flagRegistry)
|
|
47
97
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { objectFromEntries, objectEntries } from '@core/utils/object.util.ts'
|
|
2
|
+
import type { CollectionConfigFlags, CollectionItemId, FlagConfig, FlagRegistry } from './types.ts'
|
|
3
|
+
import { isRef, reactive, toValue, watch } from 'vue'
|
|
3
4
|
|
|
4
|
-
export function useFlagRegistry<TFlag extends string>(
|
|
5
|
+
export function useFlagRegistry<TFlag extends string, TId extends CollectionItemId>(
|
|
5
6
|
config: CollectionConfigFlags<TFlag> = [] as TFlag[]
|
|
6
|
-
): FlagRegistry<TFlag> {
|
|
7
|
-
const registry = reactive(new Map<TFlag, Set<
|
|
7
|
+
): FlagRegistry<TId, TFlag> {
|
|
8
|
+
const registry = reactive(new Map()) as Map<TFlag, Set<TId>>
|
|
8
9
|
|
|
9
|
-
const flags = Array.isArray(config)
|
|
10
|
+
const flags = Array.isArray(config)
|
|
11
|
+
? objectFromEntries(config.map(flag => [flag, { multiple: true } as FlagConfig]))
|
|
12
|
+
: config
|
|
13
|
+
|
|
14
|
+
for (const [flag, { multiple }] of objectEntries(flags)) {
|
|
15
|
+
if (isRef(multiple)) {
|
|
16
|
+
watch(multiple, () => clearFlag(flag))
|
|
17
|
+
}
|
|
18
|
+
}
|
|
10
19
|
|
|
11
20
|
function isFlagDefined(flag: TFlag) {
|
|
12
21
|
return Object.prototype.hasOwnProperty.call(flags, flag)
|
|
@@ -18,39 +27,51 @@ export function useFlagRegistry<TFlag extends string>(
|
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
|
|
21
|
-
function isFlagged(id:
|
|
30
|
+
function isFlagged(id: TId, flag: TFlag) {
|
|
22
31
|
assertFlag(flag)
|
|
23
32
|
|
|
24
33
|
return registry.get(flag)?.has(id) ?? false
|
|
25
34
|
}
|
|
26
35
|
|
|
27
|
-
function toggleFlag(id:
|
|
36
|
+
function toggleFlag(id: TId, flag: TFlag, shouldBeFlagged = !isFlagged(id, flag)) {
|
|
28
37
|
assertFlag(flag)
|
|
29
38
|
|
|
30
39
|
if (!registry.has(flag)) {
|
|
31
40
|
registry.set(flag, new Set())
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const flagSet = registry.get(flag)!
|
|
44
|
+
|
|
45
|
+
if (shouldBeFlagged === flagSet.has(id)) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!shouldBeFlagged) {
|
|
50
|
+
flagSet.delete(id)
|
|
51
|
+
return
|
|
41
52
|
}
|
|
53
|
+
|
|
54
|
+
if (!isMultipleAllowed(flag) && flagSet.size > 0) {
|
|
55
|
+
clearFlag(flag)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
flagSet.add(id)
|
|
42
59
|
}
|
|
43
60
|
|
|
44
61
|
function clearFlag(flag: TFlag) {
|
|
45
62
|
assertFlag(flag)
|
|
46
63
|
|
|
47
|
-
registry.
|
|
64
|
+
if (!registry.has(flag) || registry.get(flag)!.size === 0) {
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
registry.get(flag)!.clear()
|
|
48
69
|
}
|
|
49
70
|
|
|
50
71
|
function isMultipleAllowed(flag: TFlag) {
|
|
51
72
|
assertFlag(flag)
|
|
52
73
|
|
|
53
|
-
return flags[flag]?.multiple ??
|
|
74
|
+
return toValue(flags[flag]?.multiple) ?? true
|
|
54
75
|
}
|
|
55
76
|
|
|
56
77
|
return {
|