@vc-shell/framework 1.1.24 → 1.1.26
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/CHANGELOG.md +15 -0
- package/core/composables/useAppInsights/index.ts +2 -2
- package/core/composables/useErrorHandler/index.ts +3 -2
- package/core/composables/useGlobalSearch/useGlobalSearch.md +263 -0
- package/core/composables/usePermissions/index.ts +2 -2
- package/core/composables/useTheme/index.ts +74 -26
- package/core/composables/useUser/index.ts +29 -8
- package/core/composables/useUserManagement/index.ts +45 -0
- package/core/interceptors/index.ts +2 -2
- package/core/plugins/signalR/index.ts +2 -2
- package/core/services/global-search-service/global-search-service.md +203 -0
- package/core/services/widget-service.ts +35 -12
- package/dist/core/composables/useErrorHandler/index.d.ts.map +1 -1
- package/dist/core/composables/useTheme/index.d.ts +14 -5
- package/dist/core/composables/useTheme/index.d.ts.map +1 -1
- package/dist/core/composables/useUser/index.d.ts +11 -3
- package/dist/core/composables/useUser/index.d.ts.map +1 -1
- package/dist/core/composables/useUserManagement/index.d.ts +23 -0
- package/dist/core/composables/useUserManagement/index.d.ts.map +1 -0
- package/dist/core/services/widget-service.d.ts +9 -7
- package/dist/core/services/widget-service.d.ts.map +1 -1
- package/dist/framework.js +94 -93
- package/dist/{index-DvenBxy6.js → index-20xYwcGS.js} +22032 -21961
- package/dist/{index-Br7ZwtRW.js → index-3B7GY2EI.js} +1 -1
- package/dist/{index-DAnceKLv.js → index-BGUwsXYM.js} +1 -1
- package/dist/{index-eOG-NNYN.js → index-BGghog2f.js} +1 -1
- package/dist/{index-Cxkjjuah.js → index-BQNK41p5.js} +1 -1
- package/dist/{index-CIzLBvgg.js → index-BeIJlP3x.js} +1 -1
- package/dist/{index-BnqqEJTE.js → index-BobFEOd-.js} +1 -1
- package/dist/{index-BYcoxn-f.js → index-CYbdhec2.js} +1 -1
- package/dist/{index-BbuBDu8A.js → index-DQczMBoO.js} +1 -1
- package/dist/{index-cuex9jil.js → index-DSNT0XVw.js} +1 -1
- package/dist/{index-DoArZBIw.js → index-DXHjWa3b.js} +1 -1
- package/dist/{index-Dk1K3-27.js → index-Dcf_1-Il.js} +1 -1
- package/dist/{index-CGL9e-cM.js → index-DszRvG1r.js} +1 -1
- package/dist/{index-CLAYu8Qj.js → index-DyPpTQJI.js} +1 -1
- package/dist/{index-Cmbxdwnl.js → index-Nr1LNRXa.js} +1 -1
- package/dist/{index-CRwMOCjN.js → index-RMOqRXFr.js} +1 -1
- package/dist/{index-BLmjssqE.js → index-Tsyx9GI7.js} +1 -1
- package/dist/index.css +1 -1
- package/dist/locales/de.json +6 -0
- package/dist/locales/en.json +6 -0
- package/dist/shared/components/change-password/change-password.vue.d.ts.map +1 -1
- package/dist/shared/components/notifications/components/notification-container/index.d.ts +1 -1
- package/dist/shared/components/notifications/components/notification-container/index.d.ts.map +1 -1
- package/dist/shared/components/settings-menu/settings-menu.vue.d.ts.map +1 -1
- package/dist/shared/components/settings-menu-item/settings-menu-item.vue.d.ts +6 -11
- package/dist/shared/components/settings-menu-item/settings-menu-item.vue.d.ts.map +1 -1
- package/dist/shared/components/sidebar/sidebar.vue.d.ts +3 -11
- package/dist/shared/components/sidebar/sidebar.vue.d.ts.map +1 -1
- package/dist/shared/components/theme-selector/theme-selector.vue.d.ts.map +1 -1
- package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts.map +1 -1
- package/dist/shared/pages/InvitePage/components/invite/Invite.vue.d.ts.map +1 -1
- package/dist/shared/pages/LoginPage/components/login/Login.vue.d.ts.map +1 -1
- package/dist/shared/pages/ResetPasswordPage/components/reset-password/ResetPassword.vue.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/ui/components/atoms/vc-icon/vc-icon.vue.d.ts.map +1 -1
- package/dist/ui/components/atoms/vc-widget/vc-widget.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-desktop.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-mobile.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-popup/vc-popup.vue.d.ts +1 -1
- package/dist/ui/components/organisms/vc-popup/vc-popup.vue.d.ts.map +1 -1
- package/package.json +4 -4
- package/shared/components/change-password/change-password.vue +2 -3
- package/shared/components/logout-button/logout-button.vue +2 -2
- package/shared/components/settings-menu/settings-menu.vue +1 -4
- package/shared/components/settings-menu-item/settings-menu-item.vue +9 -1
- package/shared/components/sidebar/sidebar.vue +6 -1
- package/shared/components/theme-selector/theme-selector.vue +11 -11
- package/shared/components/user-dropdown-button/_internal/user-info.vue +2 -2
- package/shared/modules/dynamic/pages/dynamic-blade-form.vue +1 -0
- package/shared/pages/InvitePage/components/invite/Invite.vue +217 -216
- package/shared/pages/LoginPage/components/login/Login.vue +3 -2
- package/shared/pages/ResetPasswordPage/components/reset-password/ResetPassword.vue +3 -2
- package/ui/components/atoms/vc-icon/vc-icon.vue +5 -10
- package/ui/components/atoms/vc-widget/vc-widget.vue +13 -6
- package/ui/components/molecules/vc-input-dropdown/vc-input-dropdown.stories.ts +0 -2
- package/ui/components/organisms/vc-app/vc-app.vue +4 -3
- package/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-desktop.vue +2 -1
- package/ui/components/organisms/vc-blade/_internal/vc-blade-widget-container/_internal/vc-widget-container-mobile.vue +2 -0
- package/ui/components/organisms/vc-popup/vc-popup.stories.ts +398 -0
- package/ui/components/organisms/vc-popup/vc-popup.vue +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
## [1.1.26](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.25...v1.1.26) (2025-06-04)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **global-search:** add useGlobalSearch composable and Global Search Service documentation for enhanced search functionality across the application ([e53be10](https://github.com/VirtoCommerce/vc-shell/commit/e53be1095b2b7067dc3ad9ce9f1ab23aafaa57a2))
|
|
7
|
+
* **localization:** add theme localization support for German and English languages ([8d8fdb0](https://github.com/VirtoCommerce/vc-shell/commit/8d8fdb0a05814ea277f7ab07978902ae86ec3102))
|
|
8
|
+
* **vc-popup:** add Storybook stories for VcPopup component, showcasing various configurations and customization options ([dc8e961](https://github.com/VirtoCommerce/vc-shell/commit/dc8e961be32469b7a06fed78aa68846ba95a1d70))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
## [1.1.25](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.24...v1.1.25) (2025-05-30)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
1
16
|
## [1.1.24](https://github.com/VirtoCommerce/vc-shell/compare/v1.1.23...v1.1.24) (2025-05-29)
|
|
2
17
|
|
|
3
18
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { inject } from "vue";
|
|
2
2
|
import { AppInsightsPluginOptions, useAppInsights as useInsights } from "vue3-application-insights";
|
|
3
3
|
import { generateW3CId } from "@microsoft/applicationinsights-core-js";
|
|
4
|
-
import {
|
|
4
|
+
import { useUserManagement } from "./../useUserManagement";
|
|
5
5
|
import { ApplicationInsights, Snippet } from "@microsoft/applicationinsights-web";
|
|
6
6
|
|
|
7
7
|
export interface IUseAppInsights {
|
|
@@ -14,7 +14,7 @@ export interface IUseAppInsights {
|
|
|
14
14
|
|
|
15
15
|
export function useAppInsights(): IUseAppInsights {
|
|
16
16
|
const appInsights = useInsights();
|
|
17
|
-
const { user } =
|
|
17
|
+
const { user } = useUserManagement();
|
|
18
18
|
const appInsightsOptions = inject<AppInsightsPluginOptions>("appInsightsOptions");
|
|
19
19
|
|
|
20
20
|
function setupPageTracking() {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { onErrorCaptured, getCurrentInstance, ref, Ref } from "vue";
|
|
2
|
-
import { useAppInsights
|
|
2
|
+
import { useAppInsights } from "..";
|
|
3
|
+
import { useUserManagement } from "../useUserManagement";
|
|
3
4
|
|
|
4
5
|
interface IUseErrorHandler {
|
|
5
6
|
error: Ref<string | null>;
|
|
@@ -10,7 +11,7 @@ export function useErrorHandler(capture?: boolean): IUseErrorHandler {
|
|
|
10
11
|
const error = ref<string | null>(null);
|
|
11
12
|
const instance = getCurrentInstance();
|
|
12
13
|
const { appInsights } = useAppInsights();
|
|
13
|
-
const { user } =
|
|
14
|
+
const { user } = useUserManagement();
|
|
14
15
|
|
|
15
16
|
function reset() {
|
|
16
17
|
error.value = null;
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# useGlobalSearch Composable
|
|
2
|
+
|
|
3
|
+
The `useGlobalSearch` composable provides access to the Global Search Service, allowing components to manage search functionality across the application, particularly for blade-based interfaces.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `useGlobalSearch` composable is a core utility for managing search visibility and query state in VC-Shell applications. It's particularly useful for controlling search UI in blade components and handling mobile-specific search behavior.
|
|
8
|
+
|
|
9
|
+
## API Reference
|
|
10
|
+
|
|
11
|
+
### Return Value
|
|
12
|
+
|
|
13
|
+
The `useGlobalSearch` composable returns the global search state object with the following properties and methods:
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
interface GlobalSearchState {
|
|
17
|
+
isSearchVisible: Ref<Record<string, boolean>>; // Tracks search visibility by blade ID
|
|
18
|
+
searchQuery: Ref<Record<string, string>>; // Stores search queries by blade ID
|
|
19
|
+
toggleSearch: (bladeId: string) => void; // Toggles search visibility for a blade
|
|
20
|
+
setSearchQuery: (bladeId: string, query: string) => void; // Sets the search query for a blade
|
|
21
|
+
closeSearch: (bladeId: string) => void; // Closes search for a specific blade
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Methods
|
|
26
|
+
|
|
27
|
+
#### toggleSearch
|
|
28
|
+
|
|
29
|
+
Toggles search visibility for a specific blade.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
toggleSearch(bladeId: string): void
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
- `bladeId`: The ID of the blade to toggle search visibility for
|
|
36
|
+
|
|
37
|
+
#### setSearchQuery
|
|
38
|
+
|
|
39
|
+
Sets the search query for a specific blade.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
setSearchQuery(bladeId: string, query: string): void
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
- `bladeId`: The ID of the blade
|
|
46
|
+
- `query`: The search query string
|
|
47
|
+
|
|
48
|
+
#### closeSearch
|
|
49
|
+
|
|
50
|
+
Explicitly closes search for a specific blade.
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
closeSearch(bladeId: string): void
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- `bladeId`: The ID of the blade to close search for
|
|
57
|
+
|
|
58
|
+
### Properties
|
|
59
|
+
|
|
60
|
+
#### isSearchVisible
|
|
61
|
+
|
|
62
|
+
A reactive reference to an object that tracks search visibility state for each blade by ID.
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
isSearchVisible: Ref<Record<string, boolean>>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### searchQuery
|
|
69
|
+
|
|
70
|
+
A reactive reference to an object that stores search queries for each blade by ID.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
searchQuery: Ref<Record<string, string>>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
### Basic Usage in a Component
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { useGlobalSearch } from '@vc-shell/framework';
|
|
82
|
+
import { computed, inject } from 'vue';
|
|
83
|
+
|
|
84
|
+
export default {
|
|
85
|
+
setup() {
|
|
86
|
+
// Get the blade ID (typically injected in blade components)
|
|
87
|
+
const blade = inject('BladeInstance');
|
|
88
|
+
const bladeId = computed(() => blade.value?.id || 'fallback-id');
|
|
89
|
+
|
|
90
|
+
// Get global search functionality
|
|
91
|
+
const globalSearch = useGlobalSearch();
|
|
92
|
+
|
|
93
|
+
// Determine if search should be shown
|
|
94
|
+
const isSearchVisible = computed(() => {
|
|
95
|
+
return globalSearch.isSearchVisible.value[bladeId.value] || false;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Function to toggle search
|
|
99
|
+
function toggleSearch() {
|
|
100
|
+
globalSearch.toggleSearch(bladeId.value);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Function to update search query
|
|
104
|
+
function updateSearchQuery(query) {
|
|
105
|
+
globalSearch.setSearchQuery(bladeId.value, query);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
isSearchVisible,
|
|
110
|
+
toggleSearch,
|
|
111
|
+
updateSearchQuery
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Table Component with Search
|
|
118
|
+
|
|
119
|
+
```vue
|
|
120
|
+
<template>
|
|
121
|
+
<div class="data-table-component">
|
|
122
|
+
<!-- Search Input -->
|
|
123
|
+
<div v-if="shouldShowSearch" class="search-container">
|
|
124
|
+
<input
|
|
125
|
+
type="text"
|
|
126
|
+
v-model="searchInput"
|
|
127
|
+
placeholder="Search..."
|
|
128
|
+
@input="handleSearchInput"
|
|
129
|
+
/>
|
|
130
|
+
</div>
|
|
131
|
+
|
|
132
|
+
<!-- Table Content -->
|
|
133
|
+
<table>
|
|
134
|
+
<!-- Table structure -->
|
|
135
|
+
</table>
|
|
136
|
+
|
|
137
|
+
<!-- Mobile Search Button -->
|
|
138
|
+
<button
|
|
139
|
+
v-if="isMobile"
|
|
140
|
+
class="mobile-search-button"
|
|
141
|
+
@click="toggleSearch"
|
|
142
|
+
>
|
|
143
|
+
Search
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
|
147
|
+
|
|
148
|
+
<script setup>
|
|
149
|
+
import { ref, computed, inject, watch } from 'vue';
|
|
150
|
+
import { useGlobalSearch } from '@vc-shell/framework';
|
|
151
|
+
|
|
152
|
+
// Get mobile state
|
|
153
|
+
const isMobile = inject('isMobile', ref(false));
|
|
154
|
+
|
|
155
|
+
// Get blade and global search
|
|
156
|
+
const blade = inject('BladeInstance');
|
|
157
|
+
const bladeId = computed(() => blade.value?.id || 'fallback-id');
|
|
158
|
+
const globalSearch = useGlobalSearch();
|
|
159
|
+
|
|
160
|
+
// Track search input
|
|
161
|
+
const searchInput = ref('');
|
|
162
|
+
|
|
163
|
+
// Determine if search should be visible
|
|
164
|
+
const shouldShowSearch = computed(() => {
|
|
165
|
+
if (!isMobile.value) return true;
|
|
166
|
+
return globalSearch.isSearchVisible.value[bladeId.value];
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Watch for changes in the global search query
|
|
170
|
+
watch(() => globalSearch.searchQuery.value[bladeId.value], (newQuery) => {
|
|
171
|
+
if (newQuery !== undefined) {
|
|
172
|
+
searchInput.value = newQuery;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Toggle search visibility
|
|
177
|
+
function toggleSearch() {
|
|
178
|
+
globalSearch.toggleSearch(bladeId.value);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Handle search input changes
|
|
182
|
+
function handleSearchInput() {
|
|
183
|
+
globalSearch.setSearchQuery(bladeId.value, searchInput.value);
|
|
184
|
+
// Additional search logic
|
|
185
|
+
}
|
|
186
|
+
</script>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Mobile Search Button Implementation
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { useGlobalSearch } from '@vc-shell/framework';
|
|
193
|
+
import { useAppBarMobileButtons } from '@vc-shell/framework';
|
|
194
|
+
import { computed, onMounted, onBeforeUnmount } from 'vue';
|
|
195
|
+
|
|
196
|
+
export default {
|
|
197
|
+
setup() {
|
|
198
|
+
const bladeId = computed(() => 'your-blade-id');
|
|
199
|
+
const globalSearch = useGlobalSearch();
|
|
200
|
+
const { register: registerMobileButton, unregister: unregisterMobileButton } = useAppBarMobileButtons();
|
|
201
|
+
|
|
202
|
+
// Register a search button in the mobile app bar
|
|
203
|
+
onMounted(() => {
|
|
204
|
+
registerMobileButton({
|
|
205
|
+
id: "global-search",
|
|
206
|
+
icon: "search-icon",
|
|
207
|
+
onClick: () => {
|
|
208
|
+
globalSearch.toggleSearch(bladeId.value);
|
|
209
|
+
},
|
|
210
|
+
onClose: () => {
|
|
211
|
+
globalSearch.closeSearch(bladeId.value);
|
|
212
|
+
},
|
|
213
|
+
isVisible: computed(() => true), // Button visibility logic
|
|
214
|
+
order: 5, // Order in the mobile button list
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Cleanup when component is unmounted
|
|
219
|
+
onBeforeUnmount(() => {
|
|
220
|
+
if (globalSearch.isSearchVisible.value[bladeId.value]) {
|
|
221
|
+
globalSearch.closeSearch(bladeId.value);
|
|
222
|
+
}
|
|
223
|
+
unregisterMobileButton("global-search");
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Provider Function
|
|
230
|
+
|
|
231
|
+
In addition to the `useGlobalSearch` composable, there's a provider function to initialize the global search service in your application:
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
import { provideGlobalSearch } from '@vc-shell/framework';
|
|
235
|
+
|
|
236
|
+
export default {
|
|
237
|
+
setup() {
|
|
238
|
+
// Create and provide the global search service to the component tree
|
|
239
|
+
const globalSearchService = provideGlobalSearch();
|
|
240
|
+
|
|
241
|
+
// Now child components can use useGlobalSearch()
|
|
242
|
+
|
|
243
|
+
return {};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Best Practices
|
|
249
|
+
|
|
250
|
+
1. **Blade-Specific Search**: Always associate search state with specific blade IDs to ensure each blade can have independent search visibility.
|
|
251
|
+
|
|
252
|
+
2. **Mobile Considerations**: Implement different search behavior for mobile versus desktop views, using the isMobile flag.
|
|
253
|
+
|
|
254
|
+
3. **Cleanup**: Always close search and unregister any associated UI elements when components unmount.
|
|
255
|
+
|
|
256
|
+
4. **Search Toggling**: Use the toggleSearch method for buttons that need to both open and close search, rather than implementing toggle logic yourself.
|
|
257
|
+
|
|
258
|
+
5. **Default Visibility**: In desktop views, typically keep search visible by default, only using toggle behavior on mobile.
|
|
259
|
+
|
|
260
|
+
## Related Resources
|
|
261
|
+
|
|
262
|
+
- [Global Search Service](../../services/global-search-service/global-search-service.md) - Documentation for the underlying service
|
|
263
|
+
- [Blade Navigation](../navigation.md) - Information about the blade navigation system
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { Ref, ref } from "vue";
|
|
2
|
-
import {
|
|
2
|
+
import { useUserManagement } from "./../useUserManagement";
|
|
3
3
|
|
|
4
4
|
interface IUsePermissions {
|
|
5
5
|
hasAccess(permissions: string | string[] | undefined): boolean;
|
|
6
6
|
}
|
|
7
7
|
const userPermissions: Ref<string[]> = ref([]);
|
|
8
8
|
export function usePermissions(): IUsePermissions {
|
|
9
|
-
const { user } =
|
|
9
|
+
const { user } = useUserManagement();
|
|
10
10
|
|
|
11
11
|
if (user.value) {
|
|
12
12
|
userPermissions.value = user.value?.permissions ?? [];
|
|
@@ -1,47 +1,67 @@
|
|
|
1
1
|
import { BasicColorSchema, useColorMode, useCycleList } from "@vueuse/core";
|
|
2
2
|
import { computed, watchEffect, ref, type Ref } from "vue";
|
|
3
|
+
import { useI18n } from "vue-i18n";
|
|
4
|
+
import * as _ from "lodash-es";
|
|
5
|
+
import { i18n } from "../../plugins/i18n";
|
|
6
|
+
|
|
7
|
+
export interface ThemeDefinition {
|
|
8
|
+
key: string;
|
|
9
|
+
localizationKey?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DisplayTheme {
|
|
13
|
+
key: string;
|
|
14
|
+
name: string; // localized name
|
|
15
|
+
}
|
|
3
16
|
|
|
4
17
|
export interface IUseTheme {
|
|
5
|
-
themes: Ref<
|
|
6
|
-
|
|
18
|
+
themes: Ref<DisplayTheme[]>;
|
|
19
|
+
currentThemeKey: Ref<string>;
|
|
20
|
+
currentLocalizedName: Ref<string>;
|
|
7
21
|
next: () => void;
|
|
8
|
-
register: (
|
|
9
|
-
unregister: (
|
|
10
|
-
setTheme: (
|
|
22
|
+
register: (themesToAdd: ThemeDefinition | ThemeDefinition[]) => void;
|
|
23
|
+
unregister: (themeKeysToRemove: string | string[]) => void;
|
|
24
|
+
setTheme: (themeKey: string) => void;
|
|
11
25
|
}
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
27
|
+
// Initialize with a default "light" theme, assuming a convention for its localization key.
|
|
28
|
+
const _themeRegistry: Ref<ThemeDefinition[]> = ref([{ key: "light", localizationKey: "CORE.THEMES.LIGHT" }]);
|
|
29
|
+
|
|
16
30
|
export const useTheme = (): IUseTheme => {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
31
|
+
const { t } = i18n.global;
|
|
32
|
+
|
|
33
|
+
function register(themesToAdd: ThemeDefinition | ThemeDefinition[]) {
|
|
34
|
+
(Array.isArray(themesToAdd) ? themesToAdd : [themesToAdd]).forEach((themeDef) => {
|
|
35
|
+
if (!_themeRegistry.value.some((t) => t.key === themeDef.key)) {
|
|
36
|
+
_themeRegistry.value.push(themeDef);
|
|
21
37
|
}
|
|
22
38
|
});
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
function unregister(
|
|
26
|
-
(
|
|
27
|
-
const index =
|
|
41
|
+
function unregister(themeKeysToRemove: string | string[]) {
|
|
42
|
+
(Array.isArray(themeKeysToRemove) ? themeKeysToRemove : [themeKeysToRemove]).forEach((keyToRemove) => {
|
|
43
|
+
const index = _themeRegistry.value.findIndex((t) => t.key === keyToRemove);
|
|
28
44
|
if (index >= 0) {
|
|
29
|
-
|
|
45
|
+
// Ensure we don't unregister the last theme if it's active, or handle active theme change.
|
|
46
|
+
// For simplicity now, just remove. Consider active theme implications if this is an issue.
|
|
47
|
+
// Especially if 'light' is removed and it's the initial/fallback.
|
|
48
|
+
// However, 'light' is added by default and this function is for custom themes.
|
|
49
|
+
_themeRegistry.value.splice(index, 1);
|
|
30
50
|
}
|
|
31
51
|
});
|
|
32
52
|
}
|
|
33
53
|
|
|
34
|
-
|
|
35
|
-
state.value = theme;
|
|
36
|
-
}
|
|
54
|
+
const themeKeys = computed(() => _themeRegistry.value.map((t) => t.key));
|
|
37
55
|
|
|
38
56
|
const mode = useColorMode({
|
|
57
|
+
attribute: "data-theme",
|
|
39
58
|
emitAuto: true,
|
|
40
|
-
initialValue: "light",
|
|
59
|
+
initialValue: "light", // Should align with a default theme in _themeRegistry
|
|
41
60
|
modes: {
|
|
42
|
-
|
|
61
|
+
// Dynamically build modes from registered theme keys
|
|
62
|
+
...themeKeys.value.reduce(
|
|
43
63
|
(acc, name) => {
|
|
44
|
-
acc[name] = name;
|
|
64
|
+
acc[name] = name; // Maps theme key to itself (e.g., "ocean": "ocean")
|
|
45
65
|
return acc;
|
|
46
66
|
},
|
|
47
67
|
{} as Record<string, string>,
|
|
@@ -49,13 +69,41 @@ export const useTheme = (): IUseTheme => {
|
|
|
49
69
|
},
|
|
50
70
|
});
|
|
51
71
|
|
|
52
|
-
const { state, next } = useCycleList(
|
|
72
|
+
const { state, next } = useCycleList(themeKeys, { initialValue: mode });
|
|
73
|
+
|
|
74
|
+
watchEffect(() => {
|
|
75
|
+
if (state.value) {
|
|
76
|
+
// Ensure state.value is not undefined
|
|
77
|
+
mode.value = state.value as BasicColorSchema; // state.value is a theme key
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
function setTheme(themeKey: string) {
|
|
82
|
+
if (themeKeys.value.includes(themeKey)) {
|
|
83
|
+
state.value = themeKey;
|
|
84
|
+
} else {
|
|
85
|
+
console.warn(`[useTheme] Attempted to set an unregistered theme: ${themeKey}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
53
88
|
|
|
54
|
-
|
|
89
|
+
const currentLocalizedName = computed(() => {
|
|
90
|
+
const currentDef = _themeRegistry.value.find((t) => t.key === state.value);
|
|
91
|
+
if (currentDef) {
|
|
92
|
+
return currentDef.localizationKey ? t(currentDef.localizationKey) : _.capitalize(currentDef.key);
|
|
93
|
+
}
|
|
94
|
+
// Fallback if current theme key somehow not in registry (should not happen with proper setTheme guard)
|
|
95
|
+
return state.value ? _.capitalize(state.value) : "";
|
|
96
|
+
});
|
|
55
97
|
|
|
56
98
|
return {
|
|
57
|
-
themes: computed(() =>
|
|
58
|
-
|
|
99
|
+
themes: computed(() =>
|
|
100
|
+
_themeRegistry.value.map((themeDef) => ({
|
|
101
|
+
key: themeDef.key,
|
|
102
|
+
name: themeDef.localizationKey ? t(themeDef.localizationKey) : _.capitalize(themeDef.key),
|
|
103
|
+
})),
|
|
104
|
+
),
|
|
105
|
+
currentThemeKey: state,
|
|
106
|
+
currentLocalizedName,
|
|
59
107
|
next,
|
|
60
108
|
register,
|
|
61
109
|
unregister,
|
|
@@ -16,11 +16,11 @@ import { RequestPasswordResult } from "./../../types";
|
|
|
16
16
|
import { createSharedComposable } from "@vueuse/core";
|
|
17
17
|
import { useExternalProvider } from "../../../shared/components/sign-in/useExternalProvider";
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
// Interface for the full internal API provided by _createInternalUserLogic
|
|
20
|
+
export interface IUserInternalAPI {
|
|
20
21
|
user: ComputedRef<UserDetail | undefined>;
|
|
21
22
|
loading: ComputedRef<boolean>;
|
|
22
23
|
isAdministrator: ComputedRef<boolean | undefined>;
|
|
23
|
-
// getAccessToken: () => Promise<string | undefined>;
|
|
24
24
|
loadUser: () => Promise<UserDetail>;
|
|
25
25
|
signIn: (username: string, password: string) => Promise<SignInResult | { succeeded: boolean; error?: any }>;
|
|
26
26
|
signOut: () => Promise<void>;
|
|
@@ -32,8 +32,19 @@ interface IUseUser {
|
|
|
32
32
|
getLoginType: () => Promise<LoginType[]>;
|
|
33
33
|
isAuthenticated: ComputedRef<boolean>;
|
|
34
34
|
}
|
|
35
|
+
|
|
36
|
+
export interface IAppUserAPI {
|
|
37
|
+
user: ComputedRef<UserDetail | undefined>;
|
|
38
|
+
loading: ComputedRef<boolean>;
|
|
39
|
+
isAuthenticated: ComputedRef<boolean>;
|
|
40
|
+
isAdministrator: ComputedRef<boolean | undefined>;
|
|
41
|
+
loadUser: () => Promise<UserDetail>;
|
|
42
|
+
signOut: () => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
const user: Ref<UserDetail | undefined> = ref();
|
|
36
|
-
|
|
46
|
+
|
|
47
|
+
export function _createInternalUserLogic(): IUserInternalAPI {
|
|
37
48
|
const loading: Ref<boolean> = ref(false);
|
|
38
49
|
|
|
39
50
|
const { storage: externalSignInStorage, signOut: externalSignOut } = useExternalProvider();
|
|
@@ -72,7 +83,7 @@ function useUserFn(): IUseUser {
|
|
|
72
83
|
username: string,
|
|
73
84
|
password: string,
|
|
74
85
|
): Promise<SignInResult | { succeeded: boolean; error?: any; status?: number }> {
|
|
75
|
-
console.debug(`[@vc-shell/framework#
|
|
86
|
+
console.debug(`[@vc-shell/framework#_createInternalUserLogic:signIn] - Entry point`);
|
|
76
87
|
try {
|
|
77
88
|
loading.value = true;
|
|
78
89
|
const result = await securityClient.login(new LoginRequest({ userName: username, password }));
|
|
@@ -98,7 +109,7 @@ function useUserFn(): IUseUser {
|
|
|
98
109
|
}
|
|
99
110
|
|
|
100
111
|
async function signOut(): Promise<void> {
|
|
101
|
-
console.debug(`[@vc-shell/framework#
|
|
112
|
+
console.debug(`[@vc-shell/framework#_createInternalUserLogic:signOut] - Entry point`);
|
|
102
113
|
|
|
103
114
|
user.value = undefined;
|
|
104
115
|
|
|
@@ -110,12 +121,12 @@ function useUserFn(): IUseUser {
|
|
|
110
121
|
}
|
|
111
122
|
|
|
112
123
|
async function loadUser(): Promise<UserDetail> {
|
|
113
|
-
console.debug(`[@vc-shell/framework#
|
|
124
|
+
console.debug(`[@vc-shell/framework#_createInternalUserLogic:loadUser] - Entry point`);
|
|
114
125
|
|
|
115
126
|
try {
|
|
116
127
|
loading.value = true;
|
|
117
128
|
user.value = await securityClient.getCurrentUser();
|
|
118
|
-
console.log("[
|
|
129
|
+
console.log("[_createInternalUserLogic]: an user details has been loaded", user.value);
|
|
119
130
|
} catch (e: any) {
|
|
120
131
|
console.error(e);
|
|
121
132
|
} finally {
|
|
@@ -187,4 +198,14 @@ function useUserFn(): IUseUser {
|
|
|
187
198
|
};
|
|
188
199
|
}
|
|
189
200
|
|
|
190
|
-
export const useUser = createSharedComposable(() =>
|
|
201
|
+
export const useUser = createSharedComposable((): IAppUserAPI => {
|
|
202
|
+
const internals = _createInternalUserLogic();
|
|
203
|
+
return {
|
|
204
|
+
user: internals.user,
|
|
205
|
+
loading: internals.loading,
|
|
206
|
+
isAuthenticated: internals.isAuthenticated,
|
|
207
|
+
isAdministrator: internals.isAdministrator,
|
|
208
|
+
loadUser: internals.loadUser,
|
|
209
|
+
signOut: internals.signOut,
|
|
210
|
+
};
|
|
211
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { computed, ComputedRef } from "vue";
|
|
2
|
+
import { createSharedComposable } from "@vueuse/core";
|
|
3
|
+
import { _createInternalUserLogic, IUserInternalAPI } from "../useUser"; // Import the internal logic
|
|
4
|
+
import { SecurityResult, IdentityResult, LoginType, UserDetail, SignInResult } from "./../../api/platform";
|
|
5
|
+
import { RequestPasswordResult } from "./../../types";
|
|
6
|
+
|
|
7
|
+
// Interface for the API exposed by useUserManagement (for framework/admin use)
|
|
8
|
+
export interface IUserManagementAPI {
|
|
9
|
+
user: ComputedRef<UserDetail | undefined>;
|
|
10
|
+
loading: ComputedRef<boolean>;
|
|
11
|
+
isAdministrator: ComputedRef<boolean | undefined>;
|
|
12
|
+
isAuthenticated: ComputedRef<boolean>;
|
|
13
|
+
// Methods specific to user management
|
|
14
|
+
validateToken: (userId: string, token: string) => Promise<boolean>;
|
|
15
|
+
validatePassword: (password: string) => Promise<IdentityResult>;
|
|
16
|
+
resetPasswordByToken: (userId: string, password: string, token: string) => Promise<SecurityResult>;
|
|
17
|
+
getLoginType: () => Promise<LoginType[]>;
|
|
18
|
+
loadUser: () => Promise<UserDetail>;
|
|
19
|
+
requestPasswordReset: (loginOrEmail: string) => Promise<RequestPasswordResult>;
|
|
20
|
+
changeUserPassword: (oldPassword: string, newPassword: string) => Promise<SecurityResult | undefined>;
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
signIn: (username: string, password: string) => Promise<SignInResult | { succeeded: boolean; error?: any }>;
|
|
23
|
+
signOut: () => Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const useUserManagement = createSharedComposable((): IUserManagementAPI => {
|
|
27
|
+
// Utilize the same internal logic instance
|
|
28
|
+
const internals: IUserInternalAPI = _createInternalUserLogic();
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
user: internals.user,
|
|
32
|
+
loading: internals.loading,
|
|
33
|
+
isAdministrator: internals.isAdministrator,
|
|
34
|
+
isAuthenticated: internals.isAuthenticated,
|
|
35
|
+
validateToken: internals.validateToken,
|
|
36
|
+
validatePassword: internals.validatePassword,
|
|
37
|
+
resetPasswordByToken: internals.resetPasswordByToken,
|
|
38
|
+
getLoginType: internals.getLoginType,
|
|
39
|
+
loadUser: internals.loadUser,
|
|
40
|
+
requestPasswordReset: internals.requestPasswordReset,
|
|
41
|
+
changeUserPassword: internals.changeUserPassword,
|
|
42
|
+
signIn: internals.signIn,
|
|
43
|
+
signOut: internals.signOut,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
2
|
import { Router } from "vue-router";
|
|
3
|
-
import {
|
|
3
|
+
import { useUserManagement } from "../composables/useUserManagement";
|
|
4
4
|
import { notification } from "../../shared";
|
|
5
5
|
|
|
6
6
|
export function registerInterceptors(router: Router) {
|
|
7
7
|
const { fetch: originalFetch } = window;
|
|
8
|
-
const { signOut, isAuthenticated } =
|
|
8
|
+
const { signOut, isAuthenticated } = useUserManagement();
|
|
9
9
|
|
|
10
10
|
window.fetch = async (...args) => {
|
|
11
11
|
/**
|
|
@@ -2,7 +2,7 @@ import { App, watch, ref, InjectionKey } from "vue";
|
|
|
2
2
|
import { HubConnection, HubConnectionBuilder, LogLevel } from "@microsoft/signalr";
|
|
3
3
|
import { PushNotification } from "../../api/platform";
|
|
4
4
|
import { useNotifications } from "./../../composables/useNotifications";
|
|
5
|
-
import {
|
|
5
|
+
import { useUserManagement } from "../../composables/useUserManagement";
|
|
6
6
|
import { useCypressSignalRMock } from "cypress-signalr-mock";
|
|
7
7
|
|
|
8
8
|
const { addNotification } = useNotifications();
|
|
@@ -34,7 +34,7 @@ export const signalR = {
|
|
|
34
34
|
},
|
|
35
35
|
) {
|
|
36
36
|
currentCreator.value = options?.creator;
|
|
37
|
-
const { isAuthenticated } =
|
|
37
|
+
const { isAuthenticated } = useUserManagement();
|
|
38
38
|
let reconnect = false;
|
|
39
39
|
const connection =
|
|
40
40
|
useCypressSignalRMock("pushNotificationHub", { enableForVitest: true }) ??
|