UNPKG

@kui-shell/plugin-client-common

Version:

Kui plugin that offers stylesheets

262 lines 11.6 kB
/* * 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