@rancher/shell 3.0.8-rc.1 → 3.0.8-rc.2

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 (45) hide show
  1. package/assets/styles/global/_layout.scss +21 -35
  2. package/assets/translations/en-us.yaml +11 -6
  3. package/components/EmberPage.vue +1 -1
  4. package/components/Resource/Detail/CopyToClipboard.vue +1 -1
  5. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
  6. package/components/Resource/Detail/TitleBar/index.vue +10 -6
  7. package/components/ResourceDetail/index.vue +3 -0
  8. package/components/SortableTable/index.vue +1 -0
  9. package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
  10. package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
  11. package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
  12. package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
  13. package/components/nav/Header.vue +33 -13
  14. package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
  15. package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
  16. package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
  17. package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
  18. package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
  19. package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
  20. package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
  21. package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
  22. package/components/nav/WindowManager/constants.ts +23 -0
  23. package/components/nav/WindowManager/index.vue +61 -575
  24. package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
  25. package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
  26. package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
  27. package/components/templates/default.vue +4 -40
  28. package/components/templates/home.vue +31 -5
  29. package/config/store.js +4 -2
  30. package/detail/pod.vue +1 -0
  31. package/directives/ui-context.ts +97 -0
  32. package/initialize/install-directives.js +2 -0
  33. package/package.json +2 -2
  34. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
  35. package/store/ui-context.ts +86 -0
  36. package/store/wm.ts +244 -0
  37. package/types/window-manager.ts +22 -0
  38. package/utils/dynamic-importer.js +2 -2
  39. package/assets/images/icons/document.svg +0 -3
  40. package/store/wm.js +0 -95
  41. /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
  42. /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
  43. /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
  44. /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
  45. /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
