diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb6bf162b8f9..b7bc01a121e45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # Change Log + +## v1.12.0 + +- [property-view] added initial version of a selection-based property-view [#8655](https://github.com/eclipse-theia/theia/pull/8655) + - A default implementation is available for file selections (via file navigator and default editors). + ## v1.11.0 - 2/25/2021 - [api-samples] added example to echo the currently supported vscode API version [#8191](https://github.com/eclipse-theia/theia/pull/8191) diff --git a/configs/root-compilation.tsconfig.json b/configs/root-compilation.tsconfig.json index 9544d5b8eab68..a2524c2381a6b 100644 --- a/configs/root-compilation.tsconfig.json +++ b/configs/root-compilation.tsconfig.json @@ -138,6 +138,9 @@ }, { "path": "../packages/timeline/compile.tsconfig.json" + }, + { + "path": "../packages/property-view/compile.tsconfig.json" } ] } diff --git a/examples/api-tests/src/menus.spec.js b/examples/api-tests/src/menus.spec.js index 0e1721ea350c5..c84fd45248ae4 100644 --- a/examples/api-tests/src/menus.spec.js +++ b/examples/api-tests/src/menus.spec.js @@ -38,6 +38,7 @@ describe('Menus', function () { const { OutputContribution } = require('@theia/output/lib/browser/output-contribution'); const { PluginFrontendViewContribution } = require('@theia/plugin-ext/lib/main/browser/plugin-frontend-view-contribution'); const { ProblemContribution } = require('@theia/markers/lib/browser/problem/problem-contribution'); + const { PropertyViewContribution } = require('@theia/property-view/lib/browser/property-view-contribution'); const { SearchInWorkspaceFrontendContribution } = require('@theia/search-in-workspace/lib/browser/search-in-workspace-frontend-contribution'); const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin'); @@ -67,6 +68,7 @@ describe('Menus', function () { container.get(OutputContribution), container.get(PluginFrontendViewContribution), container.get(ProblemContribution), + container.get(PropertyViewContribution), container.get(SearchInWorkspaceFrontendContribution) ]) { it(`should toggle '${contribution.viewLabel}' view`, async () => { diff --git a/examples/api-tests/src/views.spec.js b/examples/api-tests/src/views.spec.js index 7e494c0cc8be5..31c86e72da297 100644 --- a/examples/api-tests/src/views.spec.js +++ b/examples/api-tests/src/views.spec.js @@ -25,6 +25,7 @@ describe('Views', function () { const { ScmContribution } = require('@theia/scm/lib/browser/scm-contribution'); const { OutlineViewContribution } = require('@theia/outline-view/lib/browser/outline-view-contribution'); const { ProblemContribution } = require('@theia/markers/lib/browser/problem/problem-contribution'); + const { PropertyViewContribution } = require('@theia/property-view/lib/browser/property-view-contribution'); const { HostedPluginSupport } = require('@theia/plugin-ext/lib/hosted/browser/hosted-plugin'); /** @type {import('inversify').Container} */ @@ -34,6 +35,7 @@ describe('Views', function () { const scmContribution = container.get(ScmContribution); const outlineContribution = container.get(OutlineViewContribution); const problemContribution = container.get(ProblemContribution); + const propertyViewContribution = container.get(PropertyViewContribution); const pluginService = container.get(HostedPluginSupport); before(() => Promise.all([ @@ -44,7 +46,7 @@ describe('Views', function () { })() ])); - for (const contribution of [navigatorContribution, scmContribution, outlineContribution, problemContribution]) { + for (const contribution of [navigatorContribution, scmContribution, outlineContribution, problemContribution, propertyViewContribution]) { it(`should toggle ${contribution.viewLabel}`, async function () { let view = await contribution.closeView(); if (view) { diff --git a/examples/browser/package.json b/examples/browser/package.json index 206495babf513..d103d429e17df 100644 --- a/examples/browser/package.json +++ b/examples/browser/package.json @@ -42,6 +42,7 @@ "@theia/preferences": "1.11.0", "@theia/preview": "1.11.0", "@theia/process": "1.11.0", + "@theia/property-view": "1.11.0", "@theia/scm": "1.11.0", "@theia/scm-extra": "1.11.0", "@theia/search-in-workspace": "1.11.0", diff --git a/examples/electron/compile.tsconfig.json b/examples/electron/compile.tsconfig.json index 98e92c28ac586..a121e3fa9b92f 100644 --- a/examples/electron/compile.tsconfig.json +++ b/examples/electron/compile.tsconfig.json @@ -124,6 +124,9 @@ }, { "path": "../../packages/timeline/compile.tsconfig.json" + }, + { + "path": "../../packages/property-view/compile.tsconfig.json" } ] } diff --git a/examples/electron/package.json b/examples/electron/package.json index f1c7eec942a09..ccf6f307f1d65 100644 --- a/examples/electron/package.json +++ b/examples/electron/package.json @@ -42,6 +42,7 @@ "@theia/preferences": "1.11.0", "@theia/preview": "1.11.0", "@theia/process": "1.11.0", + "@theia/property-view": "1.11.0", "@theia/scm": "1.11.0", "@theia/scm-extra": "1.11.0", "@theia/search-in-workspace": "1.11.0", diff --git a/packages/property-view/.eslintrc.js b/packages/property-view/.eslintrc.js new file mode 100644 index 0000000000000..be9cf1a1b3dff --- /dev/null +++ b/packages/property-view/.eslintrc.js @@ -0,0 +1,10 @@ +/** @type {import('eslint').Linter.Config} */ +module.exports = { + extends: [ + '../../configs/build.eslintrc.json' + ], + parserOptions: { + tsconfigRootDir: __dirname, + project: 'compile.tsconfig.json' + } +}; diff --git a/packages/property-view/README.md b/packages/property-view/README.md new file mode 100644 index 0000000000000..46c9c72431c48 --- /dev/null +++ b/packages/property-view/README.md @@ -0,0 +1,44 @@ +
+ +
+ +theia-ext-logo + +

ECLIPSE THEIA - PROPERTY-VIEW EXTENSION

+ +
+ +
+ +## Description + +The `@theia/property-view` extension contributes a generic, global property view based on Theia's global selection. + +The property view widget can be opened/toggled either via menu _View->Properties_ or via shortcut Shift+Alt+P. It is located in the bottom dock area by default. + +The following two default content widgets are implemented in this extension: +- EmptyPropertyViewWidget: If no other widget can be provided, a simple message (_No properties available_) is shown. +- ResourcePropertyViewWidget: Can handle `FileSelection`s and `Navigatable` selections (which provide their resource URI) and displays the general `FileStat` information (e.g. location, name, last modified) in a TreeWidget. + +To contribute a specific property view, it is necessary to implement a `PropertyViewDataService` which gathers the property data for a selection as well as a `PropertyViewWidgetProvider` which provides a suitable content widget to display the property data for a specific selection inside the property view widget. + +
+ +## Additional Information + +- [API documentation for `@theia/property-view`](https://eclipse-theia.github.io/theia/docs/next/modules/property_view.html) +- [Theia - GitHub](https://github.com/eclipse-theia/theia) +- [Theia - Website](https://theia-ide.org/) + +## License + +- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/) +- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp) + +## Trademark +"Theia" is a trademark of the Eclipse Foundation +https://www.eclipse.org/theia + +## License +- [Eclipse Public License 2.0](http://www.eclipse.org/legal/epl-2.0/) +- [一 (Secondary) GNU General Public License, version 2 with the GNU Classpath Exception](https://projects.eclipse.org/license/secondary-gpl-2.0-cp) diff --git a/packages/property-view/compile.tsconfig.json b/packages/property-view/compile.tsconfig.json new file mode 100644 index 0000000000000..d3e8f1dc24e40 --- /dev/null +++ b/packages/property-view/compile.tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../configs/base.tsconfig", + "compilerOptions": { + "composite": true, + "rootDir": "src", + "outDir": "lib" + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../core/compile.tsconfig.json" + }, + { + "path": "../filesystem/compile.tsconfig.json" + } + ] +} diff --git a/packages/property-view/package.json b/packages/property-view/package.json new file mode 100644 index 0000000000000..85f7da578183b --- /dev/null +++ b/packages/property-view/package.json @@ -0,0 +1,46 @@ +{ + "name": "@theia/property-view", + "version": "1.11.0", + "description": "Theia - Property View Extension", + "dependencies": { + "@theia/core": "1.11.0", + "@theia/filesystem": "1.11.0" + }, + "publishConfig": { + "access": "public" + }, + "theiaExtensions": [ + { + "frontend": "lib/browser/property-view-frontend-module" + } + ], + "keywords": [ + "theia-extension" + ], + "license": "EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0", + "repository": { + "type": "git", + "url": "https://github.com/eclipse-theia/theia.git" + }, + "bugs": { + "url": "https://github.com/eclipse-theia/theia/issues" + }, + "homepage": "https://github.com/eclipse-theia/theia", + "files": [ + "lib", + "src" + ], + "scripts": { + "lint": "theiaext lint", + "build": "theiaext build", + "watch": "theiaext watch", + "clean": "theiaext clean", + "test": "theiaext test" + }, + "devDependencies": { + "@theia/ext-scripts": "1.11.0" + }, + "nyc": { + "extends": "../../configs/nyc.json" + } +} diff --git a/packages/property-view/src/browser/empty-property-view-widget-provider.tsx b/packages/property-view/src/browser/empty-property-view-widget-provider.tsx new file mode 100644 index 0000000000000..15f6bdc7f5da3 --- /dev/null +++ b/packages/property-view/src/browser/empty-property-view-widget-provider.tsx @@ -0,0 +1,78 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 } from 'inversify'; +import * as React from 'react'; +import { PropertyViewContentWidget } from './property-view-content-widget'; +import { DefaultPropertyViewWidgetProvider } from './property-view-widget-provider'; + +class EmptyPropertyViewWidget extends ReactWidget implements PropertyViewContentWidget { + + static readonly ID = 'theia-empty-property-view'; + static readonly LABEL = 'No Properties'; + + constructor() { + super(); + this.id = EmptyPropertyViewWidget.ID; + this.title.label = EmptyPropertyViewWidget.LABEL; + this.title.caption = EmptyPropertyViewWidget.LABEL; + this.title.closable = false; + this.node.tabIndex = 0; + } + + updatePropertyViewContent(): void { + this.update(); + } + + protected render(): React.ReactNode { + return this.emptyComponent; + } + + protected emptyComponent: JSX.Element =
No properties available.
; + +} + +/** + * `DefaultPropertyViewWidgetProvider` is implemented to provide the PropertyViewEmptyWidget + * if the given selection is undefined or no other provider can handle the given selection. + */ +@injectable() +export class EmptyPropertyViewWidgetProvider extends DefaultPropertyViewWidgetProvider { + + static readonly ID = 'no-properties'; + readonly id = EmptyPropertyViewWidgetProvider.ID; + readonly label = 'DefaultPropertyViewWidgetProvider'; + + private emptyWidget: EmptyPropertyViewWidget; + + constructor() { + super(); + this.emptyWidget = new EmptyPropertyViewWidget(); + } + + canHandle(selection: Object | undefined): number { + return selection === undefined ? 1 : 0; + } + + provideWidget(selection: Object | undefined): Promise { + return Promise.resolve(this.emptyWidget); + } + + updateContentWidget(selection: Object | undefined): void { + this.emptyWidget.updatePropertyViewContent(); + } +} diff --git a/packages/property-view/src/browser/property-data-service.ts b/packages/property-view/src/browser/property-data-service.ts new file mode 100644 index 0000000000000..6bc8d9bd5429d --- /dev/null +++ b/packages/property-view/src/browser/property-data-service.ts @@ -0,0 +1,48 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 + ********************************************************************************/ + +export const PropertyDataService = Symbol('PropertyDataService'); +/** + * `PropertyDataService` should be implemented to provide property data for the given selection. + */ +export interface PropertyDataService { + + /** + * A unique id for this provider. + */ + readonly id: string; + /** + * A human-readable name for this provider. + */ + readonly label?: string; + + /** + * Test whether this provider can provide property data for the given selection. + * Return a nonzero number if this provider can provide; otherwise it cannot. + * Never reject. + * + * A returned value indicating a priority of this provider. + */ + canHandleSelection(selection: Object | undefined): number; + + /** + * Provide property data for the given selection. + * Resolve to a property view content widget. + * Never reject if `canHandle` return a positive number; otherwise should reject. + */ + providePropertyData(selection: Object | undefined): Promise; + +} diff --git a/packages/property-view/src/browser/property-view-content-widget.ts b/packages/property-view/src/browser/property-view-content-widget.ts new file mode 100644 index 0000000000000..33cdfe490573d --- /dev/null +++ b/packages/property-view/src/browser/property-view-content-widget.ts @@ -0,0 +1,22 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { Widget } from '@theia/core/lib/browser/widgets/widget'; +import { PropertyDataService } from './property-data-service'; + +export interface PropertyViewContentWidget extends Widget { + updatePropertyViewContent(propertyDataService?: PropertyDataService, selection?: Object): void; +} diff --git a/packages/property-view/src/browser/property-view-contribution.ts b/packages/property-view/src/browser/property-view-contribution.ts new file mode 100644 index 0000000000000..a4c802fc095cd --- /dev/null +++ b/packages/property-view/src/browser/property-view-contribution.ts @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 } from '@theia/core/lib/browser/shell/view-contribution'; +import { injectable } from 'inversify'; +import { PropertyViewWidget } from './property-view-widget'; + +@injectable() +export class PropertyViewContribution extends AbstractViewContribution { + + constructor() { + super({ + widgetId: PropertyViewWidget.ID, + widgetName: PropertyViewWidget.LABEL, + defaultWidgetOptions: { + area: 'bottom' + }, + toggleCommandId: 'property-view:toggle', + toggleKeybinding: 'shift+alt+p' + }); + } + +} diff --git a/packages/property-view/src/browser/property-view-frontend-module.ts b/packages/property-view/src/browser/property-view-frontend-module.ts new file mode 100644 index 0000000000000..5912e5d850ab4 --- /dev/null +++ b/packages/property-view/src/browser/property-view-frontend-module.ts @@ -0,0 +1,47 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { bindViewContribution, WidgetFactory } from '@theia/core/lib/browser'; +import { bindContributionProvider } from '@theia/core/lib/common/contribution-provider'; +import { ContainerModule } from 'inversify'; +import { EmptyPropertyViewWidgetProvider } from './empty-property-view-widget-provider'; +import { PropertyDataService } from './property-data-service'; +import { PropertyViewContribution } from './property-view-contribution'; +import { PropertyViewService } from './property-view-service'; +import { PropertyViewWidget } from './property-view-widget'; +import { PropertyViewWidgetProvider } from './property-view-widget-provider'; +import { bindResourcePropertyView } from './resource-property-view'; +import '../../src/browser/style/property-view.css'; + +export default new ContainerModule(bind => { + bind(PropertyViewService).toSelf().inSingletonScope(); + + bindContributionProvider(bind, PropertyDataService); + bindContributionProvider(bind, PropertyViewWidgetProvider); + + bind(EmptyPropertyViewWidgetProvider).toSelf().inSingletonScope(); + bind(PropertyViewWidgetProvider).to(EmptyPropertyViewWidgetProvider); + + bind(PropertyViewWidget).toSelf(); + bind(WidgetFactory).toDynamicValue(({ container }) => ({ + id: PropertyViewWidget.ID, + createWidget: () => container.get(PropertyViewWidget) + })).inSingletonScope(); + + bindViewContribution(bind, PropertyViewContribution); + + bindResourcePropertyView(bind); +}); diff --git a/packages/property-view/src/browser/property-view-service.ts b/packages/property-view/src/browser/property-view-service.ts new file mode 100644 index 0000000000000..6097f1e39a917 --- /dev/null +++ b/packages/property-view/src/browser/property-view-service.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { ContributionProvider, Prioritizeable } from '@theia/core'; +import { inject, injectable, named, postConstruct } from 'inversify'; +import { EmptyPropertyViewWidgetProvider } from './empty-property-view-widget-provider'; +import { PropertyViewWidgetProvider } from './property-view-widget-provider'; + +/** + * `PropertyViewService` provides an access to existing property view widget providers. + */ +@injectable() +export class PropertyViewService { + + @inject(ContributionProvider) @named(PropertyViewWidgetProvider) + private readonly contributions: ContributionProvider; + + @inject(EmptyPropertyViewWidgetProvider) + private readonly emptyWidgetProvider: EmptyPropertyViewWidgetProvider; + + private providers: PropertyViewWidgetProvider[] = []; + + @postConstruct() + init(): void { + this.providers = this.providers.concat(this.contributions.getContributions()); + } + + /** + * Return a property view widget provider with the highest priority for the given selection. + * Never reject, return DefaultProvider ('No properties available') if no other matches. + */ + async getProvider(selection: Object | undefined): Promise { + const provider = await this.prioritize(selection); + return provider ?? this.emptyWidgetProvider; + } + + protected async prioritize(selection: Object | undefined): Promise { + const prioritized = await Prioritizeable.prioritizeAll(this.providers, async (provider: PropertyViewWidgetProvider) => { + try { + return await provider.canHandle(selection); + } catch { + return 0; + } + }); + return prioritized.length !== 0 ? prioritized[0].value : undefined; + } + +} diff --git a/packages/property-view/src/browser/property-view-widget-provider.ts b/packages/property-view/src/browser/property-view-widget-provider.ts new file mode 100644 index 0000000000000..50fec1d7e4114 --- /dev/null +++ b/packages/property-view/src/browser/property-view-widget-provider.ts @@ -0,0 +1,107 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { ContributionProvider, MaybePromise, Prioritizeable } from '@theia/core'; +import { inject, injectable, named, postConstruct } from 'inversify'; +import { PropertyDataService } from './property-data-service'; +import { PropertyViewContentWidget } from './property-view-content-widget'; + +export const PropertyViewWidgetProvider = Symbol('PropertyViewWidgetProvider'); +export interface PropertyViewWidgetProvider { + /** + * A unique id for this provider. + */ + id: string; + /** + * A human-readable name for this provider. + */ + label?: string; + + /** + * Test whether this provider can provide a widget for the given selection. + * A returned value indicating a priority of this provider. + * + * @param selection the global selection object + * @returns a nonzero number if this provider can provide; otherwise it cannot; never reject + */ + canHandle(selection: Object | undefined): MaybePromise; + + /** + * Provide a widget for the given selection. + * Never reject if `canHandle` return a positive number; otherwise should reject. + * + * @param selection the global selection object + * @returns a resolved property view content widget. + */ + provideWidget(selection: Object | undefined): Promise; + + /** + * Update the widget with the given selection. + * Never reject if `canHandle` return a positive number; otherwise should reject. + * + * @param selection the global selection object + * @returns a resolved property view content widget. + */ + updateContentWidget(selection: Object | undefined): void; + +} +/** + * `DefaultPropertyViewWidgetProvider` should be extended to provide a new content property view widget for the given selection. + */ +@injectable() +export abstract class DefaultPropertyViewWidgetProvider implements PropertyViewWidgetProvider { + + @inject(ContributionProvider) @named(PropertyDataService) + protected readonly contributions: ContributionProvider; + + protected propertyDataServices: PropertyDataService[] = []; + + id = 'default'; + label = 'DefaultPropertyViewWidgetProvider'; + + @postConstruct() + init(): void { + this.propertyDataServices = this.propertyDataServices.concat(this.contributions.getContributions()); + } + + canHandle(selection: Object | undefined): MaybePromise { + return 0; + } + + provideWidget(selection: Object | undefined): Promise { + throw new Error('not implemented'); + } + + updateContentWidget(selection: Object | undefined): void { + // no-op + } + + protected async getPropertyDataService(selection: Object | undefined): Promise { + const dataService = await this.prioritize(selection); + return dataService ?? this.propertyDataServices[0]; + } + + protected async prioritize(selection: Object | undefined): Promise { + const prioritized = await Prioritizeable.prioritizeAll(this.propertyDataServices, async (service: PropertyDataService) => { + try { + return service.canHandleSelection(selection); + } catch { + return 0; + } + }); + return prioritized.length !== 0 ? prioritized[0].value : undefined; + } +} diff --git a/packages/property-view/src/browser/property-view-widget.tsx b/packages/property-view/src/browser/property-view-widget.tsx new file mode 100644 index 0000000000000..3606fa6087242 --- /dev/null +++ b/packages/property-view/src/browser/property-view-widget.tsx @@ -0,0 +1,112 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { Message } from '@phosphor/messaging/lib'; +import { Disposable, SelectionService } from '@theia/core'; +import { BaseWidget, MessageLoop, Widget } from '@theia/core/lib/browser/widgets/widget'; +import { DisposableCollection } from '@theia/core/lib/common/disposable'; +import { inject, injectable, postConstruct } from 'inversify'; +import { PropertyViewContentWidget } from './property-view-content-widget'; +import { PropertyViewService } from './property-view-service'; + +@injectable() +export class PropertyViewWidget extends BaseWidget { + + static readonly ID = 'property-view'; + static readonly LABEL = 'Properties'; + + protected contentWidget: PropertyViewContentWidget; + + protected toDisposeOnDetach = new DisposableCollection(); + + @inject(PropertyViewService) protected readonly propertyViewService: PropertyViewService; + @inject(SelectionService) protected readonly selectionService: SelectionService; + + @postConstruct() + init(): void { + this.id = PropertyViewWidget.ID; + this.title.label = PropertyViewWidget.LABEL; + this.title.caption = PropertyViewWidget.LABEL; + this.title.iconClass = 'fa fa-table'; + this.title.closable = true; + + this.addClass('theia-property-view-widget'); + this.node.tabIndex = 0; + + let disposed = false; + this.toDispose.push(Disposable.create(() => disposed = true)); + this.toDispose.push(this.selectionService.onSelectionChanged((selection: Object | undefined) => { + this.propertyViewService.getProvider(selection).then(provider => { + provider.provideWidget(selection).then(contentWidget => { + if (!disposed) { + this.replaceContentWidget(contentWidget); + provider.updateContentWidget(selection); + } + }); + }); + })); + } + + protected initializeContentWidget(selection: Object | undefined): void { + this.propertyViewService.getProvider(selection).then(provider => { + provider.provideWidget(selection).then(contentWidget => { + this.attachContentWidget(contentWidget); + provider.updateContentWidget(selection); + }); + }); + } + + protected replaceContentWidget(newContentWidget: PropertyViewContentWidget): void { + if (this.contentWidget.id !== newContentWidget.id) { + if (this.contentWidget) { + Widget.detach(this.contentWidget); + } + this.attachContentWidget(newContentWidget); + } + } + + protected attachContentWidget(newContentWidget: PropertyViewContentWidget): void { + this.contentWidget = newContentWidget; + Widget.attach(this.contentWidget, this.node); + this.toDisposeOnDetach = new DisposableCollection(); + this.toDisposeOnDetach.push(Disposable.create(() => { + if (this.contentWidget) { + Widget.detach(this.contentWidget); + } + })); + this.update(); + } + + protected onAfterAttach(msg: Message): void { + super.onAfterAttach(msg); + this.initializeContentWidget(this.selectionService.selection); + } + + protected onActivateRequest(msg: Message): void { + super.onActivateRequest(msg); + this.node.focus(); + if (this.contentWidget) { + this.contentWidget.activate(); + } + } + + protected onResize(msg: Widget.ResizeMessage): void { + super.onResize(msg); + if (this.contentWidget) { + MessageLoop.sendMessage(this.contentWidget, msg); + } + } +} diff --git a/packages/property-view/src/browser/resource-property-view/index.ts b/packages/property-view/src/browser/resource-property-view/index.ts new file mode 100644 index 0000000000000..a7709e66f8fc2 --- /dev/null +++ b/packages/property-view/src/browser/resource-property-view/index.ts @@ -0,0 +1,17 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 + ********************************************************************************/ + +export * from './resource-property-view-tree-container'; diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-data-service.ts b/packages/property-view/src/browser/resource-property-view/resource-property-data-service.ts new file mode 100644 index 0000000000000..b91759b2a85cf --- /dev/null +++ b/packages/property-view/src/browser/resource-property-view/resource-property-data-service.ts @@ -0,0 +1,61 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { Navigatable } from '@theia/core/lib/browser'; +import URI from '@theia/core/lib/common/uri'; +import { FileSelection } from '@theia/filesystem/lib/browser/file-selection'; +import { FileService } from '@theia/filesystem/lib/browser/file-service'; +import { FileStat } from '@theia/filesystem/lib/common/files'; +import { inject, injectable } from 'inversify'; +import { PropertyDataService } from '../property-data-service'; + +@injectable() +export class ResourcePropertyDataService implements PropertyDataService { + + readonly id = 'resources'; + readonly label = 'ResourcePropertyDataService'; + + @inject(FileService) protected readonly fileService: FileService; + + canHandleSelection(selection: Object | undefined): number { + return (this.isFileSelection(selection) || this.isNavigatableSelection(selection)) ? 1 : 0; + } + + protected isFileSelection(selection: Object | undefined): boolean { + return !!selection && Array.isArray(selection) && FileSelection.is(selection[0]); + } + + protected isNavigatableSelection(selection: Object | undefined): boolean { + return !!selection && Navigatable.is(selection); + } + + protected async getFileStat(uri: URI): Promise { + return this.fileService.resolve(uri); + } + + async providePropertyData(selection: Object | undefined): Promise { + if (this.isFileSelection(selection) && Array.isArray(selection)) { + return this.getFileStat(selection[0].fileStat.resource); + } else if (this.isNavigatableSelection(selection)) { + const navigatableUri = (selection as Navigatable).getResourceUri(); + if (navigatableUri) { + return this.getFileStat(navigatableUri); + } + } + return undefined; + } + +} diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-label-provider.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-label-provider.ts new file mode 100644 index 0000000000000..12e7c6d01dae6 --- /dev/null +++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-label-provider.ts @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { LabelProvider, LabelProviderContribution, TreeNode } from '@theia/core/lib/browser'; +import { inject, injectable } from 'inversify'; +import { ResourcePropertiesCategoryNode, ResourcePropertiesItemNode } from './resource-property-view-tree-items'; + +const DEFAULT_INFO_ICON = 'fa fa-info-circle'; + +@injectable() +export class ResourcePropertiesLabelProvider implements LabelProviderContribution { + + @inject(LabelProvider) protected readonly labelProvider: LabelProvider; + + canHandle(element: TreeNode): number { + return (ResourcePropertiesCategoryNode.is(element) || ResourcePropertiesItemNode.is(element)) ? 75 : 0; + } + + getIcon(node: ResourcePropertiesCategoryNode | ResourcePropertiesItemNode): string { + if (ResourcePropertiesCategoryNode.is(node)) { + return node.icon ?? DEFAULT_INFO_ICON; + } + return node.icon ?? ''; + } + + getName(node: ResourcePropertiesCategoryNode | ResourcePropertiesItemNode): string { + return node.name; + } + + getLongName(node: ResourcePropertiesCategoryNode | ResourcePropertiesItemNode): string { + if (ResourcePropertiesItemNode.is(node)) { + return node.property; + } + return this.getName(node); + } +} diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-container.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-container.ts new file mode 100644 index 0000000000000..b44c498ab0756 --- /dev/null +++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-container.ts @@ -0,0 +1,46 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { createTreeContainer, LabelProviderContribution, TreeProps, TreeWidget } from '@theia/core/lib/browser'; +import { interfaces } from 'inversify'; +import { PropertyDataService } from '../property-data-service'; +import { PropertyViewWidgetProvider } from '../property-view-widget-provider'; +import { ResourcePropertyDataService } from './resource-property-data-service'; +import { ResourcePropertiesLabelProvider } from './resource-property-view-label-provider'; +import { ResourcePropertyViewTreeWidget } from './resource-property-view-tree-widget'; +import { ResourcePropertyViewWidgetProvider } from './resource-property-view-widget-provider'; + +const RESOURCE_PROPERTY_VIEW_TREE_PROPS = { + multiSelect: true, + search: true, +} as TreeProps; + +function createResourcePropertyViewTreeWidget(parent: interfaces.Container): ResourcePropertyViewTreeWidget { + const child = createTreeContainer(parent, RESOURCE_PROPERTY_VIEW_TREE_PROPS); + child.unbind(TreeWidget); + child.bind(ResourcePropertyViewTreeWidget).toSelf().inSingletonScope(); + return child.get(ResourcePropertyViewTreeWidget); +} + +export function bindResourcePropertyView(bind: interfaces.Bind): void { + bind(LabelProviderContribution).to(ResourcePropertiesLabelProvider).inSingletonScope(); + bind(PropertyDataService).to(ResourcePropertyDataService).inSingletonScope(); + bind(PropertyViewWidgetProvider).to(ResourcePropertyViewWidgetProvider).inSingletonScope(); + + bind(ResourcePropertyViewTreeWidget).toDynamicValue(ctx => + createResourcePropertyViewTreeWidget(ctx.container) + ); +} diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-items.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-items.ts new file mode 100644 index 0000000000000..ca3d81b41aed9 --- /dev/null +++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-items.ts @@ -0,0 +1,53 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { CompositeTreeNode, ExpandableTreeNode, SelectableTreeNode, TreeNode } from '@theia/core/lib/browser'; + +export const ROOT_ID = 'ResourcePropertiesTree'; + +export interface ResourcePropertiesRoot extends CompositeTreeNode { + children: ResourcePropertiesCategoryNode[]; +} +export namespace ResourcePropertiesRoot { + export function is(node: Object | undefined): node is ResourcePropertiesRoot { + return CompositeTreeNode.is(node) && node.id === ROOT_ID; + } +} + +export interface ResourcePropertiesCategoryNode extends ExpandableTreeNode, SelectableTreeNode { + name: string; + icon?: string; + children: ResourcePropertiesItemNode[]; + parent: ResourcePropertiesRoot; + categoryId: string; +} +export namespace ResourcePropertiesCategoryNode { + export function is(node: TreeNode | undefined): node is ResourcePropertiesCategoryNode { + return ExpandableTreeNode.is(node) && SelectableTreeNode.is(node) && 'categoryId' in node; + } +} + +export interface ResourcePropertiesItemNode extends SelectableTreeNode { + name: string; + icon?: string; + parent: ResourcePropertiesCategoryNode; + property: string; +} +export namespace ResourcePropertiesItemNode { + export function is(node: TreeNode | undefined): node is ResourcePropertiesItemNode { + return !!node && SelectableTreeNode.is(node) && 'property' in node; + } +} diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-widget.tsx b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-widget.tsx new file mode 100644 index 0000000000000..899dc666130bf --- /dev/null +++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-tree-widget.tsx @@ -0,0 +1,212 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { + ContextMenuRenderer, + LabelProvider, + NodeProps, + TreeModel, + TreeNode, + TreeProps, + TreeWidget +} from '@theia/core/lib/browser'; +import { FileStat } from '@theia/filesystem/lib/common/files'; +import { inject, injectable, postConstruct } from 'inversify'; +import * as React from 'react'; +import { PropertyDataService } from '../property-data-service'; +import { PropertyViewContentWidget } from '../property-view-content-widget'; +import { + ResourcePropertiesCategoryNode, + ResourcePropertiesItemNode, + ResourcePropertiesRoot, + ROOT_ID +} from './resource-property-view-tree-items'; + +@injectable() +export class ResourcePropertyViewTreeWidget extends TreeWidget implements PropertyViewContentWidget { + + static readonly ID = 'resource-properties-tree-widget'; + static readonly LABEL = 'Resource Properties Tree'; + + protected propertiesTree: Map; + protected currentSelection: Object | undefined; + + @inject(LabelProvider) protected readonly labelProvider: LabelProvider; + + constructor( + @inject(TreeProps) readonly props: TreeProps, + @inject(TreeModel) model: TreeModel, + @inject(ContextMenuRenderer) protected readonly contextMenuRenderer: ContextMenuRenderer + ) { + super(props, model, contextMenuRenderer); + + model.root = { + id: ROOT_ID, + name: ResourcePropertyViewTreeWidget.LABEL, + parent: undefined, + visible: false, + children: [] + } as ResourcePropertiesRoot; + + this.propertiesTree = new Map(); + } + + @postConstruct() + protected init(): void { + super.init(); + + this.id = ResourcePropertyViewTreeWidget.ID + '-treeContainer'; + this.addClass('treeContainer'); + + this.fillPropertiesTree(); + } + + protected updateNeeded(selection: Object | undefined): boolean { + return this.currentSelection !== selection; + } + + updatePropertyViewContent(propertyDataService?: PropertyDataService, selection?: Object | undefined): void { + if (this.updateNeeded(selection)) { + this.currentSelection = selection; + if (propertyDataService) { + propertyDataService.providePropertyData(selection).then((fileStatObject?: FileStat) => { + this.fillPropertiesTree(fileStatObject); + }); + } + } + } + + protected fillPropertiesTree(fileStatObject?: FileStat): void { + if (fileStatObject) { + this.propertiesTree.clear(); + const infoNode = this.createCategoryNode('info', 'Info'); + this.propertiesTree.set('info', infoNode); + + infoNode.children.push(this.createResultLineNode('isDirectory', 'Directory', fileStatObject.isDirectory, infoNode)); + infoNode.children.push(this.createResultLineNode('isFile', 'File', fileStatObject.isFile, infoNode)); + infoNode.children.push(this.createResultLineNode('isSymbolicLink', 'Symbolic link', fileStatObject.isSymbolicLink, infoNode)); + infoNode.children.push(this.createResultLineNode('location', 'Location', this.getLocationString(fileStatObject), infoNode)); + infoNode.children.push(this.createResultLineNode('name', 'Name', this.getFileName(fileStatObject), infoNode)); + infoNode.children.push(this.createResultLineNode('path', 'Path', this.getFilePath(fileStatObject), infoNode)); + infoNode.children.push(this.createResultLineNode('lastModification', 'Last modified', this.getLastModificationString(fileStatObject), infoNode)); + infoNode.children.push(this.createResultLineNode('created', 'Created', this.getCreationTimeString(fileStatObject), infoNode)); + infoNode.children.push(this.createResultLineNode('size', 'Size', this.getSizeString(fileStatObject), infoNode)); + this.refreshModelChildren(); + } + } + + protected getLocationString(fileStat: FileStat): string { + return fileStat.resource.path.toString(); + } + + protected getFileName(fileStat: FileStat): string { + return this.labelProvider.getName(fileStat.resource); + } + + protected getFilePath(fileStat: FileStat): string { + return this.labelProvider.getLongName(fileStat.resource); + } + + protected getLastModificationString(fileStat: FileStat): string { + return fileStat.mtime ? new Date(fileStat.mtime).toLocaleString() : ''; + } + + protected getCreationTimeString(fileStat: FileStat): string { + return fileStat.ctime ? new Date(fileStat.ctime).toLocaleString() : ''; + } + + protected getSizeString(fileStat: FileStat): string { + return fileStat.size ? fileStat.size + ' bytes' : ''; + } + + /* + * Creating TreeNodes + */ + + protected createCategoryNode(categoryId: string, name: string): ResourcePropertiesCategoryNode { + return { + id: categoryId, + parent: this.model.root as ResourcePropertiesRoot, + name, + children: [], + categoryId, + selected: false, + expanded: true + }; + } + + protected createResultLineNode(id: string, name: string, property: boolean | string | undefined, parent: ResourcePropertiesCategoryNode): ResourcePropertiesItemNode { + return { + id: `${parent.id}::${id}`, + parent, + name: name, + property: property !== undefined ? String(property) : '', + selected: false + }; + } + + /** + * Rendering + */ + + protected async refreshModelChildren(): Promise { + if (ResourcePropertiesRoot.is(this.model.root)) { + this.model.root.children = Array.from(this.propertiesTree.values()); + this.model.refresh(); + } + } + + protected renderCaption(node: TreeNode, props: NodeProps): React.ReactNode { + if (ResourcePropertiesCategoryNode.is(node)) { + return this.renderExpandableNode(node); + } else if (ResourcePropertiesItemNode.is(node)) { + return this.renderItemNode(node); + } + return undefined; + } + + protected renderExpandableNode(node: ResourcePropertiesCategoryNode): React.ReactNode { + return +
+
{this.toNodeName(node)}
+
; + } + + protected renderItemNode(node: ResourcePropertiesItemNode): React.ReactNode { + return +
+
{this.toNodeName(node)}
+
{this.toNodeDescription(node)}
+
; + } + + protected createNodeAttributes(node: TreeNode, props: NodeProps): React.Attributes & React.HTMLAttributes { + return { + ...super.createNodeAttributes(node, props), + title: this.getNodeTooltip(node) + }; + } + + protected getNodeTooltip(node: TreeNode): string | undefined { + if (ResourcePropertiesCategoryNode.is(node)) { + return this.labelProvider.getName(node); + } else if (ResourcePropertiesItemNode.is(node)) { + return `${this.labelProvider.getName(node)}: ${this.labelProvider.getLongName(node)}`; + } + return undefined; + } + +} diff --git a/packages/property-view/src/browser/resource-property-view/resource-property-view-widget-provider.ts b/packages/property-view/src/browser/resource-property-view/resource-property-view-widget-provider.ts new file mode 100644 index 0000000000000..bfe6b2117959b --- /dev/null +++ b/packages/property-view/src/browser/resource-property-view/resource-property-view-widget-provider.ts @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 { Navigatable } from '@theia/core/lib/browser'; +import { FileSelection } from '@theia/filesystem/lib/browser/file-selection'; +import { inject, injectable } from 'inversify'; +import { DefaultPropertyViewWidgetProvider } from '../property-view-widget-provider'; +import { ResourcePropertyViewTreeWidget } from './resource-property-view-tree-widget'; + +@injectable() +export class ResourcePropertyViewWidgetProvider extends DefaultPropertyViewWidgetProvider { + + @inject(ResourcePropertyViewTreeWidget) protected treeWidget: ResourcePropertyViewTreeWidget; + + readonly id = 'resources'; + readonly label = 'ResourcePropertyViewWidgetProvider'; + + canHandle(selection: Object | undefined): number { + return (this.isFileSelection(selection) || this.isNavigatableSelection(selection)) ? 1 : 0; + } + + protected isFileSelection(selection: Object | undefined): boolean { + return !!selection && Array.isArray(selection) && FileSelection.is(selection[0]); + } + + protected isNavigatableSelection(selection: Object | undefined): boolean { + return !!selection && Navigatable.is(selection); + } + + provideWidget(selection: Object | undefined): Promise { + return Promise.resolve(this.treeWidget); + } + + updateContentWidget(selection: Object | undefined): void { + this.getPropertyDataService(selection).then(service => this.treeWidget.updatePropertyViewContent(service, selection)); + } + +} diff --git a/packages/property-view/src/browser/style/property-view.css b/packages/property-view/src/browser/style/property-view.css new file mode 100644 index 0000000000000..9c007d83fc22e --- /dev/null +++ b/packages/property-view/src/browser/style/property-view.css @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 + ********************************************************************************/ + +:root { + --theia-property-view-widget-padding: 5px; + --theia-empty-property-view-widget-padding: 8px; + --theia-resource-tree-node-icon-margin: 0 3px; + --theia-resource-tree-node-icon-flex-basis: 1.5%; + --theia-resource-tree-node-name-flex-basis: 30%; + --theia-resource-tree-node-property-flex-basis: 70%; +} + +.theia-property-view-widget { + padding: var(--theia-border-width); +} + +#theia-empty-property-view .theia-widget-noInfo { + padding: var(--theia-empty-property-view-widget-padding); +} + +.theia-property-view-widget .treeContainer { + height: 100%; +} + +.theia-resource-tree-node-icon { + margin: var(--theia-resource-tree-node-icon-margin); + flex-basis: var(--theia-resource-tree-node-icon-flex-basis); + align-self: center; + text-align: center; +} + +.theia-resource-tree-node-name { + flex-basis: var(--theia-resource-tree-node-name-flex-basis); +} + +.theia-resource-tree-node-property { + flex-basis: var(--theia-resource-tree-node-property-flex-basis); +} diff --git a/packages/property-view/src/package.spec.ts b/packages/property-view/src/package.spec.ts new file mode 100644 index 0000000000000..6b99fce8fae0f --- /dev/null +++ b/packages/property-view/src/package.spec.ts @@ -0,0 +1,29 @@ +/******************************************************************************** + * Copyright (C) 2020 EclipseSource 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 + ********************************************************************************/ + +/* note: this bogus test file is required so that + we are able to run mocha unit tests on this + package, without having any actual unit tests in it. + This way a coverage report will be generated, + showing 0% coverage, instead of no report. + This file can be removed once we have real unit + tests in place. */ + +describe('property-view package', () => { + + it('support code coverage statistics', () => true); + +}); diff --git a/tsconfig.json b/tsconfig.json index 734ba59947ac6..a3b9decb887d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -171,6 +171,9 @@ ], "@theia/timeline/*": [ "packages/timeline/*" + ], + "@theia/property-view/lib/*": [ + "packages/property-view/src/*" ] } }