diff --git a/examples/api-samples/src/browser/api-samples-frontend-module.ts b/examples/api-samples/src/browser/api-samples-frontend-module.ts index 2c2fe27d83186..e3741fbc0c3b6 100644 --- a/examples/api-samples/src/browser/api-samples-frontend-module.ts +++ b/examples/api-samples/src/browser/api-samples-frontend-module.ts @@ -15,14 +15,10 @@ ********************************************************************************/ import { ContainerModule } from 'inversify'; -import { CommandContribution } from '@theia/core'; -import { LabelProviderContribution } from '@theia/core/lib/browser/label-provider'; -import { ApiSamplesContribution } from './api-samples-contribution'; -import { SampleDynamicLabelProviderContribution } from './sample-dynamic-label-provider-contribution'; +import { bindDynamicLabelProvider } from './label/sample-dymanic-label-provider-command-contribution'; +import { bindSampleUnclosableView } from './view/sample-unclosable-view-contribution'; export default new ContainerModule(bind => { - bind(CommandContribution).to(ApiSamplesContribution).inSingletonScope(); - - bind(SampleDynamicLabelProviderContribution).toSelf().inSingletonScope(); - bind(LabelProviderContribution).toService(SampleDynamicLabelProviderContribution); + bindDynamicLabelProvider(bind); + bindSampleUnclosableView(bind); }); diff --git a/examples/api-samples/src/browser/api-samples-contribution.ts b/examples/api-samples/src/browser/label/sample-dymanic-label-provider-command-contribution.ts similarity index 76% rename from examples/api-samples/src/browser/api-samples-contribution.ts rename to examples/api-samples/src/browser/label/sample-dymanic-label-provider-command-contribution.ts index 14c856fa1a848..ed941899eb03d 100644 --- a/examples/api-samples/src/browser/api-samples-contribution.ts +++ b/examples/api-samples/src/browser/label/sample-dymanic-label-provider-command-contribution.ts @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ -import { injectable, inject } from 'inversify'; +import { injectable, inject, interfaces } from 'inversify'; import { Command, CommandContribution, CommandRegistry, CommandHandler } from '@theia/core'; -import { FrontendApplicationContribution } from '@theia/core/lib/browser'; +import { FrontendApplicationContribution, LabelProviderContribution } from '@theia/core/lib/browser'; import { SampleDynamicLabelProviderContribution } from './sample-dynamic-label-provider-contribution'; export namespace ExampleLabelProviderCommands { @@ -29,7 +29,7 @@ export namespace ExampleLabelProviderCommands { } @injectable() -export class ApiSamplesContribution implements FrontendApplicationContribution, CommandContribution { +export class SampleDynamicLabelProviderCommandContribution implements FrontendApplicationContribution, CommandContribution { @inject(SampleDynamicLabelProviderContribution) protected readonly labelProviderContribution: SampleDynamicLabelProviderContribution; @@ -53,3 +53,10 @@ export class ExampleLabelProviderCommandHandler implements CommandHandler { } } + +export const bindDynamicLabelProvider = (bind: interfaces.Bind) => { + bind(SampleDynamicLabelProviderContribution).toSelf().inSingletonScope(); + bind(LabelProviderContribution).toService(SampleDynamicLabelProviderContribution); + bind(CommandContribution).to(SampleDynamicLabelProviderCommandContribution).inSingletonScope(); +}; + diff --git a/examples/api-samples/src/browser/sample-dynamic-label-provider-contribution.ts b/examples/api-samples/src/browser/label/sample-dynamic-label-provider-contribution.ts similarity index 100% rename from examples/api-samples/src/browser/sample-dynamic-label-provider-contribution.ts rename to examples/api-samples/src/browser/label/sample-dynamic-label-provider-contribution.ts index d4b2a50c9930d..887f8b8b7a1c1 100644 --- a/examples/api-samples/src/browser/sample-dynamic-label-provider-contribution.ts +++ b/examples/api-samples/src/browser/label/sample-dynamic-label-provider-contribution.ts @@ -14,9 +14,9 @@ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 ********************************************************************************/ +import { injectable } from 'inversify'; import { DefaultUriLabelProviderContribution, DidChangeLabelEvent } from '@theia/core/lib/browser/label-provider'; import URI from '@theia/core/lib/common/uri'; -import { injectable } from 'inversify'; import { Emitter, Event } from '@theia/core'; @injectable() diff --git a/examples/api-samples/src/browser/view/sample-unclosable-view-contribution.ts b/examples/api-samples/src/browser/view/sample-unclosable-view-contribution.ts new file mode 100644 index 0000000000000..bbce09018a4bd --- /dev/null +++ b/examples/api-samples/src/browser/view/sample-unclosable-view-contribution.ts @@ -0,0 +1,45 @@ +/******************************************************************************** + * Copyright (C) 2020 TORO Limited and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { AbstractViewContribution, bindViewContribution, WidgetFactory } from '@theia/core/lib/browser'; +import { SampleViewUnclosableView } from './sample-unclosable-view'; +import { injectable, interfaces } from 'inversify'; + +@injectable() +export class SampleUnclosableViewContribution extends AbstractViewContribution { + + static readonly SAMPLE_UNCLOSABLE_VIEW_TOGGLE_COMMAND_ID = 'sampleUnclosableView:toggle'; + + constructor() { + super({ + widgetId: SampleViewUnclosableView.ID, + widgetName: 'Sample Unclosable View', + toggleCommandId: SampleUnclosableViewContribution.SAMPLE_UNCLOSABLE_VIEW_TOGGLE_COMMAND_ID, + defaultWidgetOptions: { + area: 'main' + } + }); + } +} + +export const bindSampleUnclosableView = (bind: interfaces.Bind) => { + bindViewContribution(bind, SampleUnclosableViewContribution); + bind(SampleViewUnclosableView).toSelf(); + bind(WidgetFactory).toDynamicValue(ctx => ({ + id: SampleViewUnclosableView.ID, + createWidget: () => ctx.container.get(SampleViewUnclosableView) + })); +}; diff --git a/examples/api-samples/src/browser/view/sample-unclosable-view.tsx b/examples/api-samples/src/browser/view/sample-unclosable-view.tsx new file mode 100644 index 0000000000000..eb90c18c15180 --- /dev/null +++ b/examples/api-samples/src/browser/view/sample-unclosable-view.tsx @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (C) 2020 TORO Limited and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the Eclipse + * Public License v. 2.0 are satisfied: GNU General Public License, version 2 + * with the GNU Classpath Exception which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + ********************************************************************************/ + +import { ReactWidget } from '@theia/core/lib/browser'; +import { injectable, postConstruct } from 'inversify'; +import * as React from 'react'; + +/** + * This sample view is used to demo the behavior of "Widget.title.closable". + */ +@injectable() +export class SampleViewUnclosableView extends ReactWidget { + static readonly ID = 'sampleUnclosableView'; + + @postConstruct() + init(): void { + this.id = SampleViewUnclosableView.ID; + this.title.caption = 'Sample Unclosable View'; + this.title.label = 'Sample Unclosable View'; + this.title.iconClass = 'fa fa-window-maximize'; + this.title.closable = false; + this.update(); + } + + protected render(): React.ReactNode { + return ( +
+ Closable + this.title.closable = e.target.checked} /> +
+ ); + } +} diff --git a/packages/core/src/browser/common-frontend-contribution.ts b/packages/core/src/browser/common-frontend-contribution.ts index 039787870f74c..af5b14e7917b0 100644 --- a/packages/core/src/browser/common-frontend-contribution.ts +++ b/packages/core/src/browser/common-frontend-contribution.ts @@ -520,7 +520,14 @@ export class CommonFrontendContribution implements FrontendApplicationContributi execute: () => this.shell.activatePreviousTab() }); commandRegistry.registerCommand(CommonCommands.CLOSE_TAB, { - isEnabled: (event?: Event) => this.findTabBar(event) !== undefined, + isEnabled: (event?: Event) => { + const tabBar = this.findTabBar(event); + if (!tabBar) { + return false; + } + const currentTitle = this.findTitle(tabBar, event); + return currentTitle !== undefined && currentTitle.closable; + }, execute: (event?: Event) => { const tabBar = this.findTabBar(event)!; const currentTitle = this.findTitle(tabBar, event); @@ -530,19 +537,22 @@ export class CommonFrontendContribution implements FrontendApplicationContributi commandRegistry.registerCommand(CommonCommands.CLOSE_OTHER_TABS, { isEnabled: (event?: Event) => { const tabBar = this.findTabBar(event); - return tabBar !== undefined && tabBar.titles.length > 1; + if (!tabBar) { + return false; + } + const currentTitle = this.findTitle(tabBar, event); + return tabBar.titles.some(title => title !== currentTitle && title.closable); }, execute: (event?: Event) => { const tabBar = this.findTabBar(event)!; const currentTitle = this.findTitle(tabBar, event); - const area = this.shell.getAreaFor(tabBar)!; - this.shell.closeTabs(area, title => title !== currentTitle); + this.shell.closeTabs(tabBar, title => title !== currentTitle && title.closable); } }); commandRegistry.registerCommand(CommonCommands.CLOSE_RIGHT_TABS, { isEnabled: (event?: Event) => { const tabBar = this.findTabBar(event); - return tabBar !== undefined && tabBar.currentIndex < tabBar.titles.length - 1; + return tabBar !== undefined && tabBar.titles.some((title, index) => index > tabBar.currentIndex && title.closable); }, isVisible: (event?: Event) => { const area = this.findTabArea(event); @@ -551,32 +561,37 @@ export class CommonFrontendContribution implements FrontendApplicationContributi execute: (event?: Event) => { const tabBar = this.findTabBar(event)!; const currentIndex = tabBar.currentIndex; - this.shell.closeTabs(tabBar, (_, index) => index > currentIndex); + this.shell.closeTabs(tabBar, (title, index) => index > currentIndex && title.closable); } }); commandRegistry.registerCommand(CommonCommands.CLOSE_ALL_TABS, { - isEnabled: (event?: Event) => this.findTabBar(event) !== undefined, - execute: (event?: Event) => this.shell.closeTabs(this.findTabArea(event)!) + isEnabled: (event?: Event) => { + const tabBar = this.findTabBar(event); + return tabBar !== undefined && tabBar.titles.some(title => title.closable); + }, + execute: (event?: Event) => this.shell.closeTabs(this.findTabBar(event)!, title => title.closable) }); commandRegistry.registerCommand(CommonCommands.CLOSE_MAIN_TAB, { - isEnabled: () => this.shell.getCurrentWidget('main') !== undefined, + isEnabled: () => { + const currentWidget = this.shell.getCurrentWidget('main'); + return currentWidget !== undefined && currentWidget.title.closable; + }, execute: () => this.shell.getCurrentWidget('main')!.close() }); commandRegistry.registerCommand(CommonCommands.CLOSE_OTHER_MAIN_TABS, { isEnabled: () => { - const tabBars = this.shell.mainAreaTabBars; - return tabBars.length > 1 || tabBars.length === 1 && tabBars[0].titles.length > 1; + const currentWidget = this.shell.getCurrentWidget('main'); + return currentWidget !== undefined && + this.shell.mainAreaTabBars.some(tb => tb.titles.some(title => title.owner !== currentWidget && title.closable)); }, execute: () => { const currentWidget = this.shell.getCurrentWidget('main'); - if (currentWidget !== undefined) { - this.shell.closeTabs('main', title => title.owner !== currentWidget); - } + this.shell.closeTabs('main', title => title.owner !== currentWidget && title.closable); } }); commandRegistry.registerCommand(CommonCommands.CLOSE_ALL_MAIN_TABS, { - isEnabled: () => this.shell.mainAreaTabBars.find(tb => tb.titles.length > 0) !== undefined, - execute: () => this.shell.closeTabs('main') + isEnabled: () => this.shell.mainAreaTabBars.some(tb => tb.titles.some(title => title.closable)), + execute: () => this.shell.closeTabs('main', title => title.closable) }); commandRegistry.registerCommand(CommonCommands.COLLAPSE_PANEL, { isEnabled: (event?: Event) => ApplicationShell.isSideArea(this.findTabArea(event)),