@vc-shell/framework 1.0.293 → 1.0.295

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 (101) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/core/composables/useLanguages/index.ts +26 -0
  3. package/core/composables/useMenuService/index.ts +1 -1
  4. package/core/composables/useSettings/index.ts +2 -3
  5. package/core/composables/useTheme/index.ts +2 -1
  6. package/core/composables/useUser/index.ts +4 -76
  7. package/core/constants/index.ts +1 -0
  8. package/core/constants/locale.ts +78 -0
  9. package/core/interceptors/index.ts +3 -0
  10. package/core/plugins/modularity/index.ts +106 -16
  11. package/dist/core/composables/useLanguages/index.d.ts +5 -0
  12. package/dist/core/composables/useLanguages/index.d.ts.map +1 -1
  13. package/dist/core/composables/useSettings/index.d.ts.map +1 -1
  14. package/dist/core/composables/useTheme/index.d.ts.map +1 -1
  15. package/dist/core/composables/useUser/index.d.ts +1 -3
  16. package/dist/core/composables/useUser/index.d.ts.map +1 -1
  17. package/dist/core/constants/index.d.ts +2 -0
  18. package/dist/core/constants/index.d.ts.map +1 -0
  19. package/dist/core/constants/locale.d.ts +7 -0
  20. package/dist/core/constants/locale.d.ts.map +1 -0
  21. package/dist/core/interceptors/index.d.ts.map +1 -1
  22. package/dist/core/plugins/modularity/index.d.ts.map +1 -1
  23. package/dist/framework.js +34165 -30178
  24. package/dist/index.css +1 -1
  25. package/dist/index.d.ts +9 -4
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/locales/en.json +2 -1
  28. package/dist/shared/components/app-switcher/components/vc-app-switcher/vc-app-switcher.vue.d.ts.map +1 -1
  29. package/dist/shared/components/index.d.ts +1 -0
  30. package/dist/shared/components/index.d.ts.map +1 -1
  31. package/dist/shared/components/language-selector/language-selector.vue.d.ts.map +1 -1
  32. package/dist/shared/components/sidebar/sidebar.vue.d.ts +1 -1
  33. package/dist/shared/components/sidebar/sidebar.vue.d.ts.map +1 -1
  34. package/dist/shared/components/sign-in/azuread.vue.d.ts +17 -0
  35. package/dist/shared/components/sign-in/azuread.vue.d.ts.map +1 -0
  36. package/dist/shared/components/sign-in/external-provider.vue.d.ts +23 -0
  37. package/dist/shared/components/sign-in/external-provider.vue.d.ts.map +1 -0
  38. package/dist/shared/components/sign-in/external-providers.vue.d.ts +16 -0
  39. package/dist/shared/components/sign-in/external-providers.vue.d.ts.map +1 -0
  40. package/dist/shared/components/sign-in/index.d.ts +2 -0
  41. package/dist/shared/components/sign-in/index.d.ts.map +1 -0
  42. package/dist/shared/components/sign-in/useExternalProvider.d.ts +12 -0
  43. package/dist/shared/components/sign-in/useExternalProvider.d.ts.map +1 -0
  44. package/dist/shared/components/theme-selector/theme-selector.vue.d.ts.map +1 -1
  45. package/dist/shared/components/user-dropdown-button/user-dropdown-button.vue.d.ts.map +1 -1
  46. package/dist/shared/modules/dynamic/index.d.ts +13 -10
  47. package/dist/shared/modules/dynamic/index.d.ts.map +1 -1
  48. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts +1 -0
  49. package/dist/shared/modules/dynamic/pages/dynamic-blade-form.vue.d.ts.map +1 -1
  50. package/dist/shared/pages/LoginPage/components/login/Login.vue.d.ts.map +1 -1
  51. package/dist/tailwind.config.d.ts +1 -1
  52. package/dist/tailwind.config.d.ts.map +1 -1
  53. package/dist/tsconfig.tsbuildinfo +1 -1
  54. package/dist/ui/components/atoms/vc-image/index.d.ts +1 -68
  55. package/dist/ui/components/atoms/vc-image/index.d.ts.map +1 -1
  56. package/dist/ui/components/atoms/vc-image/vc-image.stories.d.ts +3 -3
  57. package/dist/ui/components/atoms/vc-image/vc-image.vue.d.ts +2 -2
  58. package/dist/ui/components/atoms/vc-image/vc-image.vue.d.ts.map +1 -1
  59. package/dist/ui/components/atoms/vc-tooltip/vc-tooltip.vue.d.ts.map +1 -1
  60. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue.d.ts +2 -2
  61. package/dist/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue.d.ts.map +1 -1
  62. package/dist/ui/components/organisms/vc-table/_internal/vc-table-cell/vc-table-cell.vue.d.ts.map +1 -1
  63. package/dist/ui/components/organisms/vc-table/_internal/vc-table-filter/vc-table-filter.vue.d.ts +1 -1
  64. package/dist/ui/components/organisms/vc-table/_internal/vc-table-filter/vc-table-filter.vue.d.ts.map +1 -1
  65. package/dist/ui/components/organisms/vc-table/_internal/vc-table-mobile-item/vc-table-mobile-item.vue.d.ts.map +1 -1
  66. package/dist/ui/components/organisms/vc-table/vc-table.stories.d.ts +30 -30
  67. package/dist/ui/components/organisms/vc-table/vc-table.vue.d.ts +9 -6
  68. package/dist/ui/components/organisms/vc-table/vc-table.vue.d.ts.map +1 -1
  69. package/package.json +6 -6
  70. package/shared/components/app-switcher/components/vc-app-switcher/vc-app-switcher.vue +1 -2
  71. package/shared/components/blade-navigation/composables/useBladeNavigation/index.ts +1 -1
  72. package/shared/components/index.ts +1 -0
  73. package/shared/components/language-selector/language-selector.vue +59 -6
  74. package/shared/components/sidebar/sidebar.vue +1 -1
  75. package/shared/components/sign-in/azuread.vue +24 -0
  76. package/shared/components/sign-in/external-provider.vue +38 -0
  77. package/shared/components/sign-in/external-providers.vue +39 -0
  78. package/shared/components/sign-in/index.ts +1 -0
  79. package/shared/components/sign-in/useExternalProvider.ts +102 -0
  80. package/shared/components/theme-selector/theme-selector.vue +7 -8
  81. package/shared/components/user-dropdown-button/user-dropdown-button.vue +5 -0
  82. package/shared/modules/dynamic/helpers/nodeBuilder.ts +2 -2
  83. package/shared/modules/dynamic/helpers/override.ts +1 -1
  84. package/shared/modules/dynamic/index.ts +166 -63
  85. package/shared/modules/dynamic/pages/dynamic-blade-form.vue +99 -20
  86. package/shared/modules/dynamic/pages/dynamic-blade-list.vue +1 -1
  87. package/shared/pages/LoginPage/components/login/Login.vue +8 -40
  88. package/tailwind.config.ts +6 -198
  89. package/ui/components/atoms/vc-image/index.ts +1 -3
  90. package/ui/components/atoms/vc-image/vc-image.vue +3 -2
  91. package/ui/components/atoms/vc-tooltip/vc-tooltip.vue +7 -2
  92. package/ui/components/molecules/vc-field/_internal/vc-field-type/vc-field-type.vue +1 -1
  93. package/ui/components/molecules/vc-input/vc-input.vue +38 -0
  94. package/ui/components/molecules/vc-input-currency/vc-input-currency.vue +1 -1
  95. package/ui/components/molecules/vc-select/vc-select.vue +15 -5
  96. package/ui/components/organisms/vc-app/_internal/vc-app-bar/vc-app-bar.vue +1 -1
  97. package/ui/components/organisms/vc-app/_internal/vc-app-menu/vc-app-menu.vue +2 -24
  98. package/ui/components/organisms/vc-table/_internal/vc-table-cell/vc-table-cell.vue +12 -3
  99. package/ui/components/organisms/vc-table/_internal/vc-table-filter/vc-table-filter.vue +1 -1
  100. package/ui/components/organisms/vc-table/_internal/vc-table-mobile-item/vc-table-mobile-item.vue +2 -0
  101. package/ui/components/organisms/vc-table/vc-table.vue +124 -65
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import * as pages from "./pages";
3
- import { App, Component, defineComponent } from "vue";
3
+ import { App, Component, SetupContext, defineComponent } from "vue";
4
4
  import { DynamicSchema, OverridesSchema } from "./types";
