@rancher/shell 3.0.7 → 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 (123) hide show
  1. package/assets/images/vendor/githubapp.svg +13 -0
  2. package/assets/styles/base/_typography.scss +1 -1
  3. package/assets/styles/global/_layout.scss +21 -35
  4. package/assets/styles/themes/_modern.scss +5 -5
  5. package/assets/translations/en-us.yaml +102 -17
  6. package/assets/translations/zh-hans.yaml +0 -4
  7. package/components/EmberPage.vue +1 -1
  8. package/components/Inactivity.vue +222 -106
  9. package/components/InstallHelmCharts.vue +2 -2
  10. package/components/Resource/Detail/CopyToClipboard.vue +1 -1
  11. package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
  12. package/components/Resource/Detail/TitleBar/index.vue +10 -6
  13. package/components/ResourceDetail/index.vue +4 -1
  14. package/components/SortableTable/index.vue +18 -2
  15. package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
  16. package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
  17. package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
  18. package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
  19. package/components/fleet/FleetConfigMapSelector.vue +117 -0
  20. package/components/fleet/FleetSecretSelector.vue +127 -0
  21. package/components/fleet/__tests__/FleetConfigMapSelector.test.ts +125 -0
  22. package/components/fleet/__tests__/FleetSecretSelector.test.ts +82 -0
  23. package/components/form/FileImageSelector.vue +13 -4
  24. package/components/form/FileSelector.vue +11 -2
  25. package/components/form/ResourceLabeledSelect.vue +1 -0
  26. package/components/form/__tests__/ResourceLabeledSelect.test.ts +90 -0
  27. package/components/nav/Header.vue +34 -13
  28. package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
  29. package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
  30. package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
  31. package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
  32. package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
  33. package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
  34. package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
  35. package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
  36. package/components/nav/WindowManager/constants.ts +23 -0
  37. package/components/nav/WindowManager/index.vue +61 -575
  38. package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
  39. package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
  40. package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
  41. package/components/templates/default.vue +4 -40
  42. package/components/templates/home.vue +31 -5
  43. package/config/product/auth.js +1 -0
  44. package/config/query-params.js +1 -0
  45. package/config/settings.ts +8 -1
  46. package/config/store.js +4 -2
  47. package/config/types.js +2 -0
  48. package/detail/pod.vue +1 -0
  49. package/dialog/AddonConfigConfirmationDialog.vue +45 -1
  50. package/directives/ui-context.ts +97 -0
  51. package/edit/__tests__/fleet.cattle.io.helmop.test.ts +52 -11
  52. package/edit/auth/AuthProviderWarningBanners.vue +14 -1
  53. package/edit/auth/github-app-steps.vue +97 -0
  54. package/edit/auth/github-steps.vue +75 -0
  55. package/edit/auth/github.vue +94 -65
  56. package/edit/fleet.cattle.io.helmop.vue +51 -2
  57. package/edit/networking.k8s.io.networkpolicy/PolicyRuleTarget.vue +15 -5
  58. package/edit/provisioning.cattle.io.cluster/__tests__/rke2.test.ts +11 -9
  59. package/edit/provisioning.cattle.io.cluster/rke2.vue +56 -9
  60. package/edit/provisioning.cattle.io.cluster/tabs/AddOnConfig.vue +28 -2
  61. package/initialize/install-directives.js +2 -0
  62. package/list/projectsecret.vue +1 -1
  63. package/machine-config/azure.vue +1 -1
  64. package/mixins/chart.js +1 -1
  65. package/models/__tests__/chart.test.ts +17 -9
  66. package/models/__tests__/compliance.cattle.io.clusterscanprofile.spec.js +30 -0
  67. package/models/catalog.cattle.io.app.js +1 -1
  68. package/models/chart.js +3 -1
  69. package/models/compliance.cattle.io.clusterscanprofile.js +1 -1
  70. package/models/management.cattle.io.authconfig.js +1 -0
  71. package/package.json +2 -2
  72. package/pages/auth/login.vue +5 -2
  73. package/pages/auth/verify.vue +1 -1
  74. package/pages/c/_cluster/apps/charts/AppChartCardSubHeader.vue +3 -2
  75. package/pages/c/_cluster/apps/charts/chart.vue +2 -2
  76. package/pages/c/_cluster/explorer/EventsTable.vue +89 -3
  77. package/pages/c/_cluster/explorer/tools/index.vue +3 -3
  78. package/pages/c/_cluster/settings/performance.vue +12 -25
  79. package/pages/home.vue +313 -12
  80. package/plugins/axios.js +2 -1
  81. package/plugins/dashboard-store/actions.js +1 -1
  82. package/plugins/dashboard-store/resource-class.js +17 -2
  83. package/plugins/steve/steve-pagination-utils.ts +2 -2
  84. package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
  85. package/scripts/extension/publish +1 -1
  86. package/store/auth.js +8 -3
  87. package/store/aws.js +8 -6
  88. package/store/features.js +1 -0
  89. package/store/index.js +9 -3
  90. package/store/prefs.js +6 -0
  91. package/store/ui-context.ts +86 -0
  92. package/store/wm.ts +244 -0
  93. package/types/kube/kube-api.ts +2 -1
  94. package/types/rancher/index.d.ts +1 -0
  95. package/types/resources/settings.d.ts +29 -7
  96. package/types/shell/index.d.ts +59 -0
  97. package/types/window-manager.ts +22 -0
  98. package/utils/__tests__/cluster.test.ts +379 -1
  99. package/utils/cluster.js +157 -3
  100. package/utils/dynamic-content/__tests__/config.test.ts +187 -0
  101. package/utils/dynamic-content/__tests__/index.test.ts +390 -0
  102. package/utils/dynamic-content/__tests__/info.test.ts +263 -0
  103. package/utils/dynamic-content/__tests__/new-release.test.ts +216 -0
  104. package/utils/dynamic-content/__tests__/support-notice.test.ts +262 -0
  105. package/utils/dynamic-content/__tests__/util.test.ts +235 -0
  106. package/utils/dynamic-content/config.ts +55 -0
  107. package/utils/dynamic-content/index.ts +273 -0
  108. package/utils/dynamic-content/info.ts +219 -0
  109. package/utils/dynamic-content/new-release.ts +126 -0
  110. package/utils/dynamic-content/support-notice.ts +169 -0
  111. package/utils/dynamic-content/types.d.ts +101 -0
  112. package/utils/dynamic-content/util.ts +122 -0
  113. package/utils/dynamic-importer.js +2 -2
  114. package/utils/inactivity.ts +104 -0
  115. package/utils/pagination-utils.ts +19 -4
  116. package/utils/release-notes.ts +1 -1
  117. package/assets/images/icons/document.svg +0 -3
  118. package/store/wm.js +0 -95
  119. /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
  120. /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
  121. /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
  122. /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
  123. /package/components/{nav/WindowManager → Window}/Window.vue +0 -0
