Skip to content

Commit 29e59cd

Browse files
committed
Show recommended label in view and reason in editor #36649
1 parent c1e079b commit 29e59cd

File tree

7 files changed

+94
-19
lines changed

7 files changed

+94
-19
lines changed

src/vs/platform/extensionManagement/common/extensionManagement.ts

+1
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,7 @@ export const IExtensionTipsService = createDecorator<IExtensionTipsService>('ext
305305

306306
export interface IExtensionTipsService {
307307
_serviceBrand: any;
308+
getAllRecommendationsWithReason(): { [id: string]: string; };
308309
getFileBasedRecommendations(): string[];
309310
getOtherRecommendations(): string[];
310311
getWorkspaceRecommendations(): TPromise<string[]>;

src/vs/workbench/parts/extensions/browser/extensionEditor.ts

+17-5
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
2525
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
2626
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
2727
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
28-
import { IExtensionGalleryService, IExtensionManifest, IKeyBinding, IView } from 'vs/platform/extensionManagement/common/extensionManagement';
28+
import { IExtensionGalleryService, IExtensionManifest, IKeyBinding, IView, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
2929
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
3030
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
3131
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies } from 'vs/workbench/parts/extensions/common/extensions';
@@ -163,7 +163,8 @@ export class ExtensionEditor extends BaseEditor {
163163
private extensionActionBar: ActionBar;
164164
private navbar: NavBar;
165165
private content: HTMLElement;
166-
166+
private recommendation: HTMLElement;
167+
private header: HTMLElement;
167168
private _highlight: ITemplateData;
168169
private highlightDisposable: IDisposable;
169170

@@ -195,6 +196,7 @@ export class ExtensionEditor extends BaseEditor {
195196
@IPartService private partService: IPartService,
196197
@IContextViewService private contextViewService: IContextViewService,
197198
@IContextKeyService private contextKeyService: IContextKeyService,
199+
@IExtensionTipsService private extensionTipsService: IExtensionTipsService
198200
) {
199201
super(ExtensionEditor.ID, telemetryService, themeService);
200202
this._highlight = null;
@@ -212,11 +214,11 @@ export class ExtensionEditor extends BaseEditor {
212214
const container = parent.getHTMLElement();
213215

214216
const root = append(container, $('.extension-editor'));
215-
const header = append(root, $('.header'));
217+
this.header = append(root, $('.header'));
216218

217-
this.icon = append(header, $<HTMLImageElement>('img.icon', { draggable: false }));
219+
this.icon = append(this.header, $<HTMLImageElement>('img.icon', { draggable: false }));
218220

219-
const details = append(header, $('.details'));
221+
const details = append(this.header, $('.details'));
220222
const title = append(details, $('.title'));
221223
this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
222224
this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
@@ -249,6 +251,8 @@ export class ExtensionEditor extends BaseEditor {
249251
});
250252
this.disposables.push(this.extensionActionBar);
251253

254+
this.recommendation = append(details, $('.recommendation'));
255+
252256
chain(fromEventEmitter<{ error?: any; }>(this.extensionActionBar, 'run'))
253257
.map(({ error }) => error)
254258
.filter(error => !!error)
@@ -289,6 +293,14 @@ export class ExtensionEditor extends BaseEditor {
289293
this.publisher.textContent = extension.publisherDisplayName;
290294
this.description.textContent = extension.description;
291295

296+
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
297+
this.recommendation.textContent = extRecommendations[extension.id.toLowerCase()];
298+
if (extRecommendations[extension.id.toLowerCase()]) {
299+
addClass(this.header, 'recommended');
300+
} else {
301+
removeClass(this.header, 'recommended');
302+
}
303+
292304
if (extension.url) {
293305
this.name.onclick = finalHandler(() => window.open(extension.url));
294306
this.rating.onclick = finalHandler(() => window.open(`${extension.url}#review-details`));

src/vs/workbench/parts/extensions/browser/extensionsList.ts

+25-9
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ import { domEvent } from 'vs/base/browser/event';
1818
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
1919
import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
2020
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
21-
import { Label, RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
21+
import { RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
2222
import { EventType } from 'vs/base/common/events';
2323
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
2424
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
25+
import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
2526

2627
export interface ITemplateData {
2728
root: HTMLElement;
@@ -32,6 +33,7 @@ export interface ITemplateData {
3233
ratings: HTMLElement;
3334
author: HTMLElement;
3435
description: HTMLElement;
36+
subText: HTMLElement;
3537
extension: IExtension;
3638
disposables: IDisposable[];
3739
extensionDisposables: IDisposable[];
@@ -46,13 +48,18 @@ const actionOptions = { icon: true, label: true };
4648

4749
export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
4850

51+
private showRecommendedLabel: boolean;
4952
constructor(
53+
showRecommendedLabel: boolean,
5054
@IInstantiationService private instantiationService: IInstantiationService,
5155
@IContextMenuService private contextMenuService: IContextMenuService,
5256
@IMessageService private messageService: IMessageService,
5357
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
54-
@IExtensionService private extensionService: IExtensionService
55-
) { }
58+
@IExtensionService private extensionService: IExtensionService,
59+
@IExtensionTipsService private extensionTipsService: IExtensionTipsService
60+
) {
61+
this.showRecommendedLabel = showRecommendedLabel;
62+
}
5663

5764
get templateId() { return 'extension'; }
5865

@@ -63,7 +70,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
6370
const headerContainer = append(details, $('.header-container'));
6471
const header = append(headerContainer, $('.header'));
6572
const name = append(header, $('span.name'));
66-
const version = append(header, $('span.version'));
73+
const subText = append(header, $('span.version'));
6774
const installCount = append(header, $('span.install-count'));
6875
const ratings = append(header, $('span.ratings'));
6976
const description = append(details, $('.description.ellipsis'));
@@ -80,7 +87,6 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
8087
});
8188
actionbar.addListener(EventType.RUN, ({ error }) => error && this.messageService.show(Severity.Error, error));
8289

83-
const versionWidget = this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version);
8490
const installCountWidget = this.instantiationService.createInstance(InstallWidget, installCount, { small: true });
8591
const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, ratings, { small: true });
8692

@@ -91,13 +97,12 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
9197
const manageAction = this.instantiationService.createInstance(ManageExtensionAction);
9298

9399
actionbar.push([reloadAction, updateAction, installAction, builtinStatusAction, manageAction], actionOptions);
94-
const disposables = [versionWidget, installCountWidget, ratingsWidget, builtinStatusAction, updateAction, reloadAction, manageAction, actionbar];
100+
const disposables = [installCountWidget, ratingsWidget, builtinStatusAction, updateAction, reloadAction, manageAction, actionbar];
95101

96102
return {
97-
root, element, icon, name, installCount, ratings, author, description, disposables,
103+
root, element, icon, name, installCount, ratings, author, description, subText, disposables,
98104
extensionDisposables: [],
99105
set extension(extension: IExtension) {
100-
versionWidget.extension = extension;
101106
installCountWidget.extension = extension;
102107
ratingsWidget.extension = extension;
103108
builtinStatusAction.extension = extension;
@@ -127,10 +132,11 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
127132
removeClass(data.element, 'loading');
128133

129134
data.extensionDisposables = dispose(data.extensionDisposables);
135+
const isInstalled = this.extensionsWorkbenchService.local.some(e => e.id === extension.id);
130136

131137
this.extensionService.getExtensions().then(enabledExtensions => {
132138
const isExtensionRunning = enabledExtensions.some(e => areSameExtensions(e, extension));
133-
const isInstalled = this.extensionsWorkbenchService.local.some(e => e.id === extension.id);
139+
134140
toggleClass(data.element, 'disabled', isInstalled && !isExtensionRunning);
135141
});
136142

@@ -145,7 +151,17 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
145151
data.icon.style.visibility = 'inherit';
146152
}
147153

154+
data.subText.textContent = isInstalled ? extension.version : '';
148155
data.root.setAttribute('aria-label', extension.displayName);
156+
157+
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
158+
if (extRecommendations[extension.id.toLowerCase()] && !isInstalled) {
159+
data.root.setAttribute('aria-label', extension.displayName + '. ' + extRecommendations[extension.id]);
160+
if (this.showRecommendedLabel) {
161+
data.subText.textContent = 'Recommended';
162+
}
163+
}
164+
149165
data.name.textContent = extension.displayName;
150166
data.author.textContent = extension.publisherDisplayName;
151167
data.description.textContent = extension.description;

src/vs/workbench/parts/extensions/browser/media/extensionEditor.css

+17-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
font-size: 14px;
2222
}
2323

24+
.extension-editor > .header.recommended {
25+
height: 140px;
26+
}
27+
2428
.extension-editor > .header > .icon {
2529
height: 128px;
2630
width: 128px;
@@ -59,7 +63,7 @@
5963
}
6064

6165
.extension-editor > .header > .details > .subtitle {
62-
padding-top: 10px;
66+
padding-top: 6px;
6367
white-space: nowrap;
6468
height: 20px;
6569
line-height: 20px;
@@ -81,14 +85,14 @@
8185
}
8286

8387
.extension-editor > .header > .details > .description {
84-
margin-top: 14px;
88+
margin-top: 10px;
8589
white-space: nowrap;
8690
text-overflow: ellipsis;
8791
overflow: hidden;
8892
}
8993

9094
.extension-editor > .header > .details > .actions {
91-
margin-top: 14px;
95+
margin-top: 10px;
9296
}
9397

9498
.extension-editor > .header > .details > .actions > .monaco-action-bar {
@@ -104,6 +108,16 @@
104108
padding: 1px 6px;
105109
}
106110

111+
.extension-editor > .header.recommended > .details > .recommendation {
112+
display: none;
113+
}
114+
115+
.extension-editor > .header.recommended > .details > .recommendation {
116+
display: block;
117+
margin-top: 10px;
118+
font-size: 13px;
119+
}
120+
107121
.extension-editor > .body {
108122
height: calc(100% - 168px);
109123
overflow: hidden;

src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts

+7
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
7979
this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e)));
8080
}
8181

82+
getAllRecommendationsWithReason(): { [id: string]: string; } {
83+
let output: { [id: string]: string; } = Object.create(null);
84+
this.getFileBasedRecommendations().forEach(x => output[x.toLowerCase()] = localize('fileBasedRecommendation', "Based on your recent file history, we recommend this extension."));
85+
this._allWorkspaceRecommendedExtensions.forEach(x => output[x.toLowerCase()] = localize('workspaceRecommendation', "Your team recommends this extension."));
86+
return output;
87+
}
88+
8289
getWorkspaceRecommendations(): TPromise<string[]> {
8390
const workspace = this.contextService.getWorkspace();
8491
return TPromise.join([this.resolveWorkspaceRecommendations(workspace), ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderRecommendations(workspaceFolder))])

src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
122122
viewDescriptors.push(this.createInstalledExtensionsListViewDescriptor());
123123
viewDescriptors.push(this.createSearchInstalledExtensionsListViewDescriptor());
124124
viewDescriptors.push(this.createRecommendedExtensionsListViewDescriptor());
125+
viewDescriptors.push(this.createSearchRecommendedExtensionsListViewDescriptor());
125126
ViewsRegistry.registerViews(viewDescriptors);
126127
}
127128

@@ -131,7 +132,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
131132
name: localize('marketPlace', "Marketplace"),
132133
location: ViewLocation.Extensions,
133134
ctor: ExtensionsListView,
134-
when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions')),
135+
when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions'), ContextKeyExpr.not('searchRecommendedExtensions')),
135136
size: 100
136137
};
137138
}
@@ -170,6 +171,18 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
170171
};
171172
}
172173