5
5
  import * as _ from "lodash-es";
6
6
  import { handleOverrides } from "./helpers/override";
@@ -15,12 +15,17 @@ interface Registered {
15
15
  component: BladeInstanceConstructor;
16
16
  name: string;
17
17
  model: DynamicSchema;
18
+ composables: { [key: string]: (...args: any[]) => any };
18
19
  }
19
20
 
21
+ const registeredModules: { [key: string]: Registered } = {};
22
+ const installedBladeIds = new Set<string>();
23
+ const registeredSchemas: { [key: string]: DynamicSchema } = {};
24
+
20
25
  const createAppModuleWrapper = (args: {
21
26
  bladeName: string;
22
27
  bladeComponent: BladeInstanceConstructor;
23
- appModuleContent:
28
+ appModuleContent?:
24
29
  | {
25
30
  locales?: { [key: string]: object };
26
31
  notificationTemplates?: { [key: string]: Component };
@@ -37,31 +42,19 @@ const createAppModuleWrapper = (args: {
37
42
  );
38
43
  };
39
44
 
40
- const register = (
45
+ const createBladeInstanceConstructor = (
46
+ bladeComponent: any,
47
+ bladeName: string,
48
+ json: DynamicSchema,
41
49
  args: {
42
- app: App;
43
- component: BladeInstanceConstructor;
44
- composables: { [key: string]: (...args: any[]) => any };
45
- json: DynamicSchema;
46
- options?: { router: any };
47
50
  moduleUid: string;
51
+ composables?: { [key: string]: (...args: any[]) => any };
52
+ mixin?: ((...args: any[]) => any)[];
48
53
  },
49
- appModuleContent:
50
- | {
51
- locales?: { [key: string]: object };
52
- notificationTemplates?: { [key: string]: Component };
53
- moduleComponents?: { [key: string]: Component };
54
- }
55
- | undefined,
56
- ): Registered => {
57
- const { app, component, json, options } = args;
58
- const bladeComponent = _.cloneDeep(component);
59
- let rawUrl: `/${string}`;
60
-
61
- const bladeName = kebabToPascal(json.settings.id);
62
-
54
+ existingComposables?: { [key: string]: (...args: any[]) => any },
55
+ ) => {
63
56
  if (json.settings.url) {
64
- rawUrl = json.settings.url as `/${string}`;
57
+ const rawUrl = json.settings.url as `/${string}`;
65
58
  bladeComponent.url = rawUrl;
66
59
  }
67
60
 
@@ -69,76 +62,180 @@ const register = (
69
62
  bladeComponent.permissions = json.settings.permissions;
70
63
  }
71
64
 
72
- const BladeInstanceConstructor = defineComponent({
65
+ return defineComponent({
73
66
  ...bladeComponent,
74
67
  name: bladeName,
75
68
  isWorkspace: "isWorkspace" in json.settings && json.settings.isWorkspace,
76
69
  menuItem: ("menuItem" in json.settings && json.settings.menuItem) ?? undefined,
77
70
  moduleUid: args.moduleUid,
78
71
  routable: json.settings.routable ?? true,
79
- composables: args.composables,
80
- setup: (props: ComponentProps<typeof bladeComponent>, ctx) =>
72
+ composables: existingComposables ? { ...existingComposables, ...args.composables } : args.composables,
73
+ setup: (props: ComponentProps<typeof bladeComponent>, ctx: SetupContext) =>
81
74
  (bladeComponent?.setup &&
82
75
  bladeComponent.setup(
83
76
  reactiveComputed(() =>
84
77
  Object.assign({}, props, {
85
78
  model: json,
86
- composables: args.composables,
79
+ composables: existingComposables ? { ...existingComposables, ...args.composables } : args.composables,
80
+ mixinFn: args.mixin?.length ? args.mixin : undefined,
87
81
  } as any),
88
82
  ),
89
83
  ctx,
90
84
  )) ??
91
85
  {},
92
86
  });
87
+ };
88
+
89
+ const register = (
90
+ args: {
91
+ app: App;
92
+ component: BladeInstanceConstructor;
93
+ composables: { [key: string]: (...args: any[]) => any };
94
+ mixin?: ((...args: any[]) => any)[];
95
+ json: DynamicSchema;
96
+ options?: { router: Router };
97
+ moduleUid: string;
98
+ },
99
+ appModuleContent?:
100
+ | {
101
+ locales?: { [key: string]: object };
102
+ notificationTemplates?: { [key: string]: Component };
103
+ moduleComponents?: { [key: string]: Component };
104
+ }
105
+ | undefined,
106
+ ): Registered => {
107
+ const { app, component, json, options } = args;
108
+ const bladeId = json.settings.id;
109
+ const bladeName = kebabToPascal(bladeId);
110
+
111
+ if (registeredModules[bladeName]) {
112
+ // Module already registered, updating it
113
+ updateBlade(bladeName, json, args, appModuleContent);
114
+ return registeredModules[bladeName];
115
+ }
116
+
117
+ const bladeComponent = _.cloneDeep(component);
118
+ const BladeInstanceConstructor = createBladeInstanceConstructor(bladeComponent, bladeName, json, args);
93
119
 
94
120
  const module = createAppModuleWrapper({
95
121
  bladeName,
96
- bladeComponent: BladeInstanceConstructor,
122
+ bladeComponent: BladeInstanceConstructor as BladeInstanceConstructor,
97
123
  appModuleContent,
98
124
  });
99
125
 
100
126
  module.install(app, options);
101
127
 
102
- return {
103
- component: BladeInstanceConstructor,
128
+ const newModule: Registered = {
129
+ component: BladeInstanceConstructor as BladeInstanceConstructor,
104
130
  name: bladeName,
105
131
  model: json,
132
+ composables: args.composables,
106
133
  };
134
+
135
+ registeredModules[bladeName] = newModule;
136
+
137
+ return newModule;
107
138
  };
108
139
 
109
- const handleError = (errorKey: string, schema: { [key: string]: DynamicSchema }, text?: string) => {
140
+ const handleError = (errorKey: string, schemas: { [key: string]: DynamicSchema }, text?: string) => {
110
141
  return console.error(
111
- `Module initialization aborted. '${errorKey}' key not found in files: ${Object.keys(schema).join(
142
+ `Module initialization aborted. Key '${errorKey}' not found in schemas: ${Object.keys(schemas).join(
112
143
  ", ",
113
- )}. '${errorKey}' key ${text}`,
144
+ )}. Key '${errorKey}' ${text}`,
114
145
  );
115
146
  };
116
147
 
117
- export const createDynamicAppModule = (args: {
118
- schema: { [key: string]: DynamicSchema };
119
- composables: { [key: string]: (...args: any[]) => any };
148
+ const updateBlade = (
149
+ moduleName: string,
150
+ newSchema: DynamicSchema,
151
+ args: {
152
+ app: App;
153
+ component: BladeInstanceConstructor;
154
+ composables?: { [key: string]: (...args: any[]) => any };
155
+ mixin?: ((...args: any[]) => any)[];
156
+ json: DynamicSchema;
157
+ options?: { router: Router };
158
+ moduleUid: string;
159
+ },
160
+ appModuleContent?:
161
+ | {
162
+ locales?: { [key: string]: object };
163
+ notificationTemplates?: { [key: string]: Component };
164
+ moduleComponents?: { [key: string]: Component };
165
+ }
166
+ | undefined,
167
+ ) => {
168
+ const existingModule = registeredModules[moduleName];
169
+
170
+ if (existingModule) {
171
+ // Updating blade model
172
+ existingModule.model = newSchema;
173
+
174
+ // Updating blade component
175
+ const bladeComponent = _.cloneDeep(args.component);
176
+ const BladeInstanceConstructor = createBladeInstanceConstructor(
177
+ bladeComponent,
178
+ moduleName,
179
+ newSchema,
180
+ args,
181
+ existingModule.composables,
182
+ );
183
+
184
+ // Reinstall the blade with the updated component
185
+ const module = createAppModuleWrapper({
186
+ bladeName: moduleName,
187
+ bladeComponent: BladeInstanceConstructor as BladeInstanceConstructor,
188
+ appModuleContent,
189
+ });
190
+
191
+ module.install(args.app, args.options);
192
+
193
+ // Update registered blade with the new component
194
+ existingModule.component = BladeInstanceConstructor as BladeInstanceConstructor;
195
+ }
196
+ };
197
+
198
+ export function createDynamicAppModule(args: {
199
+ schema?: { [key: string]: DynamicSchema };
200
+ composables?: { [key: string]: (...args: any[]) => any };
201
+ mixin?: { [x: string]: ((...args: any[]) => any)[] };
120
202
  overrides?: OverridesSchema;
121
203
  moduleComponents?: { [key: string]: Component };
122
204
  locales?: { [key: string]: object };
123
205
  notificationTemplates?: { [key: string]: Component };
124
- }) => {
125
- const moduleInitializer = _.findKey(args.schema, (o) => "isWorkspace" in o.settings && o.settings.isWorkspace);
126
- const everyHasTemplate = _.every(Object.values(args.schema), (o) => o?.settings?.component);
127
-
128
- if (!everyHasTemplate) handleError("component", args.schema, "must be included in 'settings' of every file");
129
- if (!moduleInitializer)
130
- handleError(
131
- "isWorkspace",
132
- args.schema,
133
- "must be included in one of this files to initialize module workspace blade",
134
- );
206
+ }): {
207
+ install(
208
+ app: App<any>,
209
+ options?:
210
+ | {
211
+ router: Router;
212
+ }
213
+ | undefined,
214
+ ): void;
215
+ } {
216
+ let schemaCopy: { [key: string]: DynamicSchema } = {};
135
217
 
136
- let schemaCopy = _.cloneDeep({ ...args.schema });
218
+ if (args.schema && Object.keys(args.schema).length > 0) {
219
+ schemaCopy = _.cloneDeep({ ...args.schema });
220
+ // Save schemas in the global registry
221
+ Object.assign(registeredSchemas, schemaCopy);
222
+ } else {
223
+ // Use registered schemas if new ones are not provided
224
+ schemaCopy = _.cloneDeep({ ...registeredSchemas });
225
+ }
137
226
 
138
227
  if (args.overrides) {
139
228
  schemaCopy = handleOverrides(args.overrides, schemaCopy);
140
229
  }
141
230
 
231
+ // Validation
232
+ const moduleInitializer = _.findKey(schemaCopy, (o) => o.settings.isWorkspace);
233
+ const everyHasTemplate = _.every(Object.values(schemaCopy), (o) => o?.settings?.component);
234
+
235
+ if (!everyHasTemplate) handleError("component", schemaCopy, "must be included in 'settings' of each file");
236
+ if (!moduleInitializer)
237
+ handleError("isWorkspace", schemaCopy, "must be included in one of the files to initialize the module workspace");
238
+
142
239
  return {
143
240
  install(app: App, options: { router: Router }) {
144
241
  const bladePages = { ...pages };
@@ -147,27 +244,33 @@ export const createDynamicAppModule = (args: {
147
244
  notificationTemplates: args?.notificationTemplates,
148
245
  moduleComponents: args?.moduleComponents,
149
246
  };
247
+
150
248
  const moduleUid = _.uniqueId("module_");
151
- Object.entries(schemaCopy).forEach(([, JsonSchema], index) => {
152
- const blade = register(
153
- {
154
- app,
155
- component: bladePages[JsonSchema.settings.component as keyof typeof bladePages] as BladeInstanceConstructor,
156
- composables: { ...args.composables },
157
- json: JsonSchema,
158
- options,
159
- moduleUid,
160
- },
161
- index === 0 ? appModuleContent : undefined,
162
- );
163
-
164
- if (!blade) {
249
+ Object.entries(schemaCopy).forEach(([key, JsonSchema], index) => {
250
+ const bladeId = JsonSchema.settings.id;
251
+ const registerArgs = {
252
+ app,
253
+ component: bladePages[JsonSchema.settings.component as keyof typeof bladePages] as BladeInstanceConstructor,
254
+ composables: { ...args.composables },
255
+ mixin: args.mixin?.[JsonSchema.settings.id] ? args.mixin[JsonSchema.settings.id] : undefined,
256
+ json: JsonSchema,
257
+ options,
258
+ moduleUid,
259
+ };
260
+
261
+ if (installedBladeIds.has(bladeId)) {
262
+ // Blade already installed, updating it
263
+ updateBlade(kebabToPascal(bladeId), JsonSchema, registerArgs, index === 0 ? appModuleContent : undefined);
165
264
  return;
166
265
  }
266
+
267
+ installedBladeIds.add(bladeId);
268
+
269
+ register(registerArgs, index === 0 ? appModuleContent : undefined);
167
270
  });
168
271
  },
169
272
  };
170
- };
273
+ }
171
274
 
172
275
  export * from "./factories";
173
276
  export * from "./types";
@@ -84,6 +84,7 @@ import {
84
84
  toRefs,
85
85
  provide,
86
86
  toRef,
87
+ type VNode,
87
88
  } from "vue";
88
89
  import { DynamicDetailsSchema, FormContentSchema, SettingsSchema } from "../types";
89
90
  import { reactiveComputed, refDefault, toReactive, useMounted, useTemplateRefsList } from "@vueuse/core";
@@ -98,12 +99,13 @@ import {
98
99
  DetailsBladeExposed,
99
100
  } from "../../../index";
100
101
  import SchemaRender from "../components/SchemaRender";
101
- import { VcSelect } from "../../../../ui/components";
102
+ import { VcSelect, VcImage } from "../../../../ui/components";
102
103
  import { useToolbarReducer } from "../composables/useToolbarReducer";
103
104
  import { useBeforeUnload } from "../../../../core/composables/useBeforeUnload";
104
105
  import * as _ from "lodash-es";
105
- import { useNotifications } from "../../../../core/composables";
106
+ import { useLanguages, useNotifications } from "../../../../core/composables";
106
107
  import { notification } from "../../../components";
108
+ import { ComponentSlots } from "../../../utilities/vueUtils";
107
109
 
108
110
  interface Props {
109
111
  expanded?: boolean;
@@ -114,6 +116,7 @@ interface Props {
114
116
  [x: string]: unknown;
115
117
  };
116
118
  composables?: Record<string, (...args: any[]) => Record<string, any>>;
119
+ mixinFn?: ((...args: any[]) => any)[];
117
120
  }
118
121
 
119
122
  interface Emits {
@@ -133,13 +136,18 @@ const emit = defineEmits<Emits>();
133
136
  const { t } = useI18n({ useScope: "global" });
134
137
 
135
138
  const { showConfirmation } = usePopup();
139
+ const { getFlag } = useLanguages();
136
140
 
137
141
  const widgetsRefs = useTemplateRefsList<{ el: HTMLDivElement; component: ConcreteComponent }>();
142
+ const isMixinReady = ref(false);
138
143
 
139
144
  if (typeof props.composables?.[props.model?.settings?.composable ?? ""] === "undefined") {
140
145
  throw new Error(`Composable ( ${props.model?.settings?.composable} ) is not defined`);
141
146
  }
142
- const { loading, item, validationState, scope, load, remove, saveChanges, bladeTitle } = props.composables
147
+
148
+ const settings = computed(() => props.model?.settings);
149
+
150
+ let { loading, item, validationState, scope, load, remove, saveChanges, bladeTitle } = props.composables
143
151
  ? (props.composables?.[props.model?.settings?.composable ?? ""]({ emit, props, mounted: useMounted() }) as UseDetails<
144
152
  Record<string, any>,
145
153
  DetailsBaseBladeScope
@@ -155,12 +163,39 @@ const { loading, item, validationState, scope, load, remove, saveChanges, bladeT
155
163
  bladeTitle: undefined,
156
164
  } as unknown as UseDetails<Record<string, any>, DetailsBaseBladeScope>);
157
165
 
166
+ if (props.mixinFn?.length) {
167
+ const mixinResults = props.mixinFn?.map((mixin) =>
168
+ mixin({ loading, item, validationState, scope, load, remove, saveChanges, bladeTitle }),
169
+ );
170
+
171
+ const mergedResults = mixinResults.reduce((acc, result) => {
172
+ return {
173
+ ...acc,
174
+ ...result,
175
+ };
176
+ }, {});
177
+
178
+ loading = mergedResults.loading ?? loading;
179
+ item = mergedResults.item ?? item;
180
+ validationState = mergedResults.validationState ?? validationState;
181
+ scope = mergedResults.scope ?? scope;
182
+ load = mergedResults.load ?? load;
183
+
184
+ remove = mergedResults.remove ?? remove;
185
+ saveChanges = mergedResults.saveChanges ?? saveChanges;
186
+ bladeTitle = mergedResults.bladeTitle ?? bladeTitle;
187
+
188
+ isMixinReady.value = true;
189
+ } else {
190
+ isMixinReady.value = true;
191
+ }
192
+
158
193
  const { onBeforeClose } = useBladeNavigation();
159
194
  const title = ref();
160
195
  const isReady = ref(false);
161
196
  const activeWidgetExposed = ref<CoreBladeExposed>();
162
197
  const isBladeEditable = computed(() => !toValue("disabled" in toValue(scope || {}) && toValue(scope || {}).disabled));
163
- const settings = computed(() => props.model?.settings);
198
+
164
199
  const unreffedScope = reactiveComputed(() => toValue(scope) ?? {});
165
200
 
166
201
  const { moduleNotifications, markAsRead } = useNotifications(settings.value?.pushNotificationType);
@@ -223,6 +258,26 @@ const bladeStatus = computed(() => {
223
258
  return null;
224
259
  });
225
260
 
261
+ const localeOptions = ref();
262
+
263
+ watch(
264
+ () => toValue(unreffedScope).multilanguage?.localesOptions,
265
+ (newVal) => {
266
+ localeOptions.value = newVal;
267
+ },
268
+ { immediate: true, deep: true },
269
+ );
270
+
271
+ watch(
272
+ () => localeOptions.value,
273
+ async (newVal) => {
274
+ for (const lang of newVal) {
275
+ lang.flag = await getFlag(lang.value);
276
+ }
277
+ },
278
+ { deep: true },
279
+ );
280
+
226
281
  const bladeMultilanguage = reactiveComputed(() => {
227
282
  if (
228
283
  scope &&
@@ -232,19 +287,45 @@ const bladeMultilanguage = reactiveComputed(() => {
232
287
  ) {
233
288
  return {
234
289
  component: () => {
235
- return h(VcSelect as Component, {
236
- name: "currentLocale",
237
- modelValue: toValue(unreffedScope).multilanguage?.currentLocale,
238
- options: toValue(unreffedScope).multilanguage?.localesOptions,
239
- optionValue: "value",
240
- optionLabel: "label",
241
- disabled: "disabled" in toValue(unreffedScope) && toValue(unreffedScope).disabled,
242
- required: true,
243
- clearable: false,
244
- "onUpdate:modelValue": (e: string) => {
245
- toValue(unreffedScope).multilanguage?.setLocale(e);
290
+ return h(
291
+ VcSelect as Component,
292
+ {
293
+ name: "currentLocale",
294
+ modelValue: toValue(unreffedScope).multilanguage?.currentLocale,
295
+ options: localeOptions.value,
296
+ optionValue: "value",
297
+ optionLabel: "label",
298
+ disabled: "disabled" in toValue(unreffedScope) && toValue(unreffedScope).disabled,
299
+ required: true,
300
+ clearable: false,
301
+ "onUpdate:modelValue": (e: string) => {
302
+ toValue(unreffedScope).multilanguage?.setLocale(e);
303
+ },
246
304
  },
247
- });
305
+ ["selected-item", "option"].reduce(
306
+ (obj, slot) => {
307
+ obj[slot] = (
308
+ scope: Parameters<ComponentSlots<typeof VcSelect>["option"]>["0"] & {
309
+ opt: { flag: string; label: string };
310
+ },
311
+ ) => {
312
+ return h("div", { class: "tw-flex tw-items-center tw-gap-2" }, [
313
+ h(VcImage, { src: scope.opt.flag, class: "tw-w-6 tw-h-6", emptyIcon: "" }),
314
+ h("span", { class: "tw-text-sm" }, scope.opt.label),
315
+ ]);
316
+ };
317
+ return obj;
318
+ },
319
+ {} as Record<
320
+ string,
321
+ (
322
+ scope: Parameters<ComponentSlots<typeof VcSelect>["option"]>["0"] & {
323
+ opt: { flag: string; label: string };
324
+ },
325
+ ) => VNode
326
+ >,
327
+ ),
328
+ );
248
329
  },
249
330
  currentLocale: toValue(unreffedScope).multilanguage?.currentLocale,
250
331
  };
@@ -345,9 +426,7 @@ async function updateActiveWidgetCount() {
345
426
  }
346
427
 
347
428
  async function init() {
348
- if (props.param && unref(props.param)) {
349
- await load({ id: unref(props.param) });
350
- }
429
+ await load({ id: unref(props.param) });
351
430
 
352
431
  await nextTick(() => {
353
432
  isReady.value = true;
@@ -355,7 +434,7 @@ async function init() {
355
434
  }
356
435
 
357
436
  onBeforeMount(async () => {
358
- if (props.composables) await init();
437
+ if (props.composables && isMixinReady.value) await init();
359
438
  });
360
439
 
361
440
  onBeforeClose(async () => {
@@ -594,7 +594,7 @@ const onPaginationClick = async (page: number) => {
594
594
  }
595
595
  };
596
596
 
597
- const onHeaderClick = (item: ITableColumns) => {
597
+ const onHeaderClick = (item: Partial<ITableColumns>) => {
598
598
  const sortOptions = ["DESC", "ASC", ""];
599
599
 
600
600
  if (item.sortable) {
@@ -78,22 +78,8 @@
78
78
  <!-- TODO add to localization -->
79
79
  OR
80
80
  </div>
81
- <div class="vc-login-page__external-buttons">
82
- <VcButton
83
- v-for="external in loginProviders"
84
- :key="external.authenticationType"
85
- outline
86
- @click="externalSignOn(external.authenticationType ?? '')"
87
- >
88
- <div class="vc-login-page__external-button-content">
89
- <img
90
- :src="externalAuthIcon(external.authenticationType ?? '')"
91
- :alt="external.authenticationType"
92
- class="vc-login-page__external-icon"
93
- />{{ external.displayName }}
94
- </div>
95
- </VcButton>
96
- </div>
81
+
82
+ <ExternalProviders :providers="loginProviders" />
97
83
  </div>
98
84
  </template>
99
85
  <template v-else>
@@ -181,9 +167,10 @@ import { useRouter } from "vue-router";
181
167
  import { useIsFormValid, Field, useIsFormDirty, useForm } from "vee-validate";
182
168
  import { useSettings, useUser } from "./../../../../../core/composables";
183
169
  import { RequestPasswordResult } from "./../../../../../core/types";
184
- import AzureAdIcon from "./../../../../../assets/img/AzureAd.svg";
185
170
  import { ExternalSignInProviderInfo, SignInResult } from "./../../../../../core/api/platform";
186
171
  import { useI18n } from "vue-i18n";
172
+ import { default as ExternalProviders } from "./../../../../../shared/components/sign-in/external-providers.vue";
173
+ import { useExternalProvider } from "./../../../../../shared/components/sign-in/useExternalProvider";
187
174
 
188
175
  type ForgotPasswordFunc = (args: { loginOrEmail: string }) => Promise<void>;
189
176
 
@@ -205,7 +192,9 @@ let useLogin;
205
192
  const signInResult = ref({ succeeded: true }) as Ref<SignInResult & { status?: number; error?: any }>;
206
193
  const requestPassResult = ref<RequestPasswordResult>({ succeeded: true });
207
194
  const forgotPasswordRequestSent = ref(false);
208
- const { signIn, loading, externalSignIn, getExternalLoginProviders, user } = useUser();
195
+ const { signIn, loading, user } = useUser();
196
+ const { getProviders } = useExternalProvider();
197
+
209
198
  const isLogin = ref(true);
210
199
  const isValid = useIsFormValid();
211
200
  const isDirty = useIsFormDirty();
@@ -224,14 +213,9 @@ if (props.composable && typeof props.composable === "function") {
224
213
  }
225
214
 
226
215
  onMounted(async () => {
227
- loginProviders.value = await getExternalLoginProviders();
216
+ loginProviders.value = await getProviders();
228
217
  });
229
218
 
230
- const externalAuthIcon = (authenticationType: string) => {
231
- if (authenticationType === "AzureAD") return AzureAdIcon;
232
- else return;
233
- };
234
-
235
219
  const customization = computed(() => {
236
220
  return {
237
221
  logo: !customizationLoading.value ? uiSettings.value?.logo || props.logo : "",
@@ -305,10 +289,6 @@ const togglePassRequest = () => {
305
289
  }
306
290
  };
307
291
 
308
- const externalSignOn = async (authenticationType: string) => {
309
- await externalSignIn(authenticationType, window.location.pathname);
310
- };
311
-
312
292
  console.debug("Init login-page");
313
293
  </script>
314
294
 
@@ -348,18 +328,6 @@ console.debug("Init login-page");
348
328
  @apply tw-flex tw-items-center tw-text-center tw-uppercase tw-text-[color:var(--login-separator-text)] before:tw-content-[''] before:tw-flex-1 before:tw-border-b before:tw-border-b-[color:var(--login-separator)] before:tw-mr-2 after:tw-content-[''] after:tw-flex-1 after:tw-border-b after:tw-border-b-[color:var(--login-separator)] after:tw-ml-2;
349
329
  }
350
330
 
351
- &__external-buttons {
352
- @apply tw-flex tw-justify-center tw-mt-4 tw-flex-wrap tw-gap-2;
353
- }
354
-
355
- &__external-button-content {
356
- @apply tw-flex tw-flex-row tw-items-center;
357
- }
358
-
359
- &__external-icon {
360
- @apply tw-h-5 tw-mr-2;
361
- }
362
-
363
331
  &__error-hint {
364
332
  @apply tw-mt-3 tw-text-[color:var(--login-error)];
365
333
  }