@vc-shell/vc-app-skill 2.0.0-alpha.23 → 2.0.0-alpha.24
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 +8 -0
- package/bin/knowledge-stats.cjs +135 -0
- package/bin/sync-docs.cjs +62 -0
- package/package.json +5 -1
- package/runtime/VERSION +1 -1
- package/runtime/agents/details-blade-generator.md +75 -14
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/api/platform.docs.md +14 -7
- package/runtime/knowledge/docs/core/blade-navigation/blade-nav-composables.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useApiClient/useApiClient.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useAssetsManager/useAssetsManager.docs.md +149 -0
- package/runtime/knowledge/docs/core/composables/useAsync/useAsync.docs.md +7 -0
- package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +7 -0
- package/runtime/knowledge/docs/core/composables/useDashboard/useDashboard.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useMenuService/useMenuService.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/usePermissions/usePermissions.docs.md +6 -0
- package/runtime/knowledge/docs/core/composables/useToolbar/useToolbar.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/ai-agent/ai-agent.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/extension-points/extension-points.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/global-error-handler/global-error-handler.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/i18n/i18n.docs.md +74 -0
- package/runtime/knowledge/docs/core/plugins/modularity/modularity.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/permissions/permissions.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/signalR/signalR.docs.md +6 -0
- package/runtime/knowledge/docs/core/plugins/validation/validation.docs.md +6 -0
- package/runtime/knowledge/docs/modules/assets-manager/assets-manager.docs.md +29 -33
- package/runtime/knowledge/docs/shell/components/logout-button/logout-button.docs.md +3 -3
- package/runtime/knowledge/docs/ui/components/atoms/vc-button/vc-button.docs.md +11 -0
- package/runtime/knowledge/docs/ui/components/atoms/vc-card/vc-card.docs.md +11 -0
- package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +11 -0
- package/runtime/knowledge/docs/ui/components/organisms/vc-table/vc-data-table.docs.md +12 -0
- package/runtime/knowledge/index.md +60 -0
- package/runtime/knowledge/patterns/assets-management.md +213 -0
- package/runtime/knowledge/patterns/child-blade-flow.md +277 -0
- package/runtime/knowledge/patterns/details-blade-pattern.md +350 -3
- package/runtime/knowledge/patterns/extension-points-usage.md +308 -0
- package/runtime/knowledge/patterns/form-validation.md +377 -0
- package/runtime/knowledge/patterns/multilanguage-fields.md +239 -0
- package/runtime/knowledge/patterns/signalr-notifications.md +237 -0
- package/runtime/vc-app.md +44 -5
- package/runtime/knowledge/docs/shell/components/app-switcher/app-switcher.docs.md +0 -104
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# SignalR Notifications Pattern
|
|
2
|
+
|
|
3
|
+
Real-time push notifications from the VirtoCommerce platform to vc-shell modules via SignalR WebSocket transport.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Notification Flow
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Platform backend
|
|
11
|
+
| (domain event fires)
|
|
12
|
+
v
|
|
13
|
+
ASP.NET SignalR hub (/pushNotificationHub)
|
|
14
|
+
|
|
|
15
|
+
+-- "Send" channel (broadcast to all clients)
|
|
16
|
+
+-- "SendSystemEvents" (filtered by creator ID)
|
|
17
|
+
|
|
|
18
|
+
v
|
|
19
|
+
SignalR plugin (framework auto-installed)
|
|
20
|
+
|
|
|
21
|
+
v
|
|
22
|
+
NotificationStore.ingest(message)
|
|
23
|
+
|
|
|
24
|
+
+---> history[] (persistent, loaded from API)
|
|
25
|
+
+---> realtime[] (session-only, from SignalR)
|
|
26
|
+
+---> ToastController (Level 1: module-level toasts)
|
|
27
|
+
+---> subscriber callbacks (Level 2: blade-scoped handlers)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The SignalR plugin connects on login, disconnects on logout, and auto-reconnects on connection loss. Module developers never interact with SignalR directly.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Step 1: Create a Notification Template
|
|
35
|
+
|
|
36
|
+
Each notification type can have a custom Vue SFC that controls how it renders in the dropdown and toasts. The component receives its data via `useNotificationContext()`, not props.
|
|
37
|
+
|
|
38
|
+
```vue
|
|
39
|
+
<!-- notifications/OrderShippedEvent.vue -->
|
|
40
|
+
<template>
|
|
41
|
+
<NotificationTemplate
|
|
42
|
+
:color="color"
|
|
43
|
+
:title="notification.title ?? ''"
|
|
44
|
+
:icon="'lucide-truck'"
|
|
45
|
+
:notification="notification"
|
|
46
|
+
@click="onClick"
|
|
47
|
+
>
|
|
48
|
+
<VcHint v-if="notification.description" class="tw-mb-1">
|
|
49
|
+
{{ notification.description }}
|
|
50
|
+
</VcHint>
|
|
51
|
+
</NotificationTemplate>
|
|
52
|
+
</template>
|
|
53
|
+
|
|
54
|
+
<script lang="ts" setup>
|
|
55
|
+
import {
|
|
56
|
+
NotificationTemplate,
|
|
57
|
+
useNotificationContext,
|
|
58
|
+
useBlade,
|
|
59
|
+
} from "@vc-shell/framework";
|
|
60
|
+
import type { PushNotification } from "@vc-shell/framework";
|
|
61
|
+
import { computed } from "vue";
|
|
62
|
+
|
|
63
|
+
interface IOrderShippedNotification extends PushNotification {
|
|
64
|
+
orderId?: string;
|
|
65
|
+
trackingNumber?: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const notificationRef = useNotificationContext<IOrderShippedNotification>();
|
|
69
|
+
const notification = computed(() => notificationRef.value);
|
|
70
|
+
|
|
71
|
+
const color = computed(() =>
|
|
72
|
+
notification.value.trackingNumber ? "var(--success-400)" : "var(--primary-500)"
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const { openBlade } = useBlade();
|
|
76
|
+
|
|
77
|
+
async function onClick() {
|
|
78
|
+
await openBlade({ name: "OrderDetails", param: notification.value.orderId });
|
|
79
|
+
}
|
|
80
|
+
</script>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `NotificationTemplate` props
|
|
84
|
+
|
|
85
|
+
| Prop | Type | Description |
|
|
86
|
+
|----------------|--------------------|------------------------------------------------|
|
|
87
|
+
| `title` | `string` | Notification title text |
|
|
88
|
+
| `notification` | `PushNotification` | Full notification object (used for timestamp) |
|
|
89
|
+
| `icon` | `string` | Lucide icon name, e.g. `"lucide-truck"` |
|
|
90
|
+
| `color` | `string` | CSS color/variable for the icon circle |
|
|
91
|
+
|
|
92
|
+
The default slot renders below the title/timestamp. Use it for description text, progress bars, or action links.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Step 2: Register in the Module
|
|
97
|
+
|
|
98
|
+
Pass a `notifications` record to `defineAppModule`. Each key must exactly match the `notifyType` string sent by the platform backend.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// modules/orders/index.ts
|
|
102
|
+
import * as pages from "./pages";
|
|
103
|
+
import * as locales from "./locales";
|
|
104
|
+
import { defineAppModule } from "@vc-shell/framework";
|
|
105
|
+
import OrderShippedEvent from "./notifications/OrderShippedEvent.vue";
|
|
106
|
+
|
|
107
|
+
export default defineAppModule({
|
|
108
|
+
blades: pages,
|
|
109
|
+
locales,
|
|
110
|
+
notifications: {
|
|
111
|
+
OrderShippedEvent: {
|
|
112
|
+
template: OrderShippedEvent,
|
|
113
|
+
toast: { mode: "auto" },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Toast configuration
|
|
120
|
+
|
|
121
|
+
The `toast` object controls how the notification appears as a popup.
|
|
122
|
+
|
|
123
|
+
| Property | Type | Description |
|
|
124
|
+
|-----------------|---------------------------------------------|------------------------------------------------------|
|
|
125
|
+
| `mode` | `"auto" \| "progress" \| "silent"` | `auto` = fire-and-forget; `progress` = persistent until complete; `silent` = no toast |
|
|
126
|
+
| `severity` | `Severity \| (msg) => Severity` | Static or dynamic: `"info"`, `"warning"`, `"error"`, `"critical"` |
|
|
127
|
+
| `timeout` | `number` | Override default timeout (ms). Defaults: info=5s, warning=8s, error/critical=persistent |
|
|
128
|
+
| `isComplete` | `(msg) => boolean` | For `"progress"` mode: returns true when the operation finishes |
|
|
129
|
+
| `completedType` | `(msg) => string` | For `"progress"` mode: final toast variant (`"success"` or `"error"`) |
|
|
130
|
+
|
|
131
|
+
#### Toast mode examples
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
// Fire-and-forget toast with default severity (info, 5s timeout)
|
|
135
|
+
toast: { mode: "auto" }
|
|
136
|
+
|
|
137
|
+
// Warning toast with custom timeout
|
|
138
|
+
toast: { mode: "auto", severity: "warning", timeout: 8000 }
|
|
139
|
+
|
|
140
|
+
// Long-running operation with progress tracking
|
|
141
|
+
toast: {
|
|
142
|
+
mode: "progress",
|
|
143
|
+
severity: (msg) => msg.finished ? "info" : "warning",
|
|
144
|
+
isComplete: (msg) => !!msg.finished,
|
|
145
|
+
completedType: (msg) => msg.errorCount ? "error" : "success",
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// No toast -- notification only appears in the dropdown
|
|
149
|
+
toast: { mode: "silent" }
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### `groupBy` option
|
|
153
|
+
|
|
154
|
+
For notification types that emit multiple messages for the same logical operation (e.g. export progress updates), set `groupBy` to the field name that identifies the operation. The store upserts instead of appending:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
notifications: {
|
|
158
|
+
CatalogExportProgress: {
|
|
159
|
+
template: ExportProgressTemplate,
|
|
160
|
+
toast: { mode: "progress", isComplete: (msg) => !!msg.finished },
|
|
161
|
+
groupBy: "jobId",
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Step 3: React to Notifications in a Blade
|
|
169
|
+
|
|
170
|
+
Use `useBladeNotifications()` inside `<script setup>` to subscribe to specific notification types. Subscriptions auto-cleanup when the blade unmounts.
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { useBladeNotifications } from "@vc-shell/framework";
|
|
174
|
+
|
|
175
|
+
const { messages, unreadCount, markAsRead } = useBladeNotifications({
|
|
176
|
+
types: ["OrderShippedEvent"],
|
|
177
|
+
filter: (msg) => msg.orderId === currentOrderId.value,
|
|
178
|
+
onMessage: (msg) => {
|
|
179
|
+
// Refresh blade data when a relevant notification arrives
|
|
180
|
+
reloadOrder();
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### `useBladeNotifications` API
|
|
186
|
+
|
|
187
|
+
**Parameters:**
|
|
188
|
+
|
|
189
|
+
| Field | Type | Description |
|
|
190
|
+
|------------|-----------------------------|--------------------------------------------------|
|
|
191
|
+
| `types` | `string[]` | Notification type(s) to subscribe to |
|
|
192
|
+
| `filter` | `(msg) => boolean` | Optional: further filter by message fields |
|
|
193
|
+
| `onMessage`| `(msg) => void` | Callback fired for each matching notification |
|
|
194
|
+
|
|
195
|
+
**Returns:**
|
|
196
|
+
|
|
197
|
+
| Field | Type | Description |
|
|
198
|
+
|--------------|---------------------------|------------------------------------------------|
|
|
199
|
+
| `messages` | `ComputedRef<T[]>` | Matching unread messages from the realtime queue |
|
|
200
|
+
| `unreadCount`| `ComputedRef<number>` | Count of matching unread messages |
|
|
201
|
+
| `markAsRead` | `(msg) => void` | Mark a specific message as read |
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Directory Structure
|
|
206
|
+
|
|
207
|
+
```
|
|
208
|
+
src/modules/orders/
|
|
209
|
+
├── index.ts # defineAppModule with notifications
|
|
210
|
+
├── notifications/
|
|
211
|
+
│ ├── index.ts # barrel export
|
|
212
|
+
│ └── OrderShippedEvent.vue # template component
|
|
213
|
+
└── pages/
|
|
214
|
+
└── ...
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`notifications/index.ts` barrel:
|
|
218
|
+
```ts
|
|
219
|
+
export { default as OrderShippedEvent } from "./OrderShippedEvent.vue";
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Key Rules
|
|
225
|
+
|
|
226
|
+
1. **Notification keys are case-sensitive** -- the key in `notifications: { ... }` must exactly match the `notifyType` string from the platform backend.
|
|
227
|
+
2. **Use `useNotificationContext()`** inside template components, not props. The framework injects the notification via provide/inject.
|
|
228
|
+
3. **Use `useBladeNotifications()`** for blade-level subscriptions (preferred). The deprecated `useNotifications()` still works but logs a warning.
|
|
229
|
+
4. **Do not interact with SignalR directly.** The plugin is auto-installed by the framework. Use the notification store and composables instead.
|
|
230
|
+
5. **`Send` vs `SendSystemEvents`** -- broadcast notifications use the `Send` channel; user-scoped notifications use `SendSystemEvents` filtered by the `creator` field on the server side.
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Related Patterns
|
|
235
|
+
|
|
236
|
+
- [notification-template](./notification-template.md) -- detailed template component authoring guide
|
|
237
|
+
- [module-structure](./module-structure.md) -- overall module layout including notifications directory
|
package/runtime/vc-app.md
CHANGED
|
@@ -951,7 +951,29 @@ Parse `DESIGN_PROMPT` into a structured application plan. Apply these parsing ru
|
|
|
951
951
|
|
|
952
952
|
**Field extraction:**
|
|
953
953
|
- Concrete field mentions ("subscription token key", "trial period", "email") → columns or formFields with inferred types
|
|
954
|
-
- Type inference
|
|
954
|
+
- Type inference — use the most specific type possible:
|
|
955
|
+
|
|
956
|
+
| Signal in prompt | Field type | Component (details) | Column type (list) |
|
|
957
|
+
|---|---|---|---|
|
|
958
|
+
| date, deadline, birthday, created, expires | `date-time` | `VcDatePicker` | `date-ago` |
|
|
959
|
+
| is*, has*, can*, enabled, active, published | `boolean` | `VcSwitch` | `status-icon` |
|
|
960
|
+
| price, cost, amount, total, salary, budget, fee | `currency` | `VcInputCurrency` | `money` |
|
|
961
|
+
| count, quantity, age, priority (numeric) | `number` | `VcInput type="number"` | `number` |
|
|
962
|
+
| description, notes, comment, bio, summary | `text` | `VcTextarea` | — (not in list) |
|
|
963
|
+
| body, content, html, article, template | `rich-text` | `VcEditor` | — (not in list) |
|
|
964
|
+
| status, state, type, category (from fixed set) | `enum` | `VcSelect` or `VcRadioGroup` | `status` |
|
|
965
|
+
| tags, labels, categories, roles, permissions | `multi-select` | `VcMultivalue` | — (not in list) |
|
|
966
|
+
| avatar, logo, photo, thumbnail, banner | `image` | `VcImageUpload` | `image` |
|
|
967
|
+
| photos, images, screenshots, gallery | `gallery` | `VcGallery` | — (not in list) |
|
|
968
|
+
| file, attachment, document, contract | `file` | `VcFileUpload` | — (not in list) |
|
|
969
|
+
| rating, score, stars | `rating` | `VcRating` | — (custom slot) |
|
|
970
|
+
| color, colour, brandColor | `color` | `VcColorInput` | — (custom slot) |
|
|
971
|
+
| discount, opacity, percentage, progress | `range` | `VcSlider` | — (custom slot) |
|
|
972
|
+
| email | `string` | `VcInput` (rules="email") | plain text |
|
|
973
|
+
| phone, tel | `string` | `VcInput` (type="tel") | plain text |
|
|
974
|
+
| url, website, link | `string` | `VcInput` (type="url") | plain text |
|
|
975
|
+
| everything else | `string` | `VcInput` | plain text |
|
|
976
|
+
|
|
955
977
|
- If a field clearly belongs to a list view (searchable, sortable characteristic) → column
|
|
956
978
|
- If a field clearly belongs to a form (editable, configurable) → formField
|
|
957
979
|
- If unclear → put in both columns and formFields
|
|
@@ -976,9 +998,13 @@ DESIGN_PLAN = {
|
|
|
976
998
|
"name": "string — kebab-case module name (english)",
|
|
977
999
|
"description": "string — what this module does",
|
|
978
1000
|
"bladeTypes": "list+details | list-only | details-only",
|
|
1001
|
+
"readOnly": "boolean? — true if details blade is view-only (use VcField instead of VcInput). Default: false",
|
|
979
1002
|
"columns": [{ "name": "string", "type": "string", "sortable": true/false }],
|
|
980
|
-
"formFields": [{ "name": "string", "type": "string", "required": true/false }],
|
|
1003
|
+
"formFields": [{ "name": "string", "type": "string (use specific types from type inference table above)", "required": true/false, "component": "string? — VcInput|VcTextarea|VcEditor|VcDatePicker|VcSelect|VcSwitch|VcCheckbox|VcRadioGroup|VcInputCurrency|VcMultivalue|VcRating|VcSlider|VcColorInput|VcImageUpload|VcGallery|VcFileUpload|VcField", "readOnly": "boolean? — true for display-only fields within editable form" }],
|
|
981
1004
|
"toolbarActions": [{ "label": "string", "action": "string (camelCase)" }],
|
|
1005
|
+
"widgets": "string[]? — names of related sub-entities for blade sidebar widgets (e.g., ['offers', 'videos'])",
|
|
1006
|
+
"dashboard": "boolean? — true to generate a DashboardWidgetCard for this module",
|
|
1007
|
+
"notifications": "string[]? — domain event names to generate notification templates for (e.g., ['OrderCreated'])",
|
|
982
1008
|
"todos": ["string — exact quote from prompt"]
|
|
983
1009
|
}
|
|
984
1010
|
],
|
|
@@ -998,8 +1024,18 @@ DESIGN_PLAN = {
|
|
|
998
1024
|
- Entity with both list-worthy columns AND editable fields → `"list+details"`
|
|
999
1025
|
- Entity that is mainly a collection/catalog with no edit form → `"list-only"`
|
|
1000
1026
|
- Entity that is a singleton/settings with no list → `"details-only"`
|
|
1027
|
+
- Entity that is view-only (order details, transaction log, audit) → `"list+details"` but mark the details blade `readOnly: true`
|
|
1001
1028
|
- Default to `"list+details"` when unclear
|
|
1002
1029
|
|
|
1030
|
+
**Details blade mode inference:**
|
|
1031
|
+
- If the entity is view-only (order, transaction, audit log, payment) → set `readOnly: true` on the module — the details generator will use `VcField` for display instead of `VcInput`
|
|
1032
|
+
- If the entity has mixed editable + read-only fields → default `readOnly: false`, individual fields marked as `readOnly` in `formFields`
|
|
1033
|
+
|
|
1034
|
+
**Feature inference (enrich modules based on prompt signals):**
|
|
1035
|
+
- If entity mentions "related" sub-entities (e.g., "product has offers and videos") → add `widgets: ["sub-entity-name"]` — generates blade sidebar widgets with `useBladeWidgets()`
|
|
1036
|
+
- If entity is a "primary" concept that would benefit from a dashboard summary → add `dashboard: true` — generates a `DashboardWidgetCard` with stats
|
|
1037
|
+
- If entity mentions "notifications" or "events" or "alerts" → add `notifications: ["EventName"]` — generates notification template components
|
|
1038
|
+
|
|
1003
1039
|
### Phase 3: Plan Presentation
|
|
1004
1040
|
|
|
1005
1041
|
Present the parsed plan to the user in this format:
|
|
@@ -1009,10 +1045,13 @@ Application Plan: {DESIGN_PLAN.appName}
|
|
|
1009
1045
|
|
|
1010
1046
|
Modules ({count}):
|
|
1011
1047
|
─────────────────────────────────────────
|
|
1012
|
-
1. {name} ({bladeTypes})
|
|
1013
|
-
Columns: {comma-separated column names}
|
|
1014
|
-
Fields: {comma-separated
|
|
1048
|
+
1. {name} ({bladeTypes}{, read-only if readOnly})
|
|
1049
|
+
Columns: {comma-separated column names with types, e.g. "name(string), status(enum), price(currency)"}
|
|
1050
|
+
Fields: {comma-separated "fieldName → Component", e.g. "name → VcInput, description → VcTextarea, price → VcInputCurrency"}
|
|
1015
1051
|
Actions: {comma-separated action labels}
|
|
1052
|
+
{Widgets: sub-entity-1, sub-entity-2 — if widgets present}
|
|
1053
|
+
{Dashboard: yes — if dashboard present}
|
|
1054
|
+
{Notifications: EventName — if notifications present}
|
|
1016
1055
|
TODO: "{todo text}"
|
|
1017
1056
|
|
|
1018
1057
|
2. {name} ({bladeTypes})
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# App Switcher
|
|
2
|
-
|
|
3
|
-
A component and composable for switching between multiple VirtoCommerce applications (e.g., Vendor Portal, Admin Portal) within the same platform instance.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
The App Switcher fetches a list of available applications from the platform API and renders them in a dropdown. Clicking an app navigates the browser to that app's URL. Permission checks ensure users only see apps they have access to.
|
|
8
|
-
|
|
9
|
-
## Directory Structure
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
app-switcher/
|
|
13
|
-
components/
|
|
14
|
-
vc-app-switcher/
|
|
15
|
-
vc-app-switcher.vue # Dropdown UI component
|
|
16
|
-
composables/
|
|
17
|
-
useAppSwitcher/
|
|
18
|
-
index.ts # Data fetching and navigation logic
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Composable: useAppSwitcher
|
|
22
|
-
|
|
23
|
-
Provides the data layer for app switching.
|
|
24
|
-
|
|
25
|
-
```typescript
|
|
26
|
-
import { useAppSwitcher } from "@vc-shell/framework";
|
|
27
|
-
|
|
28
|
-
const { appsList, getApps, switchApp } = useAppSwitcher();
|
|
29
|
-
|
|
30
|
-
// Fetch available apps on mount
|
|
31
|
-
await getApps();
|
|
32
|
-
|
|
33
|
-
// Switch to an app (checks permissions, navigates via window.location)
|
|
34
|
-
switchApp(selectedApp);
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Return Value
|
|
38
|
-
|
|
39
|
-
| Property | Type | Description |
|
|
40
|
-
|---|---|---|
|
|
41
|
-
| `appsList` | `Ref<AppDescriptor[]>` | Reactive list of available applications (readonly computed) |
|
|
42
|
-
| `getApps` | `() => Promise<void>` | Fetches the app list from `AppsClient` |
|
|
43
|
-
| `switchApp` | `(app: AppDescriptor) => void` | Navigates to the app's URL if the user has permission |
|
|
44
|
-
|
|
45
|
-
### AppDescriptor
|
|
46
|
-
|
|
47
|
-
Each app in the list has:
|
|
48
|
-
- `id` -- unique identifier
|
|
49
|
-
- `title` -- display name
|
|
50
|
-
- `iconUrl` -- URL of the app icon
|
|
51
|
-
- `relativeUrl` -- the URL path to navigate to
|
|
52
|
-
- `permission` -- required permission string
|
|
53
|
-
|
|
54
|
-
## Component: VcAppSwitcher
|
|
55
|
-
|
|
56
|
-
Renders the app list inside a `VcDropdown`. Highlights the currently active app based on URL matching.
|
|
57
|
-
|
|
58
|
-
### Props
|
|
59
|
-
|
|
60
|
-
| Prop | Type | Description |
|
|
61
|
-
|---|---|---|
|
|
62
|
-
| `appsList` | `AppDescriptor[]` | List of apps to display |
|
|
63
|
-
|
|
64
|
-
### Events
|
|
65
|
-
|
|
66
|
-
| Event | Payload | Description |
|
|
67
|
-
|---|---|---|
|
|
68
|
-
| `onClick` | `AppDescriptor` | Emitted when an app is clicked |
|
|
69
|
-
|
|
70
|
-
## Usage
|
|
71
|
-
|
|
72
|
-
```vue
|
|
73
|
-
<script setup>
|
|
74
|
-
import { useAppSwitcher } from "@vc-shell/framework";
|
|
75
|
-
import { VcAppSwitcher } from "@vc-shell/framework";
|
|
76
|
-
|
|
77
|
-
const { appsList, getApps, switchApp } = useAppSwitcher();
|
|
78
|
-
onMounted(() => getApps());
|
|
79
|
-
</script>
|
|
80
|
-
|
|
81
|
-
<template>
|
|
82
|
-
<VcAppSwitcher
|
|
83
|
-
:apps-list="appsList"
|
|
84
|
-
@on-click="switchApp"
|
|
85
|
-
/>
|
|
86
|
-
</template>
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Behavior
|
|
90
|
-
|
|
91
|
-
- `switchApp` checks `hasAccess(app.permission)` before navigating. If access is denied, a notification error is shown.
|
|
92
|
-
- Navigation uses `window.location.href` (full page reload) since apps are separate SPA deployments.
|
|
93
|
-
- The active app is determined by matching `window.location.pathname` against each app's `relativeUrl`.
|
|
94
|
-
|
|
95
|
-
## Tips
|
|
96
|
-
|
|
97
|
-
- The composable uses `usePermissions()` internally -- no need to check permissions manually.
|
|
98
|
-
- `getApps()` calls the platform API and may throw on network errors. Wrap in try/catch if needed.
|
|
99
|
-
- The component is typically rendered in the app bar header area.
|
|
100
|
-
|
|
101
|
-
## Related
|
|
102
|
-
|
|
103
|
-
- `framework/core/api/platform/` -- `AppsClient` API client
|
|
104
|
-
- `framework/core/composables/usePermissions/` -- permission checking
|