@@ -71,6 +71,13 @@ HEADER {
71
71
  flex: 1 1 auto;
72
72
  min-height: 0px;
73
73
 
74
+ grid-template-areas:
75
+ "header header header header"
76
+ "wm-vl nav main wm-vr"
77
+ "wm-vl wm wm wm-vr";
78
+ grid-template-rows: var(--header-height) auto var(--wm-height, 0px);
79
+ grid-template-columns: var(--wm-vl-width, 0px) var(--nav-width) auto var(--wm-vr-width, 0px);
80
+
74
81
  &.dashboard-padding-left {
75
82
  padding-left: $app-bar-collapsed-width;
76
83
 
@@ -79,31 +86,6 @@ HEADER {
79
86
  }
80
87
  }
81
88
 
82
- &.pin-right {
83
- grid-template-areas:
84
- "header header header"
85
- "nav main wm";
86
- grid-template-rows: var(--header-height) auto;
87
- grid-template-columns: var(--nav-width) auto var(--wm-width, 0px);
88
- }
89
-
90
- &.pin-bottom {
91
- grid-template-areas:
92
- "header header"
93
- "nav main"
94
- "wm wm";
95
- grid-template-rows: var(--header-height) auto var(--wm-height, 0px);
96
- grid-template-columns: var(--nav-width) auto;
97
- }
98
-
99
- &.pin-left {
100
- grid-template-areas:
101
- "header header header"
102
- "wm nav main";
103
- grid-template-rows: var(--header-height) auto;
104
- grid-template-columns: var(--wm-width, 0px) var(--nav-width) auto;
105
- }
106
-
107
89
  >HEADER {
108
90
  grid-area: header;
109
91
  }
@@ -120,6 +102,20 @@ HEADER {
120
102
  position: relative;
121
103
  }
122
104
 
105
+ .wm-vr {
106
+ grid-area: wm-vr;
107
+ overflow-y: hidden;
108
+ z-index: z-index('windowsManager');
109
+ position: relative;
110
+ }
111
+
112
+ .wm-vl {
113
+ grid-area: wm-vl;
114
+ overflow-y: hidden;
115
+ z-index: z-index('windowsManager');
116
+ position: relative;
117
+ }
118
+
123
119
  .localeSelector {
124
120
  :deep(.v-popper__inner) {
125
121
  padding: 50px 0;
@@ -153,14 +149,4 @@ HEADER {
153
149
  }
154
150
  }
155
151
 
156
- .drag-start {
157
- z-index: 1000;
158
- opacity: 0.5;
159
- transition: opacity .3s ease;
160
- }
161
-
162
- .drag-end {
163
- opacity: 1;
164
- }
165
-
166
152
  // !END
@@ -370,9 +370,7 @@ layouts:
370
370
  unauthenticated: unauthenticated layout
371
371
  logout: logout layout
372
372
  verify: verify layout
373
-
374
- windowmanager:
375
- closeTab: Close tab {tabId}
373
+
376
374
  about:
377
375
  title: About
378
376
  versions:
@@ -5226,6 +5224,8 @@ plugins:
5226
5224
  third-party: This Extension is provided by a Third-Party
5227
5225
  built-in: This Extension is built-in
5228
5226
  image: This Extension Image has been loaded manually
5227
+ tooltips:
5228
+ installing: Installing...
5229
5229
  error:
5230
5230
  title: Error loading extension
5231
5231
  message: Could not load extension code
@@ -7078,7 +7078,15 @@ wizard:
7078
7078
  install: Install
7079
7079
  upgrade: Upgrade
7080
7080
  downgrade: Downgrade
7081
+
7082
+ sideWindow:
7083
+ secondary:
7084
+ resize: Resize secondary window
7085
+
7081
7086
  wm:
7087
+ resize: Resize Window - use arrow keys {arrow1} and {arrow2} to resize with keyboard
7088
+ tabIcon: Window tab icon
7089
+ closeTab: Close tab - {tabId}
7082
7090
  connection:
7083
7091
  connected: Connected
7084
7092
  connecting: Connecting…
@@ -7115,9 +7123,6 @@ wm:
7115
7123
  timestamps: Show Timestamps
7116
7124
  wrap: Wrap Lines
7117
7125
  containerShell:
7118
- resizeShellWindow: Resize Shell window - use arrow keys {arrow1} and {arrow2} to resize with keyboard
7119
- tabIcon: Shell tab icon
7120
- closeShellTab: Close Shell tab - {tab}
7121
7126
  escapeText: Press Shift+Escape to blur from terminal
7122
7127
  clear: Clear
7123
7128
  containerName: "Container: {label}"
@@ -8,7 +8,7 @@ import { findEmberPage, clearEmberInactiveTimer, startEmberInactiveTimer, EMBER_
8
8
 
9
9
  const EMBER_FRAME_HIDE_CLASS = 'ember-iframe-hidden';
10
10
  const PAGE_CHECK_TIMEOUT = 30000;
11
- const WINDOW_MANAGER = 'windowmanager';
11
+ const WINDOW_MANAGER = 'horizontal-window-manager';
12
12
 
13
13
  // Pages that we should intercept when loaded in the IFRAME and instead
14
14
  // navigate to a page in Cluster Dashboard
@@ -5,7 +5,7 @@ import { ref } from 'vue';
5
5
  import { useStore } from 'vuex';
6
6
 
7
7
  export interface Props {
8
- value: string;
8
+ value: string;
9
9
  }
10
10
 
11
11
  const props = defineProps<Props>();
@@ -3,8 +3,6 @@ import TitleBar from '@shell/components/Resource/Detail/TitleBar/index.vue';
3
3
  import ActionMenu from '@shell/components/ActionMenuShell.vue';
4
4
  import { createStore } from 'vuex';
5
5
 
6
- jest.mock(`@shell/assets/images/icons/document.svg`, () => `@shell/assets/images/icons/document.svg`);
7
-
8
6
  describe('component: TitleBar/index', () => {
9
7
  const resourceTypeLabel = 'RESOURCE_TYPE_LABEL';
10
8
  const resourceTo = 'RESOURCE_TO';
@@ -32,8 +32,6 @@ export interface TitleBarProps {
32
32
  actionMenuResource?: any;
33
33
  onShowConfiguration?: (returnFocusSelector: string) => void;
34
34
  }
35
-
36
- const showConfigurationIcon = require(`@shell/assets/images/icons/document.svg`);
37
35
  </script>
38
36
 
39
37
  <script setup lang="ts">
@@ -84,6 +82,7 @@ watch(
84
82
  </span>
85
83
  <BadgeState
86
84
  v-if="badge"
85
+ v-ui-context="{ store: store, icon: 'icon-folder', hookable: true, value: resource, tag: '__details-state', description: 'Details' }"
87
86
  class="badge-state"
88
87
  :color="badge.color"
89
88
  :label="badge.label"
@@ -99,11 +98,10 @@ watch(
99
98
  :aria-label="i18n.t('component.resource.detail.titleBar.ariaLabel.showConfiguration', { resource: resourceName })"
100
99
  @click="() => emit('show-configuration', showConfigurationReturnFocusSelector)"
101
100
  >
102
- <img
103
- :src="showConfigurationIcon"
104
- class="mmr-3"
101
+ <i
102
+ class="icon icon-document"
105
103
  aria-hidden="true"
106
- >
104
+ />
107
105
  {{ i18n.t('component.resource.detail.titleBar.showConfiguration') }}
108
106
  </RcButton>
109
107
  <ActionMenu
@@ -139,6 +137,12 @@ watch(
139
137
  position: relative;
140
138
  }
141
139
 
140
+ .icon-document {
141
+ width: 15px;
142
+ font-size: 16px;
143
+ margin-right: 10px;
144
+ }
145
+
142
146
  .show-configuration {
143
147
  margin-left: 16px;
144
148
  }
@@ -430,6 +430,7 @@ export default {
430
430
  :is="showComponent"
431
431
  v-else-if="isFullPageOverride"
432
432
  v-model:value="value"
433
+ v-ui-context="{ icon: 'icon-folder', value: value.name, tag: value.kind?.toLowerCase(), description: value.kind }"
433
434
  v-bind="$data"
434
435
  :done-params="doneParams"
435
436
  :done-route="doneRoute"
@@ -446,6 +447,7 @@ export default {
446
447
  <div v-else>
447
448
  <Masthead
448
449
  v-if="showMasthead"
450
+ v-ui-context="{ icon: 'icon-folder', value: liveModel.name, tag: liveModel.kind?.toLowerCase(), description: liveModel.kind }"
449
451
  :resource="resourceType"
450
452
  :value="liveModel"
451
453
  :mode="mode"
@@ -499,6 +501,7 @@ export default {
499
501
  v-else
500
502
  ref="comp"
501
503
  v-model:value="value"
504
+ v-ui-context="{ icon: 'icon-folder', value: value.name, tag: value.kind?.toLowerCase(), description: value.kind }"
502
505
  v-bind="$data"
503
506
  :done-params="doneParams"
504
507
  :done-route="doneRoute"
@@ -1474,6 +1474,7 @@ export default {
1474
1474
  <td
1475
1475
  v-show="!hasAdvancedFiltering || (hasAdvancedFiltering && col.col.isColVisible)"
1476
1476
  :key="col.col.name"
1477
+ v-ui-context="col.col.name === 'state' ? { icon: 'icon-folder', hookable: true, value: row.row, tag: '__sortable-table-row', description: 'Row' } : undefined"
1477
1478
  :data-title="col.col.label"
1478
1479
  :data-testid="`sortable-cell-${ i }-${ j }`"
1479
1480
  :align="col.col.align || 'left'"
@@ -10,7 +10,7 @@ import AsyncButton from '@shell/components/AsyncButton';
10
10
  import Select from '@shell/components/form/Select';
11
11
  import VirtualList from 'vue3-virtual-scroll-list';
12
12
  import LogItem from '@shell/components/LogItem';
13
- import ContainerLogsActions from '@shell/components/nav/WindowManager/ContainerLogsActions.vue';
13
+ import ContainerLogsActions from '@shell/components/Window/ContainerLogsActions.vue';
14
14
  import { shallowRef } from 'vue';
15
15
  import { useStore } from 'vuex';
16
16
  import { debounce } from 'lodash';
@@ -49,6 +49,7 @@ defineEmits([
49
49
  <rc-dropdown-item-select
50
50
  :model-value="range"
51
51
  :options="rangeOptions"
52
+ :label="t('wm.containerLogs.range.label')"
52
53
  @select="$emit('toggleRange', $event)"
53
54
  />
54
55
  <rc-dropdown-item-checkbox
@@ -1,6 +1,6 @@
1
1
  import { nextTick } from 'vue';
2
2
  import { shallowMount } from '@vue/test-utils';
3
- import ContainerLogs from '@shell/components/nav/WindowManager/ContainerLogs.vue';
3
+ import ContainerLogs from '@shell/components/Window/ContainerLogs.vue';
4
4
  import { base64Encode } from '@shell/utils/crypto';
5
5
  import { Buffer } from 'buffer';
6
6
  import { addEventListener } from '@shell/utils/socket';
@@ -1,9 +1,9 @@
1
1
  import { flushPromises, mount, Wrapper } from '@vue/test-utils';
2
- import ContainerShell from '@shell/components/nav/WindowManager/ContainerShell.vue';
2
+ import ContainerShell from '@shell/components/Window/ContainerShell.vue';
3
3
  import Socket, {
4
4
  addEventListener, EVENT_CONNECTED, EVENT_CONNECTING, EVENT_DISCONNECTED, EVENT_MESSAGE, EVENT_CONNECT_ERROR
5
5
  } from '@shell/utils/socket';
6
- import Window from '@shell/components/nav/WindowManager/Window.vue';
6
+ import Window from '@shell/components/Window/Window.vue';
7
7
 
8
8
  jest.mock('@shell/utils/socket');
9
9
  jest.mock('@shell/utils/crypto', () => {
@@ -67,18 +67,19 @@ export default {
67
67
  const shellShortcut = '(Ctrl+`)';
68
68
 
69
69
  return {
70
- authInfo: {},
71
- show: false,
72
- showTooltip: false,
73
- isUserMenuOpen: false,
74
- isPageActionMenuOpen: false,
75
- kubeConfigCopying: false,
70
+ authInfo: {},
71
+ show: false,
72
+ showTooltip: false,
73
+ isUserMenuOpen: false,
74
+ isPageActionMenuOpen: false,
75
+ kubeConfigCopying: false,
76
76
  searchShortcut,
77
77
  shellShortcut,
78
78
  LOGGED_OUT,
79
- navHeaderRight: null,
80
- extensionHeaderActions: getApplicableExtensionEnhancements(this, ExtensionPoint.ACTION, ActionLocation.HEADER, this.$route),
81
- ctx: this
79
+ navHeaderRight: null,
80
+ extensionHeaderActions: getApplicableExtensionEnhancements(this, ExtensionPoint.ACTION, ActionLocation.HEADER, this.$route),
81
+ extensionActionsEnabled: {},
82
+ ctx: this
82
83
  };
83
84
  },
84
85
 
@@ -252,6 +253,7 @@ export default {
252
253
  handler(neu) {
253
254
  if (neu) {
254
255
  this.extensionHeaderActions = getApplicableExtensionEnhancements(this, ExtensionPoint.ACTION, ActionLocation.HEADER, neu);
256
+ this.updateExtensionActionsEnabled();
255
257
 
256
258
  this.navHeaderRight = this.$plugin?.getDynamic('component', 'NavHeaderRight');
257
259
  }
@@ -368,7 +370,7 @@ export default {
368
370
  });
369
371
  },
370
372
 
371
- handleExtensionAction(action, event) {
373
+ async handleExtensionAction(action, event) {
372
374
  const fn = action.invoke;
373
375
  const opts = {
374
376
  event,
@@ -377,7 +379,7 @@ export default {
377
379
  product: this.currentProduct.name,
378
380
  cluster: this.currentCluster,
379
381
  };
380
- const enabled = action.enabled ? action.enabled.apply(this, [this.ctx]) : true;
382
+ const enabled = await this.isActionEnabled(action);
381
383
 
382
384
  if (fn && enabled) {
383
385
  fn.apply(this, [opts, [], { $route: this.$route }]);
@@ -393,7 +395,25 @@ export default {
393
395
  }
394
396
 
395
397
  return null;
396
- }
398
+ },
399
+
400
+ async updateExtensionActionsEnabled() {
401
+ for (const [i, action] of this.extensionHeaderActions.entries()) {
402
+ this.extensionActionsEnabled[i] = await this.isActionEnabled(action);
403
+ }
404
+ },
405
+
406
+ async isActionEnabled(action) {
407
+ if (action.enabled === undefined) {
408
+ return true;
409
+ }
410
+
411
+ if (typeof action.enabled === 'function') {
412
+ return await action.enabled(this.ctx);
413
+ }
414
+
415
+ return action.enabled;
416
+ },
397
417
  }
398
418
  };
399
419
  </script>
@@ -642,7 +662,7 @@ export default {
642
662
  :key="`${action.label}${i}`"
643
663
  v-clean-tooltip="handleExtensionTooltip(action)"
644
664
  v-shortkey="action.shortcutKey"
645
- :disabled="action.enabled ? !action.enabled(ctx) : false"
665
+ :disabled="!extensionActionsEnabled[i]"
646
666
  type="button"
647
667
  class="btn header-btn role-tertiary"
648
668
  :data-testid="`extension-header-action-${ action.labelKey || action.label }`"
@@ -1,105 +1,71 @@
1
- <script lang="ts">
2
- import { defineComponent } from 'vue';
3
- import { mapState } from 'vuex';
1
+ <script setup lang="ts">
2
+ import { onMounted } from 'vue';
4
3
  import { BOTTOM, CENTER, LEFT, RIGHT } from '@shell/utils/position';
5
-
6
- type Zone = null | typeof CENTER | typeof RIGHT | typeof BOTTOM | typeof LEFT;
7
-
8
- export interface Drag {
9
- active: boolean;
10
- zone: Zone;
11
- }
12
-
13
- interface Data {
14
- drag: Drag;
15
- }
16
-
17
- export default defineComponent({
18
- data(): Data {
19
- return {
20
- drag: {
21
- active: false,
22
- zone: CENTER,
23
- },
24
- };
25
- },
26
-
27
- computed: {
28
-
29
- ...mapState('wm', ['userPin']),
30
-
31
- pin: {
32
- get(): Zone {
33
- return this.userPin;
34
- },
35
-
36
- set(pin: Zone) {
37
- if (pin === CENTER) {
38
- return;
39
- }
40
- window.localStorage.setItem('wm-pin', pin as string);
41
- this.$store.commit('wm/setUserPin', pin);
42
- },
43
- },
44
-
45
- },
46
-
47
- methods: {
48
-
49
- onDragStart() {
50
- this.drag.active = true;
51
- },
52
-
53
- onDragOver(event: DragEvent, zone: Zone) {
54
- this.drag.zone = zone;
55
- if (zone !== CENTER) {
56
- event.preventDefault();
57
- }
58
- },
59
-
60
- onDragEnd() {
61
- this.pin = this.drag.zone;
62
- this.drag = {
63
- active: false,
64
- zone: CENTER,
65
- };
66
- },
67
-
68
- }
4
+ import useDragHandler from './composables/useDragHandler';
5
+ import { Z_INDEX } from './constants';
6
+
7
+ /**
8
+ * This component is responsible for rendering the pin area used during tab dragging.
9
+ *
10
+ * Behavior:
11
+ * - Enable drag areas for each position (left, right, bottom, center) when dragging is active.
12
+ * - Highlights the area where the tab can be pinned.
13
+ * - Uses z-index variables to ensure proper layering of drag areas.
14
+ */
15
+
16
+ const {
17
+ dragOverPositionsActive, pin, pinArea, lockedPositions, onDragPositionOver
18
+ } = useDragHandler();
19
+
20
+ onMounted(() => {
21
+ Object.keys(Z_INDEX).forEach((key) => {
22
+ document.documentElement.style.setProperty(
23
+ `--drag-area-${ key.toLowerCase().replaceAll('_', '-') }-z-index`, (Z_INDEX as any)[key].toString()
24
+ );
25
+ });
69
26
  });
70
27
  </script>
71
28
 
72
29
  <template>
73
- <div v-if="drag.active">
30
+ <div
31
+ v-if="dragOverPositionsActive"
32
+ class="pin-area-container"
33
+ >
74
34
  <span
75
- v-if="drag.zone != pin"
35
+ v-if="pinArea != pin"
76
36
  class="pin-effect-area"
77
- :class="drag.zone"
37
+ :class="pinArea"
78
38
  />
79
39
  <span
80
40
  class="drag-area center"
81
- @dragover="onDragOver($event, 'center')"
41
+ @dragover="onDragPositionOver($event, CENTER)"
82
42
  />
83
43
  <span
44
+ v-if="!lockedPositions.includes(RIGHT)"
84
45
  class="drag-area right"
85
- @dragover="onDragOver($event, 'right')"
46
+ @dragover="onDragPositionOver($event, RIGHT)"
86
47
  />
87
48
  <span
88
- class="drag-area bottom"
89
- @dragover="onDragOver($event, 'bottom')"
49
+ v-if="!lockedPositions.includes(LEFT)"
50
+ class="drag-area left"
51
+ @dragover="onDragPositionOver($event, LEFT)"
90
52
  />
91
53
  <span
92
- class="drag-area left"
93
- @dragover="onDragOver($event, 'left')"
54
+ v-if="!lockedPositions.includes(BOTTOM)"
55
+ class="drag-area bottom"
56
+ @dragover="onDragPositionOver($event, BOTTOM)"
94
57
  />
95
58
  </div>
96
59
  </template>
97
60
 
98
61
  <style lang='scss' scoped>
62
+ .pin-area-container {
63
+ display: contents;
64
+ }
99
65
 
100
66
  .pin-effect-area {
101
67
  position: absolute;
102
- z-index: 997;
68
+ z-index: var(--drag-area-pin-effect-z-index);
103
69
  width: 0;
104
70
  height: 0;
105
71
  border-style: hidden;
@@ -142,10 +108,8 @@ export default defineComponent({
142
108
  }
143
109
  }
144
110
 
145
- // ToDo make height and width as input variable
146
111
  .drag-area {
147
112
  position: absolute;
148
- z-index: 999;
149
113
  width: 0;
150
114
  height: 0;
151
115
  opacity: 0;
@@ -155,7 +119,7 @@ export default defineComponent({
155
119
  right: 0;
156
120
  width: 100%;
157
121
  height: 100%;
158
- z-index: 998;
122
+ z-index: var(--drag-area-center-z-index);
159
123
  }
160
124
 
161
125
  &.right {
@@ -163,6 +127,7 @@ export default defineComponent({
163
127
  right: 0;
164
128
  width: 300px;
165
129
  height: 100%;
130
+ z-index: var(--drag-area-right-z-index);
166
131
  }
167
132
 
168
133
  &.left {
@@ -170,12 +135,14 @@ export default defineComponent({
170
135
  left: 0;
171
136
  width: 300px;
172
137
  height: 100%;
138
+ z-index: var(--drag-area-left-z-index);
173
139
  }
174
140
 
175
141
  &.bottom {
176
142
  bottom: 0;
177
143
  height: 270px;
178
144
  width: 100%;
145
+ z-index: var(--drag-area-bottom-z-index);
179
146
  }
180
147
  }
181
148
  </style>
@@ -0,0 +1,70 @@
1
+ import {
2
+ ref,
3
+ markRaw,
4
+ } from 'vue';
5
+ import { useStore } from 'vuex';
6
+
7
+ const warn = (msg: string, ...args: any[]) => {
8
+ console.warn(`[wm] ${ msg } ${ args?.reduce((acc, v) => `${ acc } '${ v }'`, '') }`); /* eslint-disable-line no-console */
9
+ };
10
+
11
+ /**
12
+ * This composable is responsible for loading the tabs body components.
13
+ *
14
+ * - It supports loading components from Rancher extensions as well as from the TypeMap (defined in shell/components/Window directory).
15
+ * - The component is cached after the first load to optimize performance.
16
+ *
17
+ * Loading a component from extension:
18
+ *
19
+ * // Register the component in the extension index file:
20
+ * plugin.register('component', 'TestComponent', defineAsyncComponent(() => import('./pages/TestComponent.vue')));
21
+ *
22
+ * // Use the component in a tab by specifying its name and extensionId:
23
+ * store.dispatch('wm/open', {
24
+ * id: PRODUCT_NAME,
25
+ * extensionId: PRODUCT_NAME,
26
+ * label: 'Label',
27
+ * component: 'TestComponent',
28
+ * position: 'bottom',
29
+ * layouts: [
30
+ * Layout.default,
31
+ * Layout.home
32
+ * ],
33
+ * showHeader: false,
34
+ * }, { root: true });
35
+ *
36
+ */
37
+
38
+ export default () => {
39
+ const store = useStore();
40
+
41
+ const components = ref<any>({});
42
+
43
+ function loadComponent(tab: { component?: string, extensionId?: string }) {
44
+ const { component: name, extensionId } = tab || {};
45
+
46
+ if (!name) {
47
+ warn('component name not provided');
48
+
49
+ return null;
50
+ }
51
+
52
+ if (!components.value[name]) {
53
+ if (!!extensionId) {
54
+ warn(`loading component from extension`, name, extensionId);
55
+ components.value[name] = markRaw((store as any).$extension?.getDynamic('component', name));
56
+ } else if (store.getters['type-map/hasCustomWindowComponent'](name)) {
57
+ warn(`loading component from TypeMap`, name);
58
+ components.value[name] = markRaw(store.getters['type-map/importWindowComponent'](name));
59
+ }
60
+ }
61
+
62
+ if (!components.value[name]) {
63
+ warn(`component not found for`, name);
64
+ }
65
+
66
+ return components.value[name];
67
+ }
68
+
69
+ return { loadComponent };
70
+ };