@@ -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 }`"
@@ -842,6 +862,7 @@ export default {
842
862
  .side-menu-logo {
843
863
  align-items: center;
844
864
  display: flex;
865
+ height: 55px;
845
866
  margin-right: 8px;
846
867
  max-width: 200px;
847
868
  padding: 12px 0;
@@ -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
+ };
@@ -0,0 +1,105 @@
1
+ import { computed } from 'vue';
2
+ import debounce from 'lodash/debounce';
3
+ import { useStore } from 'vuex';
4
+ import { BOTTOM, LEFT, RIGHT } from '@shell/utils/position';
5
+ import { Position } from '@shell/types/window-manager';
6
+ import { CSS_KEY } from '../constants';
7
+
8
+ /**
9
+ * This composable is responsible for handling the dimensions of the window manager panels.
10
+ */
11
+ export default (props: { position: Position }) => {
12
+ const store = useStore();
13
+
14
+ const height = computed({
15
+ get() {
16
+ const panelHeight = store.state.wm.panelHeight[props.position];
17
+
18
+ if (panelHeight) {
19
+ return panelHeight;
20
+ }
21
+
22
+ const windowHeight = window.innerHeight;
23
+ let height = panelHeight ? parseInt(panelHeight, 10) : 0;
24
+
25
+ if ( !height ) {
26
+ height = Math.round(windowHeight / 2);
27
+ }
28
+ height = Math.min(height, 3 * windowHeight / 4);
29
+
30
+ setDimensions({ height });
31
+
32
+ return height;
33
+ },
34
+
35
+ set(height) {
36
+ setDimensions({ height });
37
+ },
38
+ });
39
+
40
+ const width = computed({
41
+ get() {
42
+ const panelWidth = store.state.wm.panelWidth[props.position];
43
+
44
+ if (panelWidth) {
45
+ return panelWidth;
46
+ }
47
+
48
+ const windowWidth = window.innerWidth;
49
+ let width = panelWidth ? parseInt(panelWidth, 10) : 0;
50
+
51
+ if (!width) {
52
+ width = Math.round(windowWidth / 8);
53
+ }
54
+ width = Math.min(width, 3 * windowWidth / 4);
55
+
56
+ setDimensions({ width });
57
+
58
+ return width;
59
+ },
60
+ set(width) {
61
+ setDimensions({ width });
62
+ }
63
+ });
64
+
65
+ function openPanel(position: Position) {
66
+ setCssVariable(position, position === BOTTOM ? height.value : width.value);
67
+ }
68
+
69
+ function closePanel(position: Position) {
70
+ setCssVariable(position, 0);
71
+ }
72
+
73
+ function setDimensions(args: { width?: number, height?: number } = {}) {
74
+ const h = Number(args.height || height.value) || 0;
75
+ const w = Number(args.width || width.value) || 0;
76
+
77
+ switch (props.position) {
78
+ case RIGHT:
79
+ case LEFT:
80
+ setCssVariable(props.position, w);
81
+ debouncedSetPanelWidth(props.position, w);
82
+ break;
83
+ case BOTTOM:
84
+ setCssVariable(BOTTOM, h);
85
+ debouncedSetPanelHeight(props.position, h);
86
+ break;
87
+ }
88
+ }
89
+
90
+ const debouncedSetPanelWidth = debounce((position, width) => {
91
+ store.commit('wm/setPanelWidth', { position, width });
92
+ }, 250);
93
+
94
+ const debouncedSetPanelHeight = debounce((position, height) => {
95
+ store.commit('wm/setPanelHeight', { position, height });
96
+ }, 250);
97
+
98
+ function setCssVariable(position: Position, value: number) {
99
+ document.documentElement.style.setProperty(CSS_KEY[position as keyof typeof CSS_KEY], `${ value }px`);
100
+ }
101
+
102
+ return {
103
+ width, height, setDimensions, openPanel, closePanel,
104
+ };
105
+ };
@@ -0,0 +1,99 @@
1
+ import { useStore } from 'vuex';
2
+ import { Position, Tab } from '@shell/types/window-manager';
3
+ import { computed, ref } from 'vue';
4
+ import { CENTER } from '@shell/utils/position';
5
+
6
+ /**
7
+ * This composable is responsible for handling the drag-and-drop behavior of tabs within the window manager.
8
+ *
9
+ * dragOverPositionsActive: A reactive reference indicating whether a drag operation is currently over the position areas.
10
+ * pinArea: A reactive reference representing the current position area being hovered over during a drag operation.
11
+ *
12
+ * Both of these references are shared across all instances of this composable (used by Horizontal and Vertical Panels)
13
+ * to maintain consistent drag-and-drop state.
14
+ */
15
+
16
+ const dragOverPositionsActive = ref(false);
17
+ const pinArea = ref(CENTER as Position);
18
+
19
+ export default (props?: { position: Position }) => {
20
+ const store = useStore();
21
+
22
+ const lockedPositions = computed(() => (store.state.wm.lockedPositions || []) as Position[]);
23
+ const lockedPosition = computed(() => props?.position ? lockedPositions.value.includes(props.position) : false);
24
+
25
+ const pin = computed({
26
+ get(): Position {
27
+ return store.state.wm.userPin;
28
+ },
29
+ set(pin: Position) {
30
+ if (pin !== CENTER) {
31
+ store.commit('wm/setUserPin', pin);
32
+ }
33
+ },
34
+ });
35
+
36
+ const dragOverTabBarActive = ref(false);
37
+
38
+ function onDragPositionStart(value: { event: DragEvent, tab: Tab }) {
39
+ value.event.dataTransfer?.setData('application/json', JSON.stringify({
40
+ position: value.tab.position,
41
+ tabId: value.tab.id
42
+ }));
43
+ dragOverPositionsActive.value = true;
44
+ dragOverTabBarActive.value = true;
45
+ }
46
+
47
+ function onDragPositionOver(event: DragEvent, position: Position) {
48
+ pinArea.value = position;
49
+ if (position !== CENTER) {
50
+ event.preventDefault();
51
+ }
52
+ }
53
+
54
+ function onDragPositionEnd(value: { event: DragEvent, tab: Tab }) {
55
+ pin.value = pinArea.value;
56
+ if (pinArea.value !== CENTER) {
57
+ store.commit('wm/switchTab', { tabId: value.tab.id, targetPosition: pinArea.value });
58
+ }
59
+ dragOverPositionsActive.value = false;
60
+ pinArea.value = CENTER;
61
+ }
62
+
63
+ function onTabBarDragOver(event: DragEvent) {
64
+ dragOverTabBarActive.value = true;
65
+ event.preventDefault();
66
+ }
67
+
68
+ function onTabBarDragLeave() {
69
+ dragOverTabBarActive.value = false;
70
+ }
71
+
72
+ function onTabBarDrop(event: DragEvent) {
73
+ dragOverTabBarActive.value = false;
74
+ const data = event.dataTransfer?.getData('application/json');
75
+
76
+ if (data) {
77
+ const { tabId } = JSON.parse(data);
78
+
79
+ store.commit('wm/switchTab', { tabId, targetPosition: props?.position });
80
+ } else {
81
+ console.warn('No data found in drag event'); // eslint-disable-line no-console
82
+ }
83
+ }
84
+
85
+ return {
86
+ dragOverPositionsActive,
87
+ dragOverTabBarActive,
88
+ pinArea,
89
+ pin,
90
+ lockedPositions,
91
+ lockedPosition,
92
+ onTabBarDragOver,
93
+ onTabBarDragLeave,
94
+ onTabBarDrop,
95
+ onDragPositionStart,
96
+ onDragPositionOver,
97
+ onDragPositionEnd
98
+ };
99
+ };
@@ -0,0 +1,72 @@
1
+ import { computed, onMounted, onBeforeUnmount } from 'vue';
2
+ import { useStore } from 'vuex';
3
+ import { Position, Tab } from '@shell/types/window-manager';
4
+ import useResizeHandler from '../composables/useResizeHandler';
5
+ import useDimensionsHandler from '../composables/useDimensionsHandler';
6
+ import useDragHandler from '../composables/useDragHandler';
7
+ import useTabsHandler from '../composables/useTabsHandler';
8
+
9
+ /**
10
+ * This composable is responsible for managing the state and behavior of the window manager panel.
11
+ */
12
+ export default (props: { position: Position }) => {
13
+ const store = useStore();
14
+
15
+ const tabs = computed(() => store.getters['wm/tabs'].filter((t: Tab) => t.position === props.position));
16
+
17
+ const isTabsHeaderEnabled = computed(() => tabs.value.every((t: Tab) => t.showHeader));
18
+
19
+ const {
20
+ activeTab, setTabActive, onTabReady, onTabClose, onPanelClose
21
+ } = useTabsHandler();
22
+
23
+ const {
24
+ height, width, setDimensions, openPanel, closePanel
25
+ } = useDimensionsHandler({ position: props.position });
26
+
27
+ const {
28
+ mouseResizeYStart, keyboardResizeY, mouseResizeXStart, keyboardResizeX
29
+ } = useResizeHandler({ position: props.position, setDimensions });
30
+
31
+ const {
32
+ dragOverPositionsActive,
33
+ dragOverTabBarActive,
34
+ onTabBarDragOver,
35
+ onTabBarDragLeave,
36
+ onTabBarDrop,
37
+ onDragPositionStart,
38
+ onDragPositionEnd,
39
+ lockedPosition
40
+ } = useDragHandler({ position: props.position });
41
+
42
+ onMounted(() => openPanel(props.position));
43
+
44
+ onBeforeUnmount(() => {
45
+ closePanel(props.position);
46
+ onPanelClose(props.position);
47
+ });
48
+
49
+ return {
50
+ tabs,
51
+ activeTab,
52
+ isTabsHeaderEnabled,
53
+ height,
54
+ width,
55
+ dragOverPositionsActive,
56
+ dragOverTabBarActive,
57
+ setTabActive,
58
+ onTabReady,
59
+ onTabClose,
60
+ onPanelClose,
61
+ mouseResizeXStart,
62
+ mouseResizeYStart,
63
+ keyboardResizeX,
64
+ keyboardResizeY,
65
+ onTabBarDragOver,
66
+ onTabBarDragLeave,
67
+ onTabBarDrop,
68
+ onDragPositionStart,
69
+ onDragPositionEnd,
70
+ lockedPosition
71
+ };
72
+ };
@@ -0,0 +1,14 @@
1
+ import { computed } from 'vue';
2
+ import { useStore } from 'vuex';
3
+ import { Layout, Position, Tab } from '@shell/types/window-manager';
4
+
5
+ export default (props?: { positions: Position[], layout: Layout }) => {
6
+ const store = useStore();
7
+
8
+ const isPanelEnabled = computed(() => (props?.positions || []).reduce((acc, pos) => ({
9
+ ...acc,
10
+ [pos]: store.state.wm.open[pos] && !store.state.wm.tabs.find((t: Tab) => !t.layouts.includes(props?.layout || Layout.default) && t.position === pos),
11
+ }), {} as Record<Position, boolean>));
12
+
13
+ return { isPanelEnabled };
14
+ };