@vc-shell/framework 1.0.147 → 1.0.149

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 (88) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/core/composables/index.ts +1 -1
  3. package/core/composables/useLanguages/index.ts +52 -0
  4. package/core/plugins/i18n/index.ts +1 -1
  5. package/core/plugins/modularity/index.ts +10 -1
  6. package/core/plugins/validation/index.ts +0 -11
  7. package/core/plugins/validation/rules.ts +7 -6
  8. package/dist/core/composables/index.d.ts +1 -1
  9. package/dist/core/composables/index.d.ts.map +1 -1
  10. package/dist/core/composables/useLanguages/index.d.ts +12 -0
  11. package/dist/core/composables/useLanguages/index.d.ts.map +1 -0
  12. package/dist/core/plugins/modularity/index.d.ts.map +1 -1
  13. package/dist/core/plugins/validation/index.d.ts +0 -3
  14. package/dist/core/plugins/validation/index.d.ts.map +1 -1
  15. package/dist/core/plugins/validation/rules.d.ts +1 -1
  16. package/dist/core/plugins/validation/rules.d.ts.map +1 -1
  17. package/dist/framework.js +11732 -10993
  18. package/dist/index.css +1 -1
  19. package/dist/index.d.ts +5 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/shared/components/app-switcher/composables/useAppSwitcher/index.d.ts.map +1 -1
  22. package/dist/shared/components/blade-navigation/components/vc-blade-navigation/vc-blade-navigation.vue.d.ts.map +1 -1
  23. package/dist/shared/components/blade-navigation/components/vc-blade-view/vc-blade-view.d.ts +0 -12
  24. package/dist/shared/components/blade-navigation/components/vc-blade-view/vc-blade-view.d.ts.map +1 -1
  25. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/index.d.ts +6 -3
  26. package/dist/shared/components/blade-navigation/composables/useBladeNavigation/index.d.ts.map +1 -1
  27. package/dist/shared/components/blade-navigation/plugin.d.ts.map +1 -1
  28. package/dist/shared/components/blade-navigation/types/index.d.ts +6 -8
  29. package/dist/shared/components/blade-navigation/types/index.d.ts.map +1 -1
  30. package/dist/shared/components/language-selector/language-selector.vue.d.ts.map +1 -1
  31. package/dist/shared/components/user-dropdown-button/user-dropdown-button.vue.d.ts.map +1 -1
  32. package/dist/shared/modules/dynamic/components/SchemaRender.d.ts +3 -3
  33. package/dist/shared/modules/dynamic/components/fields/Button.d.ts +1 -1
  34. package/dist/shared/modules/dynamic/components/fields/Card.d.ts +1 -1
  35. package/dist/shared/modules/dynamic/components/fields/Checkbox.d.ts +1 -1
  36. package/dist/shared/modules/dynamic/components/fields/ContentField.d.ts +1 -1
  37. package/dist/shared/modules/dynamic/components/fields/DynamicProperty.d.ts +1 -1
  38. package/dist/shared/modules/dynamic/components/fields/EditorField.d.ts +1 -1
  39. package/dist/shared/modules/dynamic/components/fields/Fieldset.d.ts +1 -1
  40. package/dist/shared/modules/dynamic/components/fields/GalleryField.d.ts +1 -1
  41. package/dist/shared/modules/dynamic/components/fields/ImageField.d.ts +1 -1
  42. package/dist/shared/modules/dynamic/components/fields/InputCurrency.d.ts +1 -1
  43. package/dist/shared/modules/dynamic/components/fields/InputField.d.ts +1 -1
  44. package/dist/shared/modules/dynamic/components/fields/MultivalueField.d.ts +1 -1
  45. package/dist/shared/modules/dynamic/components/fields/SelectField.d.ts +1 -1
  46. package/dist/shared/modules/dynamic/components/fields/StatusField.d.ts +1 -1
  47. package/dist/shared/modules/dynamic/components/fields/TextareaField.d.ts +1 -1
  48. package/dist/shared/modules/dynamic/components/fields/VideoField.d.ts +1 -1
  49. package/dist/shared/modules/dynamic/components/fields/props.d.ts +1 -1
  50. package/dist/shared/modules/dynamic/factories/types/index.d.ts +1 -1
  51. package/dist/shared/modules/dynamic/factories/types/index.d.ts.map +1 -1
  52. package/dist/shared/modules/dynamic/helpers/nodeBuilder.d.ts.map +1 -1
  53. package/dist/shared/modules/dynamic/helpers/override.d.ts.map +1 -1
  54. package/dist/shared/modules/dynamic/index.d.ts.map +1 -1
  55. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts +3 -1
  56. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts.map +1 -1
  57. package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts +2 -0
  58. package/dist/shared/modules/dynamic/pages/dynamic-blade-list.vue.d.ts.map +1 -1
  59. package/dist/shared/modules/dynamic/types/index.d.ts +13 -4
  60. package/dist/shared/modules/dynamic/types/index.d.ts.map +1 -1
  61. package/dist/tsconfig.tsbuildinfo +1 -1
  62. package/dist/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue.d.ts.map +1 -1
  63. package/dist/ui/components/organisms/vc-app/vc-app.vue.d.ts.map +1 -1
  64. package/package.json +6 -5
  65. package/shared/components/app-switcher/composables/useAppSwitcher/index.ts +2 -1
  66. package/shared/components/blade-navigation/components/vc-blade-navigation/vc-blade-navigation.vue +12 -26
  67. package/shared/components/blade-navigation/components/vc-blade-view/vc-blade-view.ts +18 -11
  68. package/shared/components/blade-navigation/composables/useBladeNavigation/index.ts +231 -337
  69. package/shared/components/blade-navigation/plugin.ts +2 -1
  70. package/shared/components/blade-navigation/types/index.ts +5 -11
  71. package/shared/components/language-selector/language-selector.vue +12 -10
  72. package/shared/components/notification-dropdown/notification-dropdown.vue +1 -1
  73. package/shared/components/user-dropdown-button/user-dropdown-button.vue +55 -40
  74. package/shared/modules/dynamic/factories/types/index.ts +1 -1
  75. package/shared/modules/dynamic/helpers/nodeBuilder.ts +6 -3
  76. package/shared/modules/dynamic/helpers/override.ts +29 -11
  77. package/shared/modules/dynamic/index.ts +1 -0
  78. package/shared/modules/dynamic/pages/dynamic-blade-form.vue +47 -17
  79. package/shared/modules/dynamic/pages/dynamic-blade-list.vue +11 -1
  80. package/shared/modules/dynamic/types/index.ts +13 -4
  81. package/ui/components/atoms/vc-label/vc-label.vue +18 -19
  82. package/ui/components/molecules/vc-multivalue/vc-multivalue.vue +1 -0
  83. package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +5 -19
  84. package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +1 -1
  85. package/ui/components/organisms/vc-app/vc-app.vue +2 -8
  86. package/core/composables/useI18n/index.ts +0 -7
  87. package/dist/core/composables/useI18n/index.d.ts +0 -3
  88. package/dist/core/composables/useI18n/index.d.ts.map +0 -1
