@kui-shell/plugin-client-common
Version:
Kui plugin that offers stylesheets
262 lines • 11.6 kB
JavaScript
/*
* Copyright 2020 The Kubernetes Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import React from 'react';
import { Page } from '@patternfly/react-core/dist/esm/components/Page';
import { pexecInCurrentTab } from '@kui-shell/core/mdist/api/Exec';
import { isReadOnlyClient } from '@kui-shell/core/mdist/api/Client';
import { eventBus } from '@kui-shell/core/mdist/api/Events';
import Sidebar from './Sidebar';
import TabModel, { uuidForFirstTab } from './TabModel';
import TabContent from './TabContent';
import TopTabStripe from './TopTabStripe';
import '../../../web/css/static/TabContainer.scss';
import '../../../web/scss/components/Page/_index.scss';
export default class TabContainer extends React.PureComponent {
constructor(props) {
super(props);
/** Has the first tab activated itself? */
this.isFirstTabReady = false;
this._onSwitchTab = this.onSwitchTab.bind(this);
this._onCloseTab = this.onCloseTab.bind(this);
this._onNewTab = () => this.onNewTab();
this._onTabReady = this.onTabReady.bind(this);
this._onSetTabTitle = (uuid, title) => {
this.setState(({ tabs }) => {
const tabIdx = tabs.findIndex(_ => _.uuid === uuid);
if (tabIdx < 0 || tabs[tabIdx].title === title) {
return null;
}
else {
return {
tabs: [...tabs.slice(0, tabIdx), tabs[tabIdx].setTitle(title), ...tabs.slice(tabIdx + 1)]
};
}
});
};
this.toggleSidebar = () => {
this.setState(curState => ({ isSidebarOpen: !curState.isSidebarOpen }));
};
this.toggleSidebarFromSidebar = () => {
if (this.props.guidebooksExpanded !== true) {
this.toggleSidebar();
}
};
this.state = {
isSidebarOpen: this.props.guidebooksExpanded || false,
tabs: [this.newTabModel(undefined, undefined, uuidForFirstTab().toString())],
activeIdx: 0
};
}
componentDidMount() {
eventBus.on('/tab/new/request', (evt) => {
// the protocol is: if we are opening multiple tabs in the
// "foreground", then make sure the *first* of the new tabs is
// active
const weWillAssertActiveTab = !evt.background && evt.tabs.length > 1;
evt.tabs.forEach((spec, idx) => {
// re: the `&& idx > 0` part: we do want the *first* tab to assert activeness
const doNotChangeActiveTab = evt.background || (weWillAssertActiveTab && idx > 0);
this.onNewTab(spec, doNotChangeActiveTab);
});
if (weWillAssertActiveTab) {
// here is where make sure the *first* of the new tabs is
// active
this.setState(curState => ({
activeIdx: curState.tabs.length - evt.tabs.length
}));
}
});
eventBus.on('/tab/switch/request', (idx) => {
this.onSwitchTab(idx);
});
eventBus.on('/kui/tab/edit/toggle/index', (idx) => {
const tab = this.state.tabs[idx];
if (tab && tab.uuid) {
eventBus.emitWithTabId('/kui/tab/edit/toggle', tab.uuid);
}
});
}
/** save tab state such as CWD prior to a tab switch */
captureState() {
try {
this.state.tabs[this.state.activeIdx].state.capture();
}
catch (err) {
console.error(err);
}
}
/** restore tab state after a tab switch */
restoreState(tabIdx) {
this.state.tabs[tabIdx].state.restore();
}
/**
* Switch Tab event: update state so that activeIdx=idx
*
*/
onSwitchTab(idx) {
return __awaiter(this, void 0, void 0, function* () {
// capture current state, restore state of the switched-to tab
const currentTabState = this.state.tabs[this.state.activeIdx].state;
const nextTabState = this.state.tabs[idx].state;
yield currentTabState.switchTo(nextTabState);
if (idx >= 0 && idx < this.state.tabs.length) {
this.setState({
activeIdx: idx
});
}
setTimeout(() => eventBus.emit('/tab/switch/request/done', { idx, tab: nextTabState }));
});
}
/**
* Close Tab event
*
*/
onCloseTab(idx) {
return __awaiter(this, void 0, void 0, function* () {
// execute a command onClose?
const tabModel = this.state.tabs[idx];
if (tabModel && tabModel.onClose) {
try {
yield pexecInCurrentTab(tabModel.onClose, undefined, true);
}
catch (err) {
console.error('Error executing tab onClose handler', err);
}
}
const residualTabs = this.state.tabs.slice(0, idx).concat(this.state.tabs.slice(idx + 1));
if (residualTabs.length > 0) {
const activeIdx = idx === 0 ? 0 : idx - 1;
this.restoreState(activeIdx);
this.setState({
tabs: residualTabs,
activeIdx
});
}
});
}
listenForTabClose(model) {
eventBus.onceWithTabId('/tab/close/request', model.uuid, (uuid, tab) => __awaiter(this, void 0, void 0, function* () {
if (this.state.tabs.length === 1) {
// then we are closing the last tab, so close the window
tab.REPL.qexec('window close');
}
else {
this.onCloseTab(this.state.tabs.findIndex(_ => _.uuid === model.uuid));
}
}));
}
newTabModel(spec = {}, doNotChangeActiveTab = false, uuid) {
// !this.state means: if this is the very first tab we've ever
// !created, *and* we were given an initial title (via
// !this.props.title), then use that
const model = new TabModel(uuid, spec.statusStripeDecoration, doNotChangeActiveTab, spec.title || this.props.title, undefined, undefined, spec.cmdline, spec.onClose, spec.exec);
this.listenForTabClose(model);
return model;
}
/**
* New Tab event
*
*/
onNewTab(spec = {}, doNotChangeActiveTab = false) {
// if we already have a tab with this title, and this isn't a
// background tab, then switch to it
if (spec.title) {
const existingIdx = this.state.tabs.findIndex(_ => _.title === spec.title);
if (existingIdx >= 0) {
if (!doNotChangeActiveTab) {
this.onSwitchTab(existingIdx);
}
return;
}
}
this.captureState();
const model = this.newTabModel(spec, doNotChangeActiveTab);
this.setState(curState => ({
tabs: curState.tabs.concat(model),
activeIdx: !doNotChangeActiveTab ? curState.tabs.length : curState.activeIdx
}));
}
graft(node, uuid, key) {
if (React.isValidElement(node)) {
// ^^^ this check avoids tsc errors
return React.cloneElement(node, {
key,
uuid
});
}
else {
return node;
}
}
/** Graft the tab `uuid` */
children(uuid) {
if (Array.isArray(this.props.children)) {
return this.props.children.map((child, idx) => this.graft(child, uuid, idx));
}
else {
return this.graft(this.props.children, uuid);
}
}
willUpdateTopTabButtons(uuid, buttons) {
this.setState(curState => {
const idx = curState.tabs.findIndex(_ => _.uuid === uuid);
if (idx >= 0) {
return {
tabs: curState.tabs
.slice(0, idx)
.concat([curState.tabs[idx].update(buttons)])
.concat(curState.tabs.slice(idx + 1))
};
}
});
}
onTabReady(tab) {
// "initial tab" handling
if (!this.isFirstTabReady) {
if (this.props.onTabReady) {
this.props.onTabReady(tab);
}
this.isFirstTabReady = true;
}
}
/** Render the row of Tabs along the top */
topTabStripe() {
return (React.createElement(TopTabStripe, { tabs: this.state.tabs, onNewTab: this._onNewTab, onCloseTab: this._onCloseTab, onSwitchTab: this._onSwitchTab, activeIdx: this.state.activeIdx, topTabNames: this.props.topTabNames, needsSidebar: this.needsSidebar, isSidebarOpen: this.state.isSidebarOpen, onToggleSidebar: this.toggleSidebar, noTopTabs: this.props.noTopTabs, noNewTabButton: this.props.noNewTabButton, noNewSplitButton: this.props.noNewSplitButton, closeableTabs: this.state.tabs.length > 1 && (this.props.closeableTabs || !isReadOnlyClient()) }));
}
/** Render the content of the tabs */
tabContent() {
return (React.createElement("div", { className: "tab-container" }, this.state.tabs.map((_, idx) => (React.createElement(TabContent, Object.assign({}, this.props, { tabTitle: _.title, key: _.uuid, uuid: _.uuid, active: idx === this.state.activeIdx, initialCommandLine: _.initialCommandLine, onTabReady: this._onTabReady, willSetTitle: this._onSetTabTitle, state: _.state }), this.children(_.uuid))))));
}
get needsSidebar() {
return !!this.props.guidebooks;
}
sidebar() {
return (React.createElement(Sidebar, { version: this.props.version, isOpen: this.state.isSidebarOpen, toggleOpen: this.toggleSidebarFromSidebar, noTopTabs: this.props.noTopTabs, guidebooks: this.props.guidebooks, productName: this.props.productName, indicateActiveItem: !!this.props.noTopTabs, guidebooksCommand: this.props.guidebooksCommand }));
}
render() {
return (React.createElement(Page, { mainContainerId: "kui--page-main", className: "kui--tab-container-page", header: this.topTabStripe(), sidebar: this.sidebar(), "data-sidebar-open": this.state.isSidebarOpen || undefined }, this.tabContent()));
}
}
//# sourceMappingURL=TabContainer.js.map