From 0acc996121cfa26813a65817fedf2fd2cca7483d Mon Sep 17 00:00:00 2001 From: Alex Ross Date: Fri, 17 Jul 2020 09:47:33 +0200 Subject: [PATCH] Add support for view initial state (collapsed or hidden) (#102002) Fixes #101283 --- extensions/npm/package.json | 5 ++- .../api/browser/viewsExtensionPoint.ts | 38 ++++++++++++++++++- .../browser/parts/views/viewPaneContainer.ts | 2 + src/vs/workbench/browser/viewlet.ts | 2 +- 4 files changed, 42 insertions(+), 5 deletions(-) diff --git a/extensions/npm/package.json b/extensions/npm/package.json index 0e8b70bb256..85ef490df23 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -56,8 +56,9 @@ { "id": "npm", "name": "%view.name%", - "when": "npm:showScriptExplorer || config.npm.enableScriptExplorer", - "icon": "images/code.svg" + "when": "npm:showScriptExplorer", + "icon": "images/code.svg", + "visibility": "hidden" } ] }, diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index a9aadf93a9d..0762abe8c4d 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -83,12 +83,19 @@ interface IUserFriendlyViewDescriptor { icon?: string; contextualTitle?: string; + visibility?: string; // From 'remoteViewDescriptor' type group?: string; remoteName?: string | string[]; } +enum InitialVisibility { + Visible = 'visible', + Hidden = 'hidden', + Collapsed = 'collapsed' +} + const viewDescriptor: IJSONSchema = { type: 'object', properties: { @@ -112,6 +119,20 @@ const viewDescriptor: IJSONSchema = { description: localize('vscode.extension.contributes.view.contextualTitle', "Human-readable context for when the view is moved out of its original location. By default, the view's container name will be used. Will be shown"), type: 'string' }, + visibility: { + description: localize('vscode.extension.contributes.view.initialState', "Initial state of the view when the extension is first installed. Once the user has changed the view state by collapsing, moving, or hiding the view, the initial state will not be used again."), + type: 'string', + enum: [ + 'visible', + 'hidden', + 'collapsed' + ], + enumDescriptions: [ + localize('vscode.extension.contributes.view.initialState.visible', "The default initial state for view. The view will be expanded. This may have different behavior when the view container that the view is in is built in."), + localize('vscode.extension.contributes.view.initialState.hidden', "The view will not be shown in the view container, but will be discoverable through the views menu and other view entry points and can be un-hidden by the user."), + localize('vscode.extension.contributes.view.initialState.collapsed', "The view will show in the view container, but will be collapsed.") + ] + } } }; @@ -419,6 +440,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { : undefined; const icon = item.icon ? resources.joinPath(extension.description.extensionLocation, item.icon) : undefined; + const initialVisibility = this.convertInitialVisibility(item.visibility); const viewDescriptor = { id: item.id, name: item.name, @@ -429,12 +451,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { canToggleVisibility: true, canMoveView: true, treeView: this.instantiationService.createInstance(CustomTreeView, item.id, item.name), - collapsed: this.showCollapsed(container), + collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed, order: order, extensionId: extension.description.identifier, originalContainerId: entry.key, group: item.group, - remoteAuthority: item.remoteName || (item).remoteAuthority // TODO@roblou - delete after remote extensions are updated + remoteAuthority: item.remoteName || (item).remoteAuthority, // TODO@roblou - delete after remote extensions are updated + hideByDefault: initialVisibility === InitialVisibility.Hidden }; viewIds.add(viewDescriptor.id); @@ -463,6 +486,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { } } + private convertInitialVisibility(value: any): InitialVisibility | undefined { + if (Object.values(InitialVisibility).includes(value)) { + return value; + } + return undefined; + } + private isValidViewDescriptors(viewDescriptors: IUserFriendlyViewDescriptor[], collector: ExtensionMessageCollector): boolean { if (!Array.isArray(viewDescriptors)) { collector.error(localize('requirearray', "views must be an array")); @@ -490,6 +520,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution { collector.error(localize('optstring', "property `{0}` can be omitted or must be of type `string`", 'contextualTitle')); return false; } + if (descriptor.visibility && !this.convertInitialVisibility(descriptor.visibility)) { + collector.error(localize('optenum', "property `{0}` can be omitted or must be one of {1}", 'visibility', Object.values(InitialVisibility).join(', '))); + return false; + } } return true; diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 4c94254d513..ac3577c8241 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -982,6 +982,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this.updateViewHeaders(); } }); + + this._register(this.viewContainerModel.onDidChangeActiveViewDescriptors(() => this._onTitleAreaUpdate.fire())); } getTitle(): string { diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index c1e56a1e4d3..c222e990bcb 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -45,7 +45,7 @@ export abstract class Viewlet extends PaneComposite implements IViewlet { @IConfigurationService protected configurationService: IConfigurationService ) { super(id, viewPaneContainer, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - this._register(Event.any(viewPaneContainer.onDidAddViews, viewPaneContainer.onDidRemoveViews)(() => { + this._register(Event.any(viewPaneContainer.onDidAddViews, viewPaneContainer.onDidRemoveViews, viewPaneContainer.onTitleAreaUpdate)(() => { // Update title area since there is no better way to update secondary actions this.updateTitleArea(); }));