UNPKG

alm

Version:

The best IDE for TypeScript

1,120 lines 62.3 kB
"use strict"; /** * This maintains the User interface Tabs for app, * e.g. selected tab, any handling of open tab requests etc. */ var __extends = (this && this.__extends) || (function () { var extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); Object.defineProperty(exports, "__esModule", { value: true }); var ui = require("../../ui"); var React = require("react"); var ReactDOM = require("react-dom"); var tabRegistry = require("./tabRegistry"); var commands = require("../../commands/commands"); var utils = require("../../../common/utils"); var csx = require("../../base/csx"); var utils_1 = require("../../../common/utils"); var types = require("../../../common/types"); var tips_1 = require("./../tips"); var socketClient_1 = require("../../../socket/socketClient"); var clientSession_1 = require("../../state/clientSession"); var onresize = require("onresize"); var events_1 = require("../../../common/events"); var state = require("../../state/state"); var pure = require("../../../common/pure"); var globalErrorCacheClient_1 = require("../../globalErrorCacheClient"); /** * * Golden layout * */ var GoldenLayout = require("golden-layout"); require('golden-layout/src/css/goldenlayout-base.css'); require('golden-layout/src/css/goldenlayout-dark-theme.css'); /** Golden layout wants react / react-dom to be global */ window.React = React; window.ReactDOM = ReactDOM; /** Some additional styles */ require('./appTabsContainer.css'); var AppTabsContainer = /** @class */ (function (_super) { __extends(AppTabsContainer, _super); function AppTabsContainer(props) { var _this = _super.call(this, props) || this; /** Used to undo close tab */ _this.closedTabs = []; _this.ctrls = {}; /** * Does exactly what it says. */ _this.addTabToLayout = function (tab, sendToServer) { if (sendToServer === void 0) { sendToServer = true; } _this.tabs.push(tab); if (sendToServer) { _this.sendTabInfoToServer(); _this.tabState.refreshTipHelp(); } var url = tab.url, id = tab.id; var _a = utils.getFilePathAndProtocolFromUrl(tab.url), protocol = _a.protocol, filePath = _a.filePath; var props = { url: url, additionalData: tab.additionalData, onSavedChanged: function (saved) { return _this.onSavedChanged(tab, saved); }, onFocused: function () { if (_this.selectedTabInstance && _this.selectedTabInstance.id === id) return; _this.tabState.selectTab(id); }, api: _this.createTabApi(id), setCodeEditor: function (codeEditor) { return _this.codeEditorMap[id] = codeEditor; } }; var title = tabRegistry.getTabConfigByUrl(url).getTitle(url); // Find the active stack if any var currentItemAndParent = _this.getCurrentTabRootStackIfAny(); // By default we try to add to (in desending order) // The current stack // the root stack // the root var contentRoot = (currentItemAndParent && currentItemAndParent.parent) || _this.layout.root.contentItems[0] || _this.layout.root; contentRoot.addChild({ type: 'react-component', component: protocol, title: title, props: props, id: id }); }; _this.moveTabUtils = { /** * Utility to create a container that doesn't reinitialize its children */ createContainer: function (type) { var item = _this.layout.createContentItem({ type: type, content: [] }); item.isInitialised = true; // Prevents initilizing its children return item; }, /** * Utility that doesn't *uninitalize* the child it is removing */ detachFromParent: function (item) { item.parent.removeChild(item, true); }, }; _this.moveCurrentTabRightIfAny = function () { var currentItemAndParent = _this.getCurrentTabRootStackIfAny(); if (!currentItemAndParent) return; var item = currentItemAndParent.item, parent = currentItemAndParent.parent; var root = parent.parent; /** Can't move the last item */ if (parent.contentItems.length === 1) { return; } // If parent.parent is a `row` its prettier to just add to that row ;) if (root.type === 'row') { // Create a new container for just this tab _this.moveTabUtils.detachFromParent(item); var newItemRootElement = _this.moveTabUtils.createContainer('stack'); newItemRootElement.addChild(item); // Add this new container to the root root.addChild(newItemRootElement); } else { var indexOfParentInRoot = parent.parent.contentItems.findIndex(function (c) { return c == parent; }); // Create a new container for just this tab _this.moveTabUtils.detachFromParent(item); var newItemRootElement = _this.moveTabUtils.createContainer('stack'); newItemRootElement.addChild(item); // Create a new layout to be the root of the two stacks var newRootLayout_1 = _this.moveTabUtils.createContainer('row'); newRootLayout_1.addChild(newItemRootElement); // Also add the old parent to this new row var doTheDetachAndAdd = function () { // Doing this detach immediately breaks the layout // This is because for column / row when a child is removed the splitter is gone // And by chance this is the splitter that the new item was going to :-/ _this.moveTabUtils.detachFromParent(parent); newRootLayout_1.addChild(parent, 0); }; if (root.type === 'column') { setTimeout(doTheDetachAndAdd, 10); } else { // type `root` *must* only have a single item at a time :) // So we *must* do it sync for that case doTheDetachAndAdd(); } // Add this new container to the root root.addChild(newRootLayout_1, indexOfParentInRoot); } }; _this.moveCurrentTabDownIfAny = function () { // Very similar to moveCurrentTabRightIfAny // Just replaced `row` with `column` and `column` with `row`. // This code can be consolidated but leaving as seperate as I suspect they might diverge var currentItemAndParent = _this.getCurrentTabRootStackIfAny(); if (!currentItemAndParent) return; var item = currentItemAndParent.item, parent = currentItemAndParent.parent; var root = parent.parent; /** Can't move the last item */ if (parent.contentItems.length === 1) { return; } // If parent.parent is a `column` its prettier to just add to that column ;) if (root.type === 'column') { // Create a new container for just this tab _this.moveTabUtils.detachFromParent(item); var newItemRootElement = _this.moveTabUtils.createContainer('stack'); newItemRootElement.addChild(item); // Add this new container to the root root.addChild(newItemRootElement); } else { var indexOfParentInRoot = parent.parent.contentItems.findIndex(function (c) { return c == parent; }); // Create a new container for just this tab _this.moveTabUtils.detachFromParent(item); var newItemRootElement = _this.moveTabUtils.createContainer('stack'); newItemRootElement.addChild(item); // Create a new layout to be the root of the two stacks var newRootLayout_2 = _this.moveTabUtils.createContainer('column'); newRootLayout_2.addChild(newItemRootElement); // Also add the old parent to this new row var doTheDetachAndAdd = function () { // Doing this detach immediately breaks the layout // This is because for column / row when a child is removed the splitter is gone // And by chance this is the splitter that the new item was going to :-/ _this.moveTabUtils.detachFromParent(parent); newRootLayout_2.addChild(parent, 0); }; if (root.type === 'row') { setTimeout(doTheDetachAndAdd, 10); } else { // type `root` *must* only have a single item at a time :) // So we *must* do it sync for that case doTheDetachAndAdd(); } // Add this new container to the root root.addChild(newRootLayout_2, indexOfParentInRoot); } }; _this.sendTabInfoToServer = function () { var serialized = GLUtil.serializeConfig(_this.layout.toConfig(), _this); socketClient_1.server.setOpenUITabs({ sessionId: clientSession_1.getSessionId(), tabLayout: serialized, selectedTabId: _this.selectedTabInstance && _this.selectedTabInstance.id }); }; /** * Tab State */ /** Our way to send stuff (events) down to the tab */ _this.tabApi = Object.create(null); /** This is different from the Tab Api in that its stuff *we* render for the tab */ _this.tabHandle = Object.create(null); /** * Having access to the current code editor is vital for some parts of the application * For this reason we allow the CodeTab to tell us about its codeEditor instance */ _this.codeEditorMap = Object.create(null); _this.tabs = []; /** Tab Sate */ _this.tabState = { /** * Resize handling */ _resizingDontReFocus: false, debouncedResize: utils.debounce(function () { _this.tabState._resizingDontReFocus = true; _this.tabState.resize(); }, 200), resize: function () { _this.layout.updateSize(); _this.tabState.resizeTheTabs(); }, resizeTheTabs: function () { _this.tabs.forEach(function (t) { return _this.tabApi[t.id].resize.emit({}); }); }, setTabs: function (tabs) { _this.tabs = tabs; _this.sendTabInfoToServer(); _this.tabState.refreshTipHelp(); }, selectTab: function (id) { var lastSelectedTab = _this.selectedTabInstance; if (lastSelectedTab && id !== lastSelectedTab.id) { _this.tabApi[lastSelectedTab.id] && _this.tabApi[lastSelectedTab.id].willBlur.emit({}); } _this.selectedTabInstance = _this.tabs.find(function (t) { return t.id == id; }); _this.tabState.focusSelectedTabIfAny(); if (!lastSelectedTab || (lastSelectedTab && lastSelectedTab.id !== id)) { _this.sendTabInfoToServer(); } }, focusSelectedTabIfAny: function () { _this.selectedTabInstance && _this.tabApi[_this.selectedTabInstance.id].focus.emit({}); }, triggerFocusAndSetAsSelected: function (id) { _this.tabHandle[id].triggerFocus(); _this.tabState.selectTab(id); }, refreshTipHelp: function () { // If no tabs show tips // If some tabs hide tips _this.tabs.length ? TipRender.hideTips() : TipRender.showTips(); }, tabClosedInLayout: function (id) { var closedTabInstance = _this.tabs.find(function (t) { return t.id == id; }); _this.closedTabs.push(closedTabInstance); var index = _this.tabs.map(function (t) { return t.id; }).indexOf(id); delete _this.tabHandle[id]; delete _this.tabApi[id]; delete _this.codeEditorMap[id]; _this.tabState.setTabs(_this.tabs.filter(function (t) { return t.id !== id; })); // Figure out the tab which will become active if (_this.selectedTabInstance && _this.selectedTabInstance.id === id) { _this.selectedTabInstance = GLUtil.prevOnClose({ id: id, config: _this.layout.toConfig() }); } // The close tab logic inside golden layout, can disconnect the active tab logic of ours // (we try to preserve current tab if some other tab closes) // So no matter what we need to refocus on the selected tab from within Golden-Layout if (_this.selectedTabInstance) { _this.tabHandle[_this.selectedTabInstance.id].triggerFocus(); } }, gotoPosition: function (id, position) { setTimeout(function () { return _this.tabApi[id].gotoPosition.emit(position); }); }, /** * Tab closing */ closeCurrentTab: function () { if (!_this.selectedTabInstance) return; _this.tabState.closeTabById(_this.selectedTabInstance.id); }, closeTabById: function (id) { _this.tabHandle[id].triggerClose(); }, /** * Fast tab jumping */ _showingTabIndexes: false, _jumpToTabNumber: function (oneBasedIndex) { var index = oneBasedIndex - 1; if (!_this.tabs[index]) { return; } var tab = _this.tabs[index]; _this.tabState.triggerFocusAndSetAsSelected(tab.id); _this.tabState.hideTabIndexes(); }, _fastTabJumpListener: function (evt) { var keyCodeFor1 = 49; var tabNumber = evt.keyCode - keyCodeFor1 + 1; if (tabNumber >= 1 && tabNumber <= 9) { _this.tabState._jumpToTabNumber(tabNumber); } if (evt.keyCode == 39) { _this.moveCurrentTabRightIfAny(); } if (evt.keyCode == 40) { _this.moveCurrentTabDownIfAny(); } if (evt.keyCode == 68) { commands.duplicateTab.emit({}); } // prevent key prop evt.preventDefault(); evt.stopPropagation(); evt.stopImmediatePropagation(); _this.tabState.hideTabIndexes(); // console.log(evt, tabNumber); // DEBUG }, _removeOnMouseDown: function (evt) { _this.tabState.hideTabIndexes(); }, showTabIndexes: function () { if (_this.tabState._showingTabIndexes) { _this.tabState.hideTabIndexes(); } // this.debugLayoutTree(); // DEBUG _this.tabState._showingTabIndexes = true; window.addEventListener('keydown', _this.tabState._fastTabJumpListener); window.addEventListener('mousedown', _this.tabState._removeOnMouseDown); _this.tabs.map(function (t, i) { if (!_this.tabHandle[t.id]) { return; } _this.tabHandle[t.id].showIndex(i + 1); }); if (_this.selectedTabInstance) { TabMoveHelp.showHelp(); } }, hideTabIndexes: function () { _this.tabState._showingTabIndexes = false; window.removeEventListener('keydown', _this.tabState._fastTabJumpListener); window.removeEventListener('mousedown', _this.tabState._removeOnMouseDown); _this.tabs.map(function (t, i) { if (!_this.tabHandle[t.id]) { return; } _this.tabHandle[t.id].hideIndex(); }); TabMoveHelp.hideHelp(); }, /** * Not to be used locally. * This is an external API used to drive app tabs contianer for refactorings etc. */ getFocusedCodeEditorIfAny: function () { if (!_this.selectedTabInstance || !_this.selectedTabInstance.id || !_this.selectedTabInstance.url.startsWith('file:') || !_this.codeEditorMap[_this.selectedTabInstance.id]) { return null; } return _this.codeEditorMap[_this.selectedTabInstance.id]; }, getSelectedTab: function () { return _this.selectedTabInstance; }, getSelectedFilePath: function () { var selected = _this.selectedTabInstance; if (selected) { var url = selected.url; if (url.startsWith('file://')) { return utils.getFilePathFromUrl(url); } } }, getOpenFilePaths: function () { return _this.tabs.filter(function (t) { return t.url.startsWith('file://'); }).map(function (t) { return utils.getFilePathFromUrl(t.url); }); }, addTabs: function (tabs) { tabs.forEach(function (tab) { return _this.addTabToLayout(tab); }); }, /** * TODO: this function is a bit heavy so can use some caching */ errorsByFilePathFiltered: function () { var allState = state.getState(); var filter = allState.errorsFilter.trim(); var mode = allState.errorsDisplayMode; var allErrors = globalErrorCacheClient_1.errorsCache.getErrors(); /** Flatten errors as we need those for "gotoHistory" */ var errorsFlattened = utils.selectMany(Object.keys(allErrors).map(function (x) { return allErrors[x]; })); /** Filter by string if any */ if (filter) { errorsFlattened = errorsFlattened.filter(function (e) { return e.filePath.includes(filter) || e.message.includes(filter) || e.preview.includes(filter); }); } /** Filter by path if any */ if (mode === types.ErrorsDisplayMode.openFiles) { var openFilePaths_1 = utils.createMap(_this.tabState.getOpenFilePaths()); errorsFlattened = errorsFlattened.filter(function (e) { return openFilePaths_1[e.filePath]; }); } return { errorsFlattened: errorsFlattened, errorsByFilePath: utils.createMapByKey(errorsFlattened, function (e) { return e.filePath; }) }; } }; /** Setup the singleton */ exports.tabState = _this.tabState; return _this; } AppTabsContainer.prototype.componentDidMount = function () { var _this = this; socketClient_1.server.getOpenUITabs({ sessionId: clientSession_1.getSessionId() }).then(function (res) { var config = GLUtil.unserializeConfig(res.tabLayout, _this); /** This is needed as we use this ordered information in quite a few places */ _this.tabs = GLUtil.orderedTabs(config); /** * Setup golden layout * https://golden-layout.com/docs/Config.html */ _this.layout = new GoldenLayout(config, _this.ctrls.root); /** * Register all the tab components with layout */ tabRegistry.getTabConfigs().forEach(function (_a) { var protocol = _a.protocol, config = _a.config; _this.layout.registerComponent(protocol, config.component); }); /** Setup window resize */ _this.disposible.add(onresize.on(function () { return _this.tabState.resize(); })); /** * Tab selection * I tried to use the config to figure out the selected tab. * That didn't work out so well * So the plan is to intercept the tab clicks to focus * and on state changes just focus on the last selected tab if any */ _this.layout.on('tabCreated', function (tabInfo) { _this.createTabHandle(tabInfo); }); _this.layout.on('itemDestroyed', function (evt) { if (evt.config && evt.config.id) { _this.tabState.tabClosedInLayout(evt.config.id); } }); var oldConfig = config; var initialStateChange = true; _this.layout.on('stateChanged', function (evt) { if (initialStateChange) { // Select the last tab _this.tabs.length && res.selectedTabId && _this.tabs.find(function (t) { return t.id === res.selectedTabId; }) && exports.tabState.triggerFocusAndSetAsSelected(res.selectedTabId); initialStateChange = false; } var newConfig = _this.layout.toConfig(); var contentEqual = function (a, b) { if (a.type !== b.type) return false; if (a.activeItemIndex !== b.activeItemIndex) return false; if (!pure.shallowEqual(a.dimension, b.dimension)) return false; if (a.height !== b.height) return false; if (a.width !== b.width) return false; if (a.content) { if (!b.content) return false; if (a.content.length !== b.content.length) return false; return a.content.every(function (c, i) { return contentEqual(c, b.content[i]); }); } return true; }; var equal = contentEqual(oldConfig, newConfig); oldConfig = newConfig; if (equal) { return; } // Due to state changes layout needs to happen on *all tabs* (because it might expose some other tabs) // PREF : you can go thorough all the `stack` in the layout and only call resize on the active ones. _this.tabState.resizeTheTabs(); // Ignore the events where the user is dragging stuff // This is because at this time the `config` doesn't contain the *dragged* item. var orderedtabs = GLUtil.orderedTabs(newConfig); if (orderedtabs.length !== _this.tabs.length) { return; } // Store the tabs in the right order _this.tabState.setTabs(orderedtabs); // If there was a selected tab focus on it again. if (_this.tabState._resizingDontReFocus) { _this.tabState._resizingDontReFocus = false; } else { _this.selectedTabInstance && _this.tabState.selectTab(_this.selectedTabInstance.id); } }); // initialize the layout _this.layout.init(); // If there are no tabs then show the tip help if (!_this.tabs.length) _this.tabState.refreshTipHelp(); /** * General command handling */ _this.setupCommandHandling(); }); }; /** * Lots of commands are raised in the app that we need to care about * e.g. tab saving / tab switching / file opening etc. * We do that all in here */ AppTabsContainer.prototype.setupCommandHandling = function () { var _this = this; // commands.bas.on((e) => { // const filePath = getCurrentFilePathOrWarn(); // if (!filePath) return; // server.gitDiff({ filePath }).then(res => { // console.log(res); // }); // }); commands.saveTab.on(function (e) { _this.getSelectedTabApiIfAny().save.emit({}); }); commands.esc.on(function () { // Focus selected tab _this.tabState.focusSelectedTabIfAny(); // Exit jump tab mode _this.tabState.hideTabIndexes(); // Hide search if (_this.tabApi[_this.selectedTabInstance && _this.selectedTabInstance.id]) { _this.tabApi[_this.selectedTabInstance && _this.selectedTabInstance.id].search.hideSearch.emit({}); } }); commands.jumpToTab.on(function () { // jump to tab _this.tabState.showTabIndexes(); }); /** * File opening commands */ commands.toggleOutputJS.on(function () { var filePath = getCurrentFilePathOrWarn(); if (!filePath) return; var outputStatus = state.getState().outputStatusCache[filePath]; if (!outputStatus) { ui.notifyWarningNormalDisappear('Your current tab needs to be a TypeScript file in project and project should have compileOnSave enabled'); return; } commands.doToggleFileTab.emit({ filePath: outputStatus.outputFilePath }); }); commands.doToggleFileTab.on(function (_a) { var filePath = _a.filePath; // If tab is open we just close it var existing = _this.tabs.find(function (t) { return utils.getFilePathFromUrl(t.url) == filePath; }); if (existing) { _this.tabState.closeTabById(existing.id); return; } // Othewise we open the tab, and select back to the current tab var currentTabId = _this.selectedTabInstance && _this.selectedTabInstance.id; commands.doOpenOrActivateFileTab.emit({ filePath: filePath }); _this.moveCurrentTabRightIfAny(); if (currentTabId) { _this.tabState.triggerFocusAndSetAsSelected(currentTabId); } }); commands.doOpenFile.on(function (e) { var codeTab = { id: utils_1.createId(), url: "file://" + e.filePath, additionalData: null, }; // Add tab _this.addTabToLayout(codeTab); // Focus _this.tabState.selectTab(codeTab.id); if (e.position) { _this.tabApi[codeTab.id].gotoPosition.emit(e.position); } }); commands.doOpenOrFocusFile.on(function (e) { // if open and not focused then focus and goto pos var existingTab = // Open and current (_this.selectedTabInstance && utils.getFilePathFromUrl(_this.selectedTabInstance.url) === e.filePath && utils.getFilePathAndProtocolFromUrl(_this.selectedTabInstance.url).protocol === tabRegistry.tabs.file.protocol && _this.selectedTabInstance) // Open but not current || _this.tabs.find(function (t) { return utils.getFilePathFromUrl(t.url) == e.filePath && utils.getFilePathAndProtocolFromUrl(t.url).protocol === tabRegistry.tabs.file.protocol; }); if (existingTab) { // Focus if not focused if (!_this.selectedTabInstance || _this.selectedTabInstance.id !== existingTab.id) { _this.tabState.triggerFocusAndSetAsSelected(existingTab.id); } if (e.position) { _this.tabState.gotoPosition(existingTab.id, e.position); } } else { commands.doOpenFile.emit(e); } }); commands.doOpenOrActivateFileTab.on(function (e) { // Basically we have to maintain the current focus var activeElement = document.activeElement; commands.doOpenOrFocusFile.emit(e); if (activeElement) { setTimeout(function () { return $(activeElement).focus(); }, 100); } }); commands.doOpenOrFocusTab.on(function (e) { // if open and not focused then focus and goto pos var existingTab = _this.tabs.find(function (t) { return t.id == e.tabId; }); if (existingTab) { // Focus if not focused if (!_this.selectedTabInstance || _this.selectedTabInstance.id !== existingTab.id) { _this.tabState.triggerFocusAndSetAsSelected(existingTab.id); } if (e.position) { _this.tabState.gotoPosition(existingTab.id, e.position); } } else { var codeTab = { id: e.tabId, url: e.tabUrl, additionalData: null }; // Add tab _this.addTabToLayout(codeTab); // Focus _this.tabState.selectTab(codeTab.id); if (e.position) { _this.tabState.gotoPosition(codeTab.id, e.position); } } }); commands.closeFilesDirs.on(function (e) { var toClose = function (filePath) { return e.files.indexOf(filePath) !== -1 || e.dirs.some(function (dirPath) { return filePath.startsWith(dirPath); }); }; var tabsToClose = _this.tabs.filter(function (t, i) { var _a = utils.getFilePathAndProtocolFromUrl(t.url), protocol = _a.protocol, filePath = _a.filePath; return protocol === 'file' && toClose(filePath); }); tabsToClose.forEach(function (t) { return _this.tabHandle[t.id].triggerClose(); }); }); commands.undoCloseTab.on(function () { if (_this.closedTabs.length) { var tab_1 = _this.closedTabs.pop(); // Add tab _this.addTabToLayout(tab_1); // Focus _this.tabState.selectTab(tab_1.id); } }); commands.duplicateTab.on(function (e) { var currentFilePath = getCurrentFilePathOrWarn(); if (!currentFilePath) return; if (!_this.selectedTabInstance) return; var codeTab = { id: utils_1.createId(), url: _this.selectedTabInstance.url, additionalData: _this.selectedTabInstance.additionalData }; // Add tab _this.addTabToLayout(codeTab); // Focus _this.tabState.selectTab(codeTab.id); }); /** * Goto tab by index */ var gotoNumber = this.tabState._jumpToTabNumber; commands.gotoTab1.on(function () { return gotoNumber(1); }); commands.gotoTab2.on(function () { return gotoNumber(2); }); commands.gotoTab3.on(function () { return gotoNumber(3); }); commands.gotoTab4.on(function () { return gotoNumber(4); }); commands.gotoTab5.on(function () { return gotoNumber(5); }); commands.gotoTab6.on(function () { return gotoNumber(6); }); commands.gotoTab7.on(function () { return gotoNumber(7); }); commands.gotoTab8.on(function () { return gotoNumber(8); }); commands.gotoTab9.on(function () { return gotoNumber(9); }); /** * Next and previous tabs */ commands.nextTab.on(function () { var currentTabId = _this.selectedTabInstance && _this.selectedTabInstance.id; var currentIndex = _this.tabs.findIndex(function (t) { return t.id === currentTabId; }); var nextIndex = utils.rangeLimited({ min: 0, max: _this.tabs.length - 1, num: currentIndex + 1, loopAround: true }); setTimeout(function () { _this.tabState.triggerFocusAndSetAsSelected(_this.tabs[nextIndex].id); }); }); commands.prevTab.on(function () { var currentTabId = _this.selectedTabInstance && _this.selectedTabInstance.id; var currentIndex = _this.tabs.findIndex(function (t) { return t.id === currentTabId; }); var nextIndex = utils.rangeLimited({ min: 0, max: _this.tabs.length - 1, num: currentIndex - 1, loopAround: true }); setTimeout(function () { _this.tabState.triggerFocusAndSetAsSelected(_this.tabs[nextIndex].id); }); }); /** * Close tab commands */ commands.closeTab.on(function (e) { // Remove the selected _this.tabState.closeCurrentTab(); }); commands.closeOtherTabs.on(function (e) { var currentTabId = _this.selectedTabInstance && _this.selectedTabInstance.id; var otherTabs = _this.tabs.filter(function (t) { return t.id !== currentTabId; }); otherTabs.forEach(function (t) { return _this.tabHandle[t.id].triggerClose(); }); }); commands.closeAllTabs.on(function (e) { commands.closeOtherTabs.emit({}); commands.closeTab.emit({}); }); /** * Find and Replace */ state.subscribeSub(function (state) { return state.findOptions; }, function (findQuery) { var api = _this.getSelectedTabApiIfAny(); var options = state.getState().findOptions; if (options.isShown) { api.search.doSearch.emit(options); } else { api.search.hideSearch.emit(options); } }); commands.findNext.on(function () { var component = _this.getSelectedTabApiIfAny(); var findOptions = state.getState().findOptions; component.search.findNext.emit(findOptions); }); commands.findPrevious.on(function () { var component = _this.getSelectedTabApiIfAny(); var findOptions = state.getState().findOptions; component.search.findPrevious.emit(findOptions); }); commands.replaceNext.on(function (e) { var component = _this.getSelectedTabApiIfAny(); component.search.replaceNext.emit(e); }); commands.replacePrevious.on(function (e) { var component = _this.getSelectedTabApiIfAny(); component.search.replacePrevious.emit(e); }); commands.replaceAll.on(function (e) { var component = _this.getSelectedTabApiIfAny(); component.search.replaceAll.emit(e); }); /** * Other Types Of tabs */ commands.doOpenDependencyView.on(function (e) { var codeTab = { id: utils_1.createId(), url: "dependency://Dependency View", additionalData: null, }; // Add tab _this.addTabToLayout(codeTab); // Focus _this.tabState.selectTab(codeTab.id); }); /** Only allows a single tab of a type */ var openOrFocusSingletonTab = function (_a) { var protocol = _a.protocol, url = _a.url; // if open and active => focus // if open and not active => active // if not open and active if (_this.selectedTabInstance && utils.getFilePathAndProtocolFromUrl(_this.selectedTabInstance && _this.selectedTabInstance.url).protocol === protocol) { _this.tabState.focusSelectedTabIfAny(); return; } var openTabIndex = _this.tabs.findIndex(function (t) { return utils.getFilePathAndProtocolFromUrl(t.url).protocol === protocol; }); if (openTabIndex != -1) { _this.tabState.triggerFocusAndSetAsSelected(_this.tabs[openTabIndex].id); return; } var newTab = { id: utils_1.createId(), url: url, additionalData: null, }; // Add tab _this.addTabToLayout(newTab); // Focus _this.tabState.selectTab(newTab.id); }; /** Documentation view */ commands.toggleDocumentationBrowser.on(function () { var protocol = tabRegistry.tabs.documentation.protocol; var url = protocol + "://Documentation"; openOrFocusSingletonTab({ protocol: protocol, url: url }); }); /** Tested view */ commands.doOpenTestResultsView.on(function () { var protocol = tabRegistry.tabs.tested.protocol; var url = protocol + "://Tested"; openOrFocusSingletonTab({ protocol: protocol, url: url }); }); /** Find and replace multi */ commands.findAndReplaceMulti.on(function (e) { var protocol = tabRegistry.tabs.farm.protocol; var url = protocol + "://Find And Replace"; openOrFocusSingletonTab({ protocol: protocol, url: url }); }); /** Live demo view */ commands.ensureLiveDemoTab.on(function (e) { var protocol = tabRegistry.tabs.livedemo.protocol; var url = protocol + "://" + e.filePath; var currentTabId = _this.selectedTabInstance && _this.selectedTabInstance.id; openOrFocusSingletonTab({ protocol: protocol, url: url }); _this.moveCurrentTabRightIfAny(); if (currentTabId) { _this.tabState.triggerFocusAndSetAsSelected(currentTabId); } }); commands.closeDemoTab.on(function (e) { // If tab is open we just close it var protocol = tabRegistry.tabs.livedemo.protocol; var existing = _this.tabs.find(function (t) { return utils.getFilePathAndProtocolFromUrl(t.url).protocol == protocol; }); if (existing) { _this.tabState.closeTabById(existing.id); return; } }); commands.ensureLiveDemoReactTab.on(function (e) { var protocol = tabRegistry.tabs.livedemoreact.protocol; var url = protocol + "://" + e.filePath; var currentTabId = _this.selectedTabInstance && _this.selectedTabInstance.id; openOrFocusSingletonTab({ protocol: protocol, url: url }); _this.moveCurrentTabRightIfAny(); if (currentTabId) { _this.tabState.triggerFocusAndSetAsSelected(currentTabId); } }); commands.closeDemoReactTab.on(function (e) { // If tab is open we just close it var protocol = tabRegistry.tabs.livedemoreact.protocol; var existing = _this.tabs.find(function (t) { return utils.getFilePathAndProtocolFromUrl(t.url).protocol == protocol; }); if (existing) { _this.tabState.closeTabById(existing.id); return; } }); /** AST view */ var getCurrentFilePathOrWarn = function () { var tab = _this.tabState.getSelectedTab(); var notify = function () { return ui.notifyWarningNormalDisappear('Need a valid file path for this action. Make sure you have a *file* tab as active'); }; if (!tab) { notify(); return; } var _a = utils.getFilePathAndProtocolFromUrl(tab.url), protocol = _a.protocol, filePath = _a.filePath; if (protocol !== 'file') { notify(); return; } return filePath; }; var openAnalysisViewForCurrentFilePath = function (getUrl) { var filePath = getCurrentFilePathOrWarn(); if (!filePath) return; var codeTab = { id: utils_1.createId(), url: getUrl(filePath), additionalData: null }; // Add tab _this.addTabToLayout(codeTab); // Focus _this.tabState.selectTab(codeTab.id); }; commands.doOpenASTView.on(function (e) { openAnalysisViewForCurrentFilePath(function (filePath) { return tabRegistry.tabs.ast.protocol + "://" + filePath; }); }); commands.doOpenASTFullView.on(function (e) { openAnalysisViewForCurrentFilePath(function (filePath) { return tabRegistry.tabs.astfull.protocol + "://" + filePath; }); }); commands.doOpenUmlDiagram.on(function (e) { openAnalysisViewForCurrentFilePath(function (filePath) { return tabRegistry.tabs.uml.protocol + "://" + filePath; }); }); commands.launchTsFlow.on(function (e) { var filePath = getCurrentFilePathOrWarn(); if (!filePath) return; var tsFlowTab = { id: utils_1.createId(), url: tabRegistry.tabs.tsflow.protocol + "://" + filePath, additionalData: { position: 0 /** TODO: tsflow load from actual file path current cursor */ } }; // Add tab _this.addTabToLayout(tsFlowTab); // Focus _this.tabState.selectTab(tsFlowTab.id); }); }; AppTabsContainer.prototype.render = function () { var _this = this; return (React.createElement("div", { ref: function (root) { return _this.ctrls.root = root; }, style: csx.extend(csx.vertical, csx.flex, { maxWidth: '100%', overflow: 'hidden' }), className: "app-tabs" })); }; AppTabsContainer.prototype.onSavedChanged = function (tab, saved) { if (this.tabHandle[tab.id]) { this.tabHandle[tab.id].setSaved(saved); } }; AppTabsContainer.prototype.getCurrentTabRootStackIfAny = function () { if (this.selectedTabInstance) { var id = this.selectedTabInstance.id; var item = this.layout.root.contentItems[0].getItemsById(id)[0]; if (item && item.parent && item.parent.type == 'stack') { return { item: item, parent: item.parent }; } } }; AppTabsContainer.prototype.createTabApi = function (id) { var api = newTabApi(); this.tabApi[id] = api; return api; }; AppTabsContainer.prototype.debugLayoutTree = function () { var config = this.layout.toConfig(); var root = config.content; var generateSpaces = function (indent) { return Array((indent * 2) + 1).map(function (i) { return " "; }).join(' '); }; var printItem = function (item, depth) { if (depth === void 0) { depth = 0; } if (!depth) { console.log('ROOT-----'); } else { var indent = generateSpaces(depth); console.log(indent + item.type); } if (item.content) { item.content.forEach(function (c) { return printItem(c, depth + 1); }); } }; // console.log(config); printItem(config); }; /** * If we have a selected tab we return its api. * Otherwise we create one on the fly so you don't need to worry about * constantly checking if selected tab. (pain from v1). */ AppTabsContainer.prototype.getSelectedTabApiIfAny = function () { if (this.selectedTabInstance && this.tabApi[this.selectedTabInstance.id]) { return this.tabApi[this.selectedTabInstance.id]; } else { return newTabApi(); } }; AppTabsContainer.prototype.getTabApi = function (id) { return this.tabApi[id]; }; AppTabsContainer.prototype.createTabHandle = function (tabInfo) { var _this = this; // console.log(tabInfo); var item = tabInfo.contentItem; var tab = tabInfo.element; var tabConfig = tabInfo.contentItem.config; var id = tabConfig.id; // mouse down because we want tab state to change even if user initiates a drag tab.on('mousedown', function (evt) { // if the user is center clicking, close the tab var centerClick = evt.button === 1; if (centerClick) { tab.find('.lm_close_tab').trigger('click'); return; } // But not if the user is clicking the close button var closeButtonClicked = evt.target && evt.target.className == "lm_close_tab"; if (closeButtonClicked) { return; } _this.tabState.selectTab(id); }); var tabIndexDisplay = null; var removeTabIndexDisplayIfAny = function () { if (tabIndexDisplay) { tabIndexDisplay.remove(); tabIndexDisplay = null; } }; this.tabHandle[id] = { saved: true, setSaved: function (saved) { _this.tabHandle[id].saved = saved; tab.toggleClass('unsaved', !saved); }, triggerFocus: function () { /** * setTimeout needed because we call `triggerFocus` when * golden-layout is still in the process of changing tabs sometimes */ setTimeout(function () { tabInfo.header.parent.setActiveContentItem(item); }); }, showIndex: function (index) { if (index > 9) { // Wow this user has too many tabs. Abort return; } tabIndexDisplay = $('<div class="alm_jumpIndex">' + index + '</div>'); tab.append(tabIndexDisplay); }, hideIndex: removeTabIndexDisplayIfAny, triggerClose: function () { tab.find('.lm_close_tab').trigger('click'); }, /** Selected class */ addSelectedClass: function () { tab.addClass('alm_selected'); }, removeSelectedClass: function () { tab.removeClass('alm_selected'); }, }; }; Object.defineProperty(AppTabsContainer.prototype, "selectedTabInstance", { get: function () { return this._selectedTabInstance; }, set: function (value) { // The active tab class management if (this._selectedTabInstance && this.tabHandle[this._selectedTabInstance.id]) { this.tabHandle[this._selectedTabInstance.id].removeSelectedClass(); } if (value && value.id && this.tabHandle[value.id]) { this.tabHandle[value.id].addSelectedClass(); } // We don't tell non current tabs about search states. // So we need to tell them if they *come into focus* if (value && value.id && this.tabApi[value.id]) { var api = this.tabApi[value.id]; var options = state.getState().findOptions; if (!options.isShown || !options.query) { api.search.hideSearch.emit({}); } else { ap