@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.
- package/assets/styles/global/_layout.scss +21 -35
- package/assets/translations/en-us.yaml +11 -6
- package/components/EmberPage.vue +1 -1
- package/components/Resource/Detail/CopyToClipboard.vue +1 -1
- package/components/Resource/Detail/TitleBar/__tests__/index.test.ts +0 -2
- package/components/Resource/Detail/TitleBar/index.vue +10 -6
- package/components/ResourceDetail/index.vue +3 -0
- package/components/SortableTable/index.vue +1 -0
- package/components/{nav/WindowManager → Window}/ContainerLogs.vue +1 -1
- package/components/{nav/WindowManager → Window}/ContainerLogsActions.vue +1 -0
- package/components/{nav/WindowManager → Window}/__tests__/ContainerLogs.test.ts +1 -1
- package/components/{nav/WindowManager → Window}/__tests__/ContainerShell.test.ts +2 -2
- package/components/nav/Header.vue +33 -13
- package/components/{DraggableZone.vue → nav/WindowManager/PinArea.vue} +47 -80
- package/components/nav/WindowManager/composables/useComponentsMount.ts +70 -0
- package/components/nav/WindowManager/composables/useDimensionsHandler.ts +105 -0
- package/components/nav/WindowManager/composables/useDragHandler.ts +99 -0
- package/components/nav/WindowManager/composables/usePanelHandler.ts +72 -0
- package/components/nav/WindowManager/composables/usePanelsHandler.ts +14 -0
- package/components/nav/WindowManager/composables/useResizeHandler.ts +167 -0
- package/components/nav/WindowManager/composables/useTabsHandler.ts +51 -0
- package/components/nav/WindowManager/constants.ts +23 -0
- package/components/nav/WindowManager/index.vue +61 -575
- package/components/nav/WindowManager/panels/HorizontalPanel.vue +265 -0
- package/components/nav/WindowManager/panels/TabBodyContainer.vue +39 -0
- package/components/nav/WindowManager/panels/VerticalPanel.vue +308 -0
- package/components/templates/default.vue +4 -40
- package/components/templates/home.vue +31 -5
- package/config/store.js +4 -2
- package/detail/pod.vue +1 -0
- package/directives/ui-context.ts +97 -0
- package/initialize/install-directives.js +2 -0
- package/package.json +2 -2
- package/rancher-components/RcDropdown/RcDropdownItemSelect.vue +5 -1
- package/store/ui-context.ts +86 -0
- package/store/wm.ts +244 -0
- package/types/window-manager.ts +22 -0
- package/utils/dynamic-importer.js +2 -2
- package/assets/images/icons/document.svg +0 -3
- package/store/wm.js +0 -95
- /package/components/{nav/WindowManager → Window}/ChartReadme.vue +0 -0
- /package/components/{nav/WindowManager → Window}/ContainerShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/KubectlShell.vue +0 -0
- /package/components/{nav/WindowManager → Window}/MachineSsh.vue +0 -0
- /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}"
|
package/components/EmberPage.vue
CHANGED
|
@@ -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 = '
|
|
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
|
|
@@ -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
|
-
<
|
|
103
|
-
|
|
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/
|
|
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';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { nextTick } from 'vue';
|
|
2
2
|
import { shallowMount } from '@vue/test-utils';
|
|
3
|
-
import ContainerLogs from '@shell/components/
|
|
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/
|
|
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/
|
|
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:
|
|
72
|
-
showTooltip:
|
|
73
|
-
isUserMenuOpen:
|
|
74
|
-
isPageActionMenuOpen:
|
|
75
|
-
kubeConfigCopying:
|
|
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:
|
|
80
|
-
extensionHeaderActions:
|
|
81
|
-
|
|
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 =
|
|
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="
|
|
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 {
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|
30
|
+
<div
|
|
31
|
+
v-if="dragOverPositionsActive"
|
|
32
|
+
class="pin-area-container"
|
|
33
|
+
>
|
|
74
34
|
<span
|
|
75
|
-
v-if="
|
|
35
|
+
v-if="pinArea != pin"
|
|
76
36
|
class="pin-effect-area"
|
|
77
|
-
:class="
|
|
37
|
+
:class="pinArea"
|
|
78
38
|
/>
|
|
79
39
|
<span
|
|
80
40
|
class="drag-area center"
|
|
81
|
-
@dragover="
|
|
41
|
+
@dragover="onDragPositionOver($event, CENTER)"
|
|
82
42
|
/>
|
|
83
43
|
<span
|
|
44
|
+
v-if="!lockedPositions.includes(RIGHT)"
|
|
84
45
|
class="drag-area right"
|
|
85
|
-
@dragover="
|
|
46
|
+
@dragover="onDragPositionOver($event, RIGHT)"
|
|
86
47
|
/>
|
|
87
48
|
<span
|
|
88
|
-
|
|
89
|
-
|
|
49
|
+
v-if="!lockedPositions.includes(LEFT)"
|
|
50
|
+
class="drag-area left"
|
|
51
|
+
@dragover="onDragPositionOver($event, LEFT)"
|
|
90
52
|
/>
|
|
91
53
|
<span
|
|
92
|
-
|
|
93
|
-
|
|
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:
|
|
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:
|
|
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
|
+
};
|