ui/app/components/page-header/component.js

252 lines
6.8 KiB
JavaScript

import { get, set, setProperties } from '@ember/object';
import { computed, observer } from '@ember/object';
import { alias } from '@ember/object/computed';
import Component from '@ember/component';
import { inject as service } from '@ember/service'
import layout from './template';
import C from 'shared/utils/constants';
import { get as getTree } from 'shared/utils/navigation-tree';
import { run } from '@ember/runloop';
import $ from 'jquery';
function fnOrValue(val, ctx) {
if ( typeof val === 'function' ) {
return val.call(ctx);
} else {
return val;
}
}
export default Component.extend({
// Injections
intl: service(),
scope: service(),
features: service(),
settings: service(),
access: service(),
prefs: service(),
router: service(),
layout,
// Inputs
pageScope: null,
// Component options
tagName: 'header',
classNames: ['page-header'],
dropdownSelector: '.navbar .dropdown',
stacks: null,
// This computed property generates the active list of choices to display
navTree: null,
clusterId: alias('scope.currentCluster.id'),
cluster: alias('scope.currentCluster'),
projectId: alias('scope.currentProject.id'),
project: alias('scope.currentProject'),
accessEnabled: alias('access.enabled'),
init() {
this._super(...arguments);
get(this, 'intl.locale');
setProperties(this, {
stacks: this.store.all('stack'),
hosts: this.store.all('host'),
stackSchema: this.store.getById('schema', 'stack'),
});
run.once(this, 'updateNavTree');
run.scheduleOnce('render', this, this.setupResponsiveNav);
},
didInsertElement() {
run.scheduleOnce('afterRender', this, this.setupTearDown);
},
shouldUpdateNavTree: observer(
'pageScope',
'clusterId',
'cluster.isReady',
'projectId',
'stacks.@each.group',
`prefs.${ C.PREFS.ACCESS_WARNING }`,
'access.enabled',
'intl.locale',
function() {
run.scheduleOnce('afterRender', this, 'updateNavTree');
}
),
// beyond things listed in "Inputs"
hasProject: computed('project', function() {
return !!this.project;
}),
// Hackery: You're an owner if you can write to the 'system' field of a stack
isOwner: computed('stackSchema.resourceFields.system.update', function() {
return !!get(this, 'stackSchema.resourceFields.system.update');
}),
dashboardBaseLink: computed('scope.dashboardBase', function() {
return get(this, 'scope.dashboardBase').replace(/\/+$/, '');
}),
dashboardLink: computed('cluster.isReady', 'clusterId', 'pageScope', 'scope.dashboardLink', function() {
if ( this.pageScope === 'global' || !this.clusterId ) {
// Only inside a cluster
return;
}
const cluster = this.cluster;
if ( !cluster || !cluster.isReady ) {
// Only in ready/active clusters
return;
}
return get(this, 'scope.dashboardLink');
}),
updateNavTree() {
const currentScope = this.pageScope;
const out = getTree().filter((item) => {
if ( typeof get(item, 'condition') === 'function' ) {
if ( !item.condition.call(this) ) {
return false;
}
}
if ( get(item, 'scope') && get(item, 'scope') !== currentScope ) {
return false;
}
const itemRoute = fnOrValue(get(item, 'route'), this);
const itemContext = (get(item, 'ctx') || []).map( (prop) => fnOrValue(prop, this));
setProperties(item, {
localizedLabel: fnOrValue(get(item, 'localizedLabel'), this),
label: fnOrValue(get(item, 'label'), this),
route: itemRoute,
ctx: itemContext,
submenu: fnOrValue(get(item, 'submenu'), this),
});
set(item, 'submenu', ( get(item, 'submenu') || [] ).filter((subitem) => {
if ( typeof get(subitem, 'condition') === 'function' && !subitem.condition.call(this) ) {
return false;
}
const subItemRoute = fnOrValue(get(subitem, 'route'), this);
const subItemContext = ( get(subitem, 'ctx') || [] ).map( (prop) => fnOrValue(prop, this));
setProperties(subitem, {
localizedLabel: fnOrValue(get(subitem, 'localizedLabel'), this),
label: fnOrValue(get(subitem, 'label'), this),
route: subItemRoute,
ctx: subItemContext,
});
return true;
}));
return true;
});
const old = JSON.stringify(this.navTree);
const neu = JSON.stringify(out);
if ( old !== neu ) {
set(this, 'navTree', out);
}
},
keyUp(e) {
const code = e.keyCode;
let tabList = $(`.nav-item a:first-of-type`);
let $target = $(e.target).hasClass('ember-basic-dropdown-trigger') ? $(e.target).find('a') : e.target;
let currentFocusIndex = tabList.index($target);
let nextIndex;
switch (code) {
case 37: {
// left
nextIndex = currentFocusIndex - 1;
if (nextIndex >= tabList.length) {
tabList.eq(tabList.length).focus();
} else {
if (tabList.eq(nextIndex).parent().hasClass('ember-basic-dropdown-trigger')) {
tabList.eq(nextIndex).parent().focus();
} else {
tabList.eq(nextIndex).focus();
}
}
break;
}
case 39: {
// right
nextIndex = currentFocusIndex + 1;
if (nextIndex >= tabList.length) {
tabList.eq(0).focus();
} else {
if (tabList.eq(nextIndex).parent().hasClass('ember-basic-dropdown-trigger')) {
tabList.eq(nextIndex).parent().focus();
} else {
tabList.eq(nextIndex).focus();
}
}
break;
}
default:
}
},
setupResponsiveNav() {
// responsive nav 63-87
var responsiveNav = document.getElementById('js-responsive-nav');
var toggleBtn = document.createElement('a');
toggleBtn.setAttribute('class', 'nav-toggle');
responsiveNav.insertBefore(toggleBtn, responsiveNav.firstChild);
function hasClass(e, t){
return (new RegExp(` ${ t } `)).test(` ${ e.className } `)
}
function toggleClass(e, t){
var n = ` ${ e.className.replace(/[\t\r\n]/g, ' ') } `;
if (hasClass(e, t)){
while (n.indexOf(` ${ t } `) >= 0){
n = n.replace(` ${ t } `, ' ')
}e.className = n.replace(/^\s+|\s+$/g, '')
} else {
e.className += ` ${ t }`
}
}
toggleBtn.onclick = function() {
toggleClass(this.parentNode, 'nav-open');
}
var root = document.documentElement;
root.className = `${ root.className } js`;
},
setupTearDown() {
this.router.on('routeWillChange', () => {
$('header > nav').removeClass('nav-open');// eslint-disable-line
});
}
});