alm
Version:
The best IDE for TypeScript
1,120 lines • 62.3 kB
JavaScript
"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