@@ -1,6 +1,18 @@
1
+ import {
2
+ markRaw,
3
+ computed,
4
+ getCurrentInstance,
5
+ inject,
6
+ warn,
7
+ Component,
8
+ watch,
9
+ isVNode,
10
+ h,
11
+ shallowRef,
12
+ ComputedRef,
13
+ } from "vue";
1
14
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
- import { reactiveComputed } from "@vueuse/core";
3
- import { computed, getCurrentInstance, inject, warn, Component, watch, isVNode, h, shallowRef, ComputedRef } from "vue";
15
+ import { createSharedComposable, reactiveComputed } from "@vueuse/core";
4
16
  import * as _ from "lodash-es";
5
17
  import { RouteLocationNormalized, useRoute, NavigationFailure } from "vue-router";
6
18
  import { bladeNavigationInstance } from "../../plugin";
@@ -11,27 +23,25 @@ import {
11
23
  IBladeEvent,
12
24
  IParentCallArgs,
13
25
  BladeInstanceConstructor,
14
- BladeRouteRecordLocationNormalized,
15
26
  BladeRoutesRecord,
16
27
  } from "../../types";
17
28
  import { navigationViewLocation } from "../../injectionKeys";
18
- import { generateId } from "../../../../../core/utilities";
19
-
20
- type TParsedRoute = {
21
- blade: string;
22
- param: string | null;
23
- name: string;
24
- };
29
+ import { usePermissions } from "../../../../../core/composables";
30
+ import { notification } from "./../../../notifications";
31
+ import "core-js/actual/array/find-last";
32
+ import "core-js/actual/array/find-last-index";
33
+ import { i18n } from "../../../../../core/plugins/i18n";
25
34
 
26
35
  interface IUseBladeNavigation {
27
- readonly blades: ComputedRef<BladeRouteRecordLocationNormalized | undefined>;
36
+ readonly blades: ComputedRef<BladeVNode[]>;
28
37
  readonly currentBladeNavigationData: ComputedRef<BladeVNode["props"]["navigation"]>;
29
38
  openBlade: <Blade extends Component>(
30
39
  { blade, param, options, onOpen, onClose }: IBladeEvent<Blade>,
31
40
  isWorkspace?: boolean,
32
41
  ) => Promise<void | NavigationFailure>;
33
- closeBlade: (index: number, changeLocation?: boolean) => Promise<false | void | NavigationFailure>;
42
+ closeBlade: (index: number) => Promise<boolean>;
34
43
  onParentCall: (parentExposedMethods: Record<string, any>, args: IParentCallArgs) => void;
44
+ onBeforeClose: (cb: () => Promise<boolean | undefined>) => void;
35
45
  resolveBladeByName: (name: string) => BladeInstanceConstructor;
36
46
  routeResolver: (to: RouteLocationNormalized) => Promise<void | NavigationFailure | undefined> | undefined;
37
47
  getCurrentBlade: () => BladeVNode;
@@ -39,25 +49,33 @@ interface IUseBladeNavigation {
39
49
 
40
50
  const activeWorkspace = shallowRef<BladeVNode>();
41
51
  const baseUrl = shallowRef<string>();
42
- export function useBladeNavigation(): IUseBladeNavigation {
43
- const navigationView = inject(navigationViewLocation, undefined) as BladeVNode;
44
52
 
53
+ function parseUrl(url: string) {
54
+ const urlRegex = /^\/([a-zA-Z0-9_-]+)(?:\/([a-zA-Z0-9_-]+))?(?:\/([a-zA-Z0-9_-]+))?$/;
55
+ const match = url.match(urlRegex);
56
+
57
+ if (!match) {
58
+ return undefined;
59
+ }
60
+
61
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
62
+ const [_, workspace, blade, param] = match;
63
+
64
+ return {
65
+ workspace,
66
+ blade: blade || undefined,
67
+ param: param || undefined,
68
+ };
69
+ }
70
+
71
+ const useBladeNavigationSingleton = createSharedComposable(() => {
45
72
  const route = useRoute();
46
73
 
47
74
  const instance: BladeComponentInternalInstance = getCurrentInstance() as BladeComponentInternalInstance;
48
75
  const navigationInstance =
49
76
  (instance !== null && inject<BladeNavigationPlugin>("bladeNavigationPlugin")) || bladeNavigationInstance;
50
-
51
77
  const router = navigationInstance?.router;
52
78
 
53
- const mainRoute = router.getRoutes().find((r) => r.meta?.root)!;
54
-
55
- const blades = computed(() => {
56
- return router.getRoutes().find((routeItem) => {
57
- return route.name === routeItem.name;
58
- });
59
- }) as ComputedRef<BladeRouteRecordLocationNormalized>;
60
-
61
79
  function parseWorkspaceUrl(path: string): string {
62
80
  // Object.values(route.params)[0] will always be base path of the app
63
81
  if (!baseUrl.value) {
@@ -71,41 +89,135 @@ export function useBladeNavigation(): IUseBladeNavigation {
71
89
 
72
90
  watch(
73
91
  () => route.path,
74
- (newVal) => {
92
+ async (newVal, oldVal) => {
75
93
  const workspaceUrl = parseWorkspaceUrl(newVal);
76
94
 
77
- const wsRouteComponent = router.resolve({ path: workspaceUrl })?.matched?.[1]?.components?.default as BladeVNode;
78
-
79
- if (wsRouteComponent && wsRouteComponent.type?.isWorkspace) {
80
- activeWorkspace.value = wsRouteComponent;
95
+ const wsRouteComponent =
96
+ (route?.matched?.[1]?.components?.default as BladeVNode) ??
97
+ (router.resolve({ path: workspaceUrl })?.matched?.[1]?.components?.default as BladeVNode);
98
+
99
+ if (wsRouteComponent !== undefined) {
100
+ if (isVNode(wsRouteComponent)) {
101
+ navigationInstance.blades.value[0] = markRaw(wsRouteComponent);
102
+ activeWorkspace.value = wsRouteComponent;
103
+ } else {
104
+ const isPrevented = await closeBlade(0);
105
+
106
+ if (!isPrevented) {
107
+ // add not blade page to blades array to show simple routes as
108
+ // we use only one router-view for all routes
109
+ navigationInstance.blades.value = [markRaw(wsRouteComponent)];
110
+ } else {
111
+ if (oldVal) router.replace({ path: oldVal });
112
+ }
113
+ }
81
114
  }
82
115
  },
83
116
  { immediate: true },
84
117
  );
85
118
 
86
- /**
87
- * The function `openWorkspace` adds a route to the router and pushes the route to navigate to a
88
- * specific workspace component.
89
- * @param - - `blade`: The component to be rendered in the workspace.
90
- * @returns the result of the `router.push()` method, which is a Promise.
91
- */
119
+ watch(
120
+ navigationInstance.blades,
121
+ (newVal) => {
122
+ const workspace = navigationInstance.blades.value[0];
123
+
124
+ // method that checks if last item in newVal array has url and returns item. If it has no url - it returns previous item with url, but not first item from array as it's workspace
125
+ const getLastItemWithUrl = () => {
126
+ // Find the previous item with a URL
127
+ for (let i = newVal.length - 1; i > 0; i--) {
128
+ if (newVal[i].type.url) {
129
+ return newVal[i]; // return the previous item with a URL
130
+ }
131
+ }
132
+ return;
133
+ };
134
+
135
+ if (workspace?.type?.url) {
136
+ let url: string;
137
+ // when restoring blades we need to use full path of the blade to show last blade from the url as workspace.
138
+ // fullPath is provided by generateRoute function
139
+ const wsBladeUrl = workspace?.props?.navigation?.fullPath || workspace?.type.url;
140
+
141
+ const lastBlade = getLastItemWithUrl();
142
+ if (getLastItemWithUrl()) {
143
+ url =
144
+ "/" +
145
+ parseUrl(wsBladeUrl)?.workspace +
146
+ lastBlade?.type.url +
147
+ (lastBlade?.props?.param ? "/" + lastBlade?.props.param : "");
148
+ } else {
149
+ url = wsBladeUrl;
150
+ }
151
+
152
+ if (url) history.replaceState({}, "", "#" + url);
153
+ }
154
+ },
155
+ { deep: true },
156
+ );
157
+
158
+ async function closeBlade(index: number) {
159
+ console.debug(`[@vc-shell/framework#useBladeNavigation] - closeBlade called.`);
160
+
161
+ if (navigationInstance.blades.value.length === 1) {
162
+ return false;
163
+ }
164
+
165
+ const children = navigationInstance.blades.value.slice(index).reverse();
166
+ let isPrevented = false;
167
+ for (let index = 0; index < children.length; index++) {
168
+ const element = children[index];
169
+
170
+ if (element.props?.navigation?.onBeforeClose) {
171
+ const result = await element.props.navigation.onBeforeClose();
172
+
173
+ if (result === false) {
174
+ isPrevented = true;
175
+ console.debug(`[@vc-shell/framework#useBladeNavigation] - Navigation is prevented`);
176
+ }
177
+ }
178
+ }
179
+
180
+ if (!isPrevented) {
181
+ navigationInstance.blades.value.splice(index);
182
+ }
183
+
184
+ return isPrevented;
185
+ }
186
+
187
+ return {
188
+ navigationInstance,
189
+ router,
190
+ closeBlade,
191
+ };
192
+ });
193
+
194
+ export function useBladeNavigation(): IUseBladeNavigation {
195
+ const navigationView = inject(navigationViewLocation, undefined) as BladeVNode;
196
+
197
+ const { hasAccess } = usePermissions();
198
+
199
+ const instance: BladeComponentInternalInstance = getCurrentInstance() as BladeComponentInternalInstance;
200
+
201
+ const { router, navigationInstance, closeBlade } = useBladeNavigationSingleton();
202
+
203
+ const mainRoute = router.getRoutes().find((r) => r.meta?.root)!;
204
+
92
205
  async function openWorkspace<Blade extends Component>({ blade, param, options }: IBladeEvent<Blade>) {
93
- const createdComponent = h(blade, { param, options }) as BladeVNode;
206
+ const createdComponent = h(blade, {
207
+ param,
208
+ options,
209
+ navigation: {
210
+ idx: 0,
211
+ },
212
+ }) as BladeVNode;
94
213
 
95
214
  try {
96
- if (createdComponent.type?.url) {
97
- router.addRoute(mainRoute.name as string, {
98
- name: createdComponent.type?.name,
99
- path: createdComponent.type?.url,
100
- components: {
101
- default: createdComponent,
102
- },
103
- meta: {
104
- permissions: createdComponent.type?.permissions,
105
- },
106
- });
107
-
108
- return await router.push({ path: createdComponent.type?.url as string });
215
+ const isPrevented = await closeBlade(0);
216
+
217
+ if (!isPrevented && createdComponent.type?.url) {
218
+ navigationInstance.blades.value = [createdComponent];
219
+
220
+ return await router.replace({ path: createdComponent.type?.url as string });
109
221
  }
110
222
  } catch (e) {
111
223
  console.log(e);
@@ -113,17 +225,9 @@ export function useBladeNavigation(): IUseBladeNavigation {
113
225
  }
114
226
  }
115
227
 
116
- /**
117
- * The `openBlade` function is used to open a blade component in a workspace or navigation view.
118
- * @param - - `blade`: The component that represents the blade to be opened.
119
- * @param [isWorkspace=false] - A boolean value indicating whether the blade is being opened as a
120
- * workspace or not.
121
- * @returns a Promise that resolves to the result of the `router.push()` method.
122
- */
123
228
  async function openBlade<Blade extends Component>(
124
229
  { blade, param, options, onOpen, onClose }: IBladeEvent<Blade>,
125
230
  isWorkspace = false,
126
- // update = true,
127
231
  ) {
128
232
  if (!blade) {
129
233
  throw new Error("You should pass blade component as openBlade argument");
@@ -136,34 +240,22 @@ export function useBladeNavigation(): IUseBladeNavigation {
136
240
  try {
137
241
  const instanceComponent = navigationView || activeWorkspace.value;
138
242
 
139
- if (instanceComponent && activeWorkspace.value) {
140
- const initialBlade = router.resolve({
141
- path: instanceComponent.props?.navigation?.fullPath ?? instanceComponent.type.url,
142
- })?.matched[1];
243
+ if (isVNode(instanceComponent) || activeWorkspace.value) {
244
+ const instanceComponentIndex =
245
+ navigationInstance.blades.value /* @ts-expect-error - findLastIndex is not parsed correctly by ts */
246
+ .findLastIndex((x) => _.isEqual(x.type, instanceComponent.type));
143
247
 
144
- const base =
145
- router.resolve({ name: initialBlade?.name }).path + (blade.url ? blade.url + (param ? "/" + param : "") : "");
248
+ const instanceComponentChild =
249
+ instanceComponentIndex >= 0 ? navigationInstance.blades.value[instanceComponentIndex + 1] : undefined;
146
250
 
147
- const url = (baseUrl.value === "/" ? "" : baseUrl.value) + base;
251
+ let isPrevented = false;
148
252
 
149
- const rawRouterUrl = (mainRoute.path === "/" ? "" : mainRoute.path) + base;
150
-
151
- /**
152
- * Removes routes without paths and default route from next route.
153
- */
154
- const alreadyAdded = _.omitBy(initialBlade?.components, (value: BladeVNode, key) =>
155
- blade.url ? !value?.props?.navigation?.bladePath : false || key === "default",
156
- );
253
+ if (instanceComponentChild) {
254
+ isPrevented = await closeBlade(instanceComponentChild.props?.navigation?.idx);
255
+ }
157
256
 
158
257
  const currentBladeIdx = instanceComponent.props?.navigation?.idx ? instanceComponent.props?.navigation?.idx : 0;
159
258
 
160
- /**
161
- * Closes all child blades in current route to prevent blades without url to preserve.
162
- */
163
- await closeBlade(currentBladeIdx + 1, false);
164
-
165
- const isInitialBlade = activeWorkspace.value?.type.url === url;
166
-
167
259
  const bladeNode = h(
168
260
  blade,
169
261
  Object.assign(
@@ -171,41 +263,21 @@ export function useBladeNavigation(): IUseBladeNavigation {
171
263
  reactiveComputed(() => ({ options, param })),
172
264
  {
173
265
  navigation: {
174
- bladePath: blade.url ? blade.url + (param ? "/" + param : "") : undefined,
175
- fullPath: url,
176
266
  onClose,
177
267
  onOpen,
178
268
  idx: currentBladeIdx + 1,
179
- uniqueRouteKey: generateId(),
180
269
  },
181
270
  },
182
271
  ),
183
- );
272
+ ) as BladeVNode;
184
273
 
185
- router.addRoute(mainRoute.name as string, {
186
- name: isInitialBlade ? activeWorkspace.value?.type.name : url,
187
- path: rawRouterUrl,
188
- components: Object.assign(
189
- {},
190
- { default: activeWorkspace.value },
191
- { ...alreadyAdded },
192
- blade.url
193
- ? {
194
- [url]: bladeNode,
195
- }
196
- : {
197
- [blade.name]: bladeNode,
198
- },
199
- ),
200
- meta: {
201
- permissions: activeWorkspace.value && mergePermissions(activeWorkspace.value, blade),
202
- },
203
- });
204
-
205
- return await router.push({
206
- path: url,
207
- replace: !blade.url,
208
- });
274
+ if (!isPrevented) {
275
+ if (hasAccess(blade.permissions)) navigationInstance.blades.value.push(bladeNode);
276
+ else
277
+ notification.error(i18n.global.t("PERMISSION_MESSAGES.ACCESS_RESTRICTED"), {
278
+ timeout: 3000,
279
+ });
280
+ }
209
281
  } else {
210
282
  throw new Error("No workspace found");
211
283
  }
@@ -214,97 +286,10 @@ export function useBladeNavigation(): IUseBladeNavigation {
214
286
  }
215
287
  }
216
288
 
217
- /**
218
- * The function merges the permissions of a workspace blade and a child blade.
219
- * @param {BladeVNode} workspaceBlade - The `workspaceBlade` parameter is a BladeVNode object
220
- * representing the workspace blade.
221
- * @param {BladeVNode | BladeInstanceConstructor} childBlade - The `childBlade` parameter is either a
222
- * `BladeVNode` or a `BladeInstanceConstructor`.
223
- * @returns an array of permissions.
224
- */
225
- function mergePermissions(workspaceBlade: BladeVNode, childBlade: BladeVNode | BladeInstanceConstructor) {
226
- const child = (isVNode(childBlade) ? childBlade : h(childBlade)) as BladeVNode;
227
- if (child && child.type?.permissions) {
228
- const childPermissionsArr =
229
- typeof child.type.permissions === "string" ? [child.type.permissions] : child.type.permissions;
230
- if (workspaceBlade.type.permissions) {
231
- const workspacePermissionsArr =
232
- typeof workspaceBlade.type.permissions === "string"
233
- ? [workspaceBlade.type.permissions]
234
- : workspaceBlade.type.permissions;
235
- return workspacePermissionsArr.concat(childPermissionsArr);
236
- } else {
237
- return childPermissionsArr;
238
- }
239
- } else return workspaceBlade.type.permissions;
240
- }
241
-
242
- /**
243
- * The function removes a specified substring from a given input string.
244
- * @param {string} inputString - The input string from which the substring will be removed.
245
- * @param {string} substringToRemove - The `substringToRemove` parameter is a string that represents
246
- * the substring that you want to remove from the `inputString`.
247
- * @returns the input string with the specified substring removed.
248
- */
249
- function removeSubstring(inputString: string, substringToRemove: string) {
250
- const regex = new RegExp(`${substringToRemove}+$`);
251
- return inputString.replace(regex, "");
252
- }
253
-
254
- /**
255
- * The `closeBlade` function is used to close a blade and update the router location if necessary.
256
- * @param {number} index - The `index` parameter is a number that represents the index of the blade
257
- * to be closed.
258
- * @param [changeLocation=true] - A boolean value indicating whether the location should be changed
259
- * when closing the blade. The default value is `true`.
260
- * @returns a Promise that resolves to a boolean value if `changeLocation` is true and the router
261
- * successfully replaces the path, otherwise it returns undefined.
262
- */
263
- async function closeBlade(index: number, changeLocation = true) {
264
- console.debug(`[@vc-shell/framework#useBladeNavigation] - closeBlade called.`);
265
-
266
- const bladeByIndex = Object.values(blades.value?.components || {}).find(
267
- (x) => "props" in x && x.props?.navigation?.idx === index,
268
- ) as BladeVNode;
269
-
270
- if (bladeByIndex && bladeByIndex?.props?.navigation?.bladePath) {
271
- const path = removeSubstring(bladeByIndex.props.navigation.fullPath, bladeByIndex.props.navigation?.bladePath);
272
-
273
- return changeLocation && (await router.replace(path));
274
- }
275
-
276
- const routeWithNamedBlade = router.getRoutes().find((r) => r.path === bladeByIndex?.props?.navigation?.fullPath);
277
-
278
- if (routeWithNamedBlade) {
279
- const isInitialBlade = activeWorkspace.value?.type.name === routeWithNamedBlade.name;
280
-
281
- router.addRoute(mainRoute.name as string, {
282
- name: isInitialBlade ? routeWithNamedBlade?.name : routeWithNamedBlade?.path,
283
- path: routeWithNamedBlade?.path,
284
- components: _.omitBy(routeWithNamedBlade?.components, (value: BladeVNode) => {
285
- if (value.props && value.props.navigation && bladeByIndex.props) {
286
- return value.props.navigation.idx >= bladeByIndex.props.navigation.idx;
287
- }
288
- }) as Record<string, BladeVNode>,
289
- });
290
-
291
- return changeLocation && (await router.replace(routeWithNamedBlade?.path));
292
- }
293
- }
294
-
295
- /**
296
- * The function `onParentCall` handles method calls from a parent component and executes the
297
- * corresponding method if it exists, otherwise it logs an error message.
298
- * @param parentExposedMethods - parentExposedMethods is an object that contains the methods exposed
299
- * by the parent blade. Each method is represented as a key-value pair, where the key is the method
300
- * name and the value is the method itself.
301
- * @param {IParentCallArgs} args - The `args` parameter is an object that contains the following
302
- * properties:
303
- */
304
289
  async function onParentCall(parentExposedMethods: Record<string, any>, args: IParentCallArgs) {
305
290
  console.debug(`vc-app#onParentCall({ method: ${args.method} }) called.`);
306
291
 
307
- if (args.method && typeof parentExposedMethods[args.method] === "function") {
292
+ if (args.method && parentExposedMethods && typeof parentExposedMethods[args.method] === "function") {
308
293
  const method = parentExposedMethods[args.method] as (args: unknown) => Promise<unknown>;
309
294
  const result = await method(args.args);
310
295
  if (typeof args.callback === "function") {
@@ -317,13 +302,6 @@ export function useBladeNavigation(): IUseBladeNavigation {
317
302
  }
318
303
  }
319
304
 
320
- /**
321
- * The function `resolveBladeByName` resolves a Blade component by its name and returns its
322
- * constructor.
323
- * @param {string} name - The `name` parameter is a string that represents the name of a Blade
324
- * component.
325
- * @returns a BladeInstanceConstructor, which is the constructor function for a Blade instance.
326
- */
327
305
  function resolveBladeByName(name: string): BladeInstanceConstructor {
328
306
  if (!instance) {
329
307
  warn("resolveComponentByName can only be used in setup().");
@@ -343,159 +321,66 @@ export function useBladeNavigation(): IUseBladeNavigation {
343
321
  }
344
322
  }
345
323
 
346
- /**
347
- * The function `routeResolver` checks if a necessary route exists and generates a route if it
348
- * doesn't.
349
- * @param {RouteLocationNormalized} to - The `to` parameter is of type `RouteLocationNormalized`,
350
- * which represents the target route that needs to be resolved. It contains information about the
351
- * target route, such as the path, query parameters, and hash.
352
- * @returns the result of the `generateRoute` function if the `hasNecessaryRoute` function returns
353
- * false.
354
- */
355
324
  function routeResolver(to: RouteLocationNormalized) {
356
325
  if (!hasNecessaryRoute(to)) {
357
326
  return generateRoute(to, navigationInstance.internalRoutes);
358
327
  }
359
328
  }
360
329
 
361
- /**
362
- * The function checks if a given route exists in a list of routes.
363
- * @param {RouteLocationNormalized} to - The "to" parameter is of type RouteLocationNormalized, which
364
- * represents the destination route location.
365
- * @returns a route object from the `routes` array that has a matching `path` property with the
366
- * `to.path` value.
367
- */
368
330
  function hasNecessaryRoute(to: RouteLocationNormalized) {
369
331
  return router.getRoutes().find((route) => route.path === to.path);
370
332
  }
371
333
 
372
- /**
373
- * The function generates a route based on the provided destination and routes, and then pushes the
374
- * generated route to the router.
375
- * @param {RouteLocationNormalized} to - The `to` parameter is of type `RouteLocationNormalized` and
376
- * represents the destination route that we want to generate. It contains information about the path,
377
- * query parameters, and other route-related data.
378
- * @param {RouteRecordNormalized[]} routes - The `routes` parameter is an array of
379
- * `RouteRecordNormalized` objects. Each object represents a route in the application and contains
380
- * information such as the route path, components to render, and any meta information associated with
381
- * the route.
382
- * @returns the result of the `router.push()` method.
383
- */
384
334
  async function generateRoute(to: RouteLocationNormalized, routes: BladeRoutesRecord[]) {
385
- const parsedRoutes: TParsedRoute[] = parseRoutes(to.path, routes);
386
-
387
- const workspace = parsedRoutes[0];
388
- const workspaceComponent = routes.find((route) => route.route === workspace.blade)?.component;
389
-
390
- if (workspaceComponent) {
391
- const children: Record<string, BladeVNode> = {};
392
-
393
- parsedRoutes
394
- .filter((r) => r !== workspace)
395
- .forEach((parsedRoute, index) => {
396
- const registeredRouteComponent = routes.find((route) => route.route === parsedRoute.blade)?.component;
397
-
398
- if (registeredRouteComponent && parsedRoute.name) {
399
- children[parsedRoute.name] = _.merge({}, registeredRouteComponent, {
400
- props: {
401
- param: parsedRoute.param,
402
- navigation: {
403
- bladePath: parsedRoute.blade + (parsedRoute.param ? "/" + parsedRoute.param : ""),
404
- fullPath: parsedRoute.name,
405
- idx: index + 1,
406
- uniqueRouteKey: generateId(),
407
- },
408
- },
409
- }) as BladeVNode;
410
-
411
- // Add routes one by one
335
+ const parsedRoutes = parseUrl(to.path);
336
+
337
+ if (parsedRoutes) {
338
+ // here we check if route is registered in bladeRoutes
339
+ const registeredRouteComponent = routes.find((route) => route.route === "/" + parsedRoutes?.blade)?.component;
340
+
341
+ if (registeredRouteComponent) {
342
+ if (registeredRouteComponent.type?.isWorkspace) {
343
+ // if route is workspace we just push it to router
344
+ router.replace({ path: registeredRouteComponent.type.url as string });
345
+ } else {
346
+ // if route is not workspace we need to add it to router and push
347
+ const url =
348
+ "/" +
349
+ parsedRoutes?.workspace +
350
+ "/" +
351
+ parsedRoutes.blade +
352
+ (parsedRoutes.param ? "/" + parsedRoutes.param : "");
353
+
354
+ // if route is not routable we will redirect to workspace
355
+ if (!parsedRoutes.param && !registeredRouteComponent.type?.routable) {
356
+ return router.replace("/" + parsedRoutes?.workspace);
357
+ } else {
412
358
  router.addRoute(mainRoute.name as string, {
413
- name: parsedRoute.name,
414
- path: parsedRoute.name,
359
+ name: url,
360
+ path: url,
415
361
  components: {
416
- default: workspaceComponent, // { param: Object.values(children)[0].props?.param }),
417
- ...children,
362
+ default: _.merge({}, registeredRouteComponent, {
363
+ props: {
364
+ param: parsedRoutes.param,
365
+ navigation: {
366
+ fullPath: url,
367
+ idx: 0,
368
+ },
369
+ },
370
+ }),
418
371
  },
419
372
  meta: {
420
- permissions: children[parsedRoute.name].type?.permissions,
373
+ permissions: registeredRouteComponent.type?.permissions,
421
374
  },
422
375
  });
423
- }
424
- });
425
-
426
- const mergedPermissions = Object.values(children)
427
- .filter((childComponent) => childComponent.type.permissions)
428
- .flatMap((comp) => mergePermissions(workspaceComponent, comp));
429
-
430
- // Add summary route
431
- router.addRoute(mainRoute.name as string, {
432
- name: to.path,
433
- path: to.path,
434
- components: {
435
- default: workspaceComponent,
436
- ...children,
437
- },
438
- meta: {
439
- permissions: mergedPermissions.length ? mergedPermissions : undefined,
440
- },
441
- });
442
-
443
- return router.push(to.path);
444
- } else return router.push({ name: mainRoute.name as string });
445
- }
446
376
 
447
- /**
448
- * The function `parseRoutes` takes a route string and an array of common routes, and returns an
449
- * array of route objects with blade, param, and name properties.
450
- * @param {string} route - The `route` parameter is a string representing a route path. It is the
451
- * route that needs to be parsed into its individual parts.
452
- * @param {RouteRecordNormalized[]} commonRoutes - commonRoutes is an array of RouteRecordNormalized
453
- * objects. Each object represents a common route in the application and has properties like "path"
454
- * which represents the route path.
455
- * @returns The function `parseRoutes` returns an array of objects. Each object in the array
456
- * represents a part of the route and contains the following properties: blade, param, and name.
457
- */
458
- function parseRoutes(route: string, commonRoutes: BladeRoutesRecord[]) {
459
- const parts: string[] = route.split("/").filter((part) => part !== "");
460
- const result = [];
461
- let currentBlade = "";
462
- let currentName = "";
463
-
464
- for (let i = 0; i < parts.length; i++) {
465
- currentBlade = "/" + parts[i];
466
-
467
- /**
468
- * If current blade is not registered in routes, then it's a param
469
- */
470
- if (!navigationInstance.internalRoutes.some((x) => currentBlade === x?.route)) {
471
- baseUrl.value = currentBlade;
472
- continue;
473
- }
474
-
475
- let currentParam = null;
476
-
477
- if (i + 1 < parts.length) {
478
- const nextPart = "/" + parts.slice(i + 1, i + 2).join("/");
479
- currentName += currentBlade;
480
-
481
- if (!commonRoutes.some((route) => nextPart === route.route)) {
482
- currentParam = parts[i + 1];
483
- currentName += "/" + currentParam;
484
- i++; // Skip the next part as it's a param
377
+ return router.replace(url);
378
+ }
485
379
  }
486
- } else if (i < parts.length) {
487
- const nextPart = "/" + parts.slice(i).join("/");
488
- currentName += nextPart;
380
+ } else {
381
+ return router.replace({ name: mainRoute.name as string });
489
382
  }
490
-
491
- result.push({
492
- blade: currentBlade,
493
- param: currentParam,
494
- name: currentName,
495
- });
496
383
  }
497
-
498
- return result;
499
384
  }
500
385
 
501
386
  /**
@@ -506,12 +391,20 @@ export function useBladeNavigation(): IUseBladeNavigation {
506
391
  return instance.vnode;
507
392
  }
508
393
 
509
- const currentBladeNavigationData = computed(
510
- () => (instance && (instance.vnode.props?.navigation as BladeVNode["props"]["navigation"])) ?? undefined,
511
- );
394
+ const currentBladeNavigationData = computed(() => navigationView?.props?.navigation ?? undefined);
395
+
396
+ function onBeforeClose(cb: () => Promise<boolean | undefined>) {
397
+ const instanceComponent = navigationView;
398
+
399
+ const currentBlade = navigationInstance.blades.value.find((x: any) => _.isEqual(x, instanceComponent));
400
+
401
+ if (currentBlade) {
402
+ currentBlade.props.navigation.onBeforeClose = cb;
403
+ }
404
+ }
512
405
 
513
406
  return {
514
- blades,
407
+ blades: computed(() => navigationInstance.blades.value),
515
408
  openBlade,
516
409
  closeBlade,
517
410
  onParentCall,
@@ -519,5 +412,6 @@ export function useBladeNavigation(): IUseBladeNavigation {
519
412
  routeResolver,
520
413
  getCurrentBlade,
521
414
  currentBladeNavigationData,
415
+ onBeforeClose,
522
416
  };
523
417
  }