mirror of https://github.com/rancher/dashboard.git
292 lines
6.2 KiB
Vue
292 lines
6.2 KiB
Vue
<script>
|
|
import { mapGetters } from 'vuex';
|
|
import { insertAt, findBy } from '@/utils/array';
|
|
import { sortBy } from '@/utils/sort';
|
|
import { ucFirst } from '@/utils/string';
|
|
import { createPopper } from '@popperjs/core';
|
|
import $ from 'jquery';
|
|
|
|
export default {
|
|
data() {
|
|
return { previous: null };
|
|
},
|
|
|
|
computed: {
|
|
...mapGetters(['clusterId']),
|
|
...mapGetters('type-map', ['activeProducts']),
|
|
|
|
value: {
|
|
get() {
|
|
return this.$store.getters['productId'];
|
|
},
|
|
},
|
|
|
|
options() {
|
|
const t = this.$store.getters['i18n/t'];
|
|
const entries = this.activeProducts.map((p) => {
|
|
let label;
|
|
const key = `product.${ p.name }`;
|
|
|
|
if ( this.$store.getters['i18n/exists'](key) ) {
|
|
label = t(key);
|
|
} else {
|
|
label = ucFirst(p.name);
|
|
}
|
|
|
|
const out = {
|
|
label,
|
|
icon: `icon-${ p.icon || 'copy' }`,
|
|
value: p.name,
|
|
removable: p.removable !== false,
|
|
weight: p.weight || 1,
|
|
};
|
|
|
|
if ( p.externalLink ) {
|
|
out.kind = 'external';
|
|
out.link = p.externalLink;
|
|
} else if ( p.link ) {
|
|
out.kind = 'internal';
|
|
out.link = p.link;
|
|
} else {
|
|
out.kind = 'internal';
|
|
}
|
|
|
|
return out;
|
|
});
|
|
|
|
const out = sortBy(entries, ['weight:desc', 'label']);
|
|
let last;
|
|
|
|
for ( let i = out.length - 1 ; i >= 0 ; i-- ) {
|
|
if ( last && last !== out[i].weight ) {
|
|
insertAt(out, i + 1, {
|
|
label: `The great divide ${ i }`,
|
|
kind: 'divider',
|
|
disabled: true
|
|
});
|
|
}
|
|
|
|
last = out[i].weight;
|
|
}
|
|
|
|
return out;
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
focus() {
|
|
this.$refs.select.$refs.search.focus();
|
|
},
|
|
|
|
shortcutsActive() {
|
|
const forms = $('FORM');
|
|
|
|
return forms.length === 0;
|
|
},
|
|
|
|
switchToApps() {
|
|
if ( !this.shortcutsActive() ) {
|
|
return;
|
|
}
|
|
|
|
this.change('apps');
|
|
},
|
|
|
|
switchToExplorer() {
|
|
if ( !this.shortcutsActive() ) {
|
|
return;
|
|
}
|
|
|
|
this.change('explorer');
|
|
},
|
|
|
|
switchToFleet() {
|
|
if ( !this.shortcutsActive() ) {
|
|
return;
|
|
}
|
|
|
|
this.change('fleet');
|
|
},
|
|
|
|
change(product) {
|
|
const entry = findBy(this.options, 'value', product);
|
|
|
|
if ( !entry ) {
|
|
return;
|
|
}
|
|
|
|
if ( entry?.link ) {
|
|
if ( entry.kind === 'external' ) {
|
|
let windowName = '_blank';
|
|
|
|
// Non-removable external links (MCM) go to a named window
|
|
if ( entry.removable === false ) {
|
|
windowName = `R_${ product }`;
|
|
}
|
|
|
|
window.open(entry.link, windowName);
|
|
this.value = this.previous;
|
|
|
|
return;
|
|
} else {
|
|
window.location.href = entry.link;
|
|
}
|
|
}
|
|
|
|
this.previous = this.value;
|
|
|
|
// Try product-specific index first
|
|
const opt = {
|
|
name: `c-cluster-${ product }`,
|
|
params: { cluster: this.clusterId, product }
|
|
};
|
|
|
|
if ( !this.$router.getMatchedComponents(opt).length ) {
|
|
opt.name = 'c-cluster-product';
|
|
}
|
|
|
|
this.$router.push(opt);
|
|
},
|
|
|
|
withPopper(dropdownList, component, { width }) {
|
|
dropdownList.className += ' product-menu';
|
|
|
|
const popper = createPopper(component.$refs.toggle, dropdownList, {
|
|
strategy: 'fixed',
|
|
modifiers: [
|
|
{
|
|
name: 'toggleClass',
|
|
enabled: true,
|
|
phase: 'write',
|
|
fn({ state }) {
|
|
component.$el.setAttribute('x-placement', state.placement);
|
|
},
|
|
}]
|
|
});
|
|
|
|
return () => popper.destroy();
|
|
},
|
|
},
|
|
};
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<div class="filter">
|
|
<v-select
|
|
ref="select"
|
|
key="product"
|
|
:value="value"
|
|
:clearable="false"
|
|
:selectable="option => !option.disabled"
|
|
:options="options"
|
|
:reduce="opt=>opt.value"
|
|
:calculate-position="withPopper"
|
|
:append-to-body="true"
|
|
|
|
label="label"
|
|
@input="change"
|
|
>
|
|
<template v-slot:option="opt">
|
|
<template v-if="opt.kind === 'divider'">
|
|
<hr />
|
|
</template>
|
|
<template v-else>
|
|
<i class="product-icon icon icon-lg icon-fw" :class="{[opt.icon]: true}" />
|
|
{{ opt.label }}
|
|
<i v-if="opt.kind === 'external'" class="icon icon-external-link ml-10" />
|
|
</template>
|
|
</template>
|
|
</v-select>
|
|
<button v-shortkey.once="['p']" class="hide" @shortkey="focus()" />
|
|
<button v-shortkey.once="['f']" class="hide" @shortkey="switchToFleet()" />
|
|
<button v-shortkey.once="['e']" class="hide" @shortkey="switchToExplorer()" />
|
|
<button v-shortkey.once="['a']" class="hide" @shortkey="switchToApps($event)" />
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
.filter ::v-deep .v-select {
|
|
max-width: 100%;
|
|
display: inline-block;
|
|
|
|
&.vs--disabled .vs__actions {
|
|
display: none;
|
|
}
|
|
|
|
.vs__actions:after {
|
|
fill: white !important;
|
|
color: white !important;
|
|
}
|
|
|
|
.vs__dropdown-toggle {
|
|
height: var(--header-height);
|
|
// margin-left: 35px;
|
|
background-color: transparent;
|
|
border: 0;
|
|
position: relative;
|
|
// left: 35px;
|
|
|
|
.vs__actions {
|
|
// margin-left: -10px;
|
|
}
|
|
}
|
|
|
|
.vs__selected {
|
|
user-select: none;
|
|
cursor: default;
|
|
color: white;
|
|
line-height: calc(var(--header-height) - 14px);
|
|
position: relative;
|
|
left: 40px;
|
|
}
|
|
}
|
|
|
|
.filter ::v-deep INPUT {
|
|
width: auto;
|
|
background-color: transparent;
|
|
}
|
|
|
|
.filter ::v-deep INPUT:hover {
|
|
background-color: transparent;
|
|
}
|
|
|
|
</style>
|
|
|
|
<style lang="scss">
|
|
.product-menu {
|
|
width: 300px;
|
|
|
|
.vs__dropdown-option {
|
|
padding: 10px;
|
|
text-decoration: none;
|
|
|
|
&.vs__dropdown-option--disabled {
|
|
// The dividers
|
|
padding: 0;
|
|
}
|
|
|
|
.product-icon {
|
|
color: var(--product-icon);
|
|
margin-right: 5px;
|
|
}
|
|
|
|
UL {
|
|
margin: 0;
|
|
}
|
|
}
|
|
|
|
.vs__dropdown-option--selected {
|
|
color: var(--body-text);
|
|
|
|
.product-icon {
|
|
color: var(--product-icon-active);
|
|
}
|
|
|
|
A, A:hover, A:focus {
|
|
color: var(--body-text);
|
|
}
|
|
}
|
|
}
|
|
</style>
|