174+
private createSearchRecommendedExtensionsListViewDescriptor(): IViewDescriptor {
175+
return {
176+
id: 'extensions.searchrecommendedList',
177+
name: localize('recommendedExtensions', "Recommended"),
178+
location: ViewLocation.Extensions,
179+
ctor: RecommendedExtensionsView,
180+
when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.has('searchRecommendedExtensions')),
181+
size: 50,
182+
canToggleVisibility: true
183+
};
184+
}
185+
173186
async create(parent: Builder): TPromise<void> {
174187
parent.addClass('extensions-viewlet');
175188
this.root = parent.getHTMLElement();

src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,15 @@ export class ExtensionsListView extends ViewsViewletPanel {
7979
this.disposables.push(attachBadgeStyler(this.badge, this.themeService));
8080
}
8181

82+
showRecommendedLabel() {
83+
return true;
84+
}
85+
8286
renderBody(container: HTMLElement): void {
8387
this.extensionsList = append(container, $('.extensions-list'));
8488
this.messageBox = append(container, $('.message'));
8589
const delegate = new Delegate();
86-
const renderer = this.instantiationService.createInstance(Renderer);
90+
const renderer = this.instantiationService.createInstance(Renderer, this.showRecommendedLabel());
8791
this.list = new PagedList(this.extensionsList, delegate, [renderer], {
8892
ariaLabel: localize('extensions', "Extensions"),
8993
keyboardSupport: false
@@ -534,6 +538,10 @@ export class InstalledExtensionsView extends ExtensionsListView {
534538
return super.show(searchInstalledQuery);
535539
}
536540

541+
showRecommendedLabel() {
542+
return false;
543+
}
544+
537545
}
538546

539547
export class RecommendedExtensionsView extends ExtensionsListView {
@@ -552,4 +560,8 @@ export class RecommendedExtensionsView extends ExtensionsListView {
552560
return super.show(searchInstalledQuery);
553561
}
554562

563+
showRecommendedLabel() {
564+
return false;
565+
}
566+
555567
}

0 commit comments

Comments
 (0)