extensionsViewer.ts 8.9 KB
Newer Older
S
Sandeep Somavarapu 已提交
1 2 3 4 5 6
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as dom from 'vs/base/browser/dom';
7
import { localize } from 'vs/nls';
8
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
9 10
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
11
import { Action } from 'vs/base/common/actions';
12
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions';
J
Joao Moreno 已提交
13
import { Event } from 'vs/base/common/event';
S
Sandeep Somavarapu 已提交
14
import { domEvent } from 'vs/base/browser/event';
15
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
16
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
17
import { WorkbenchTreeController, WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
18
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
19 20
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IThemeService } from 'vs/platform/theme/common/themeService';
21 22

export interface IExtensionTemplateData {
S
Sandeep Somavarapu 已提交
23 24 25 26 27
	icon: HTMLImageElement;
	name: HTMLElement;
	identifier: HTMLElement;
	author: HTMLElement;
	extensionDisposables: IDisposable[];
28
	extensionData: IExtensionData;
S
Sandeep Somavarapu 已提交
29 30
}

31 32 33 34
export interface IUnknownExtensionTemplateData {
	identifier: HTMLElement;
}

35 36 37
export interface IExtensionData {
	extension: IExtension;
	hasChildren: boolean;
38 39
	getChildren: () => Promise<IExtensionData[] | null>;
	parent: IExtensionData | null;
40 41
}

S
Sandeep Somavarapu 已提交
42 43
export class DataSource implements IDataSource {

44
	public getId(tree: ITree, { extension, parent }: IExtensionData): string {
S
Sandeep Somavarapu 已提交
45
		return parent ? this.getId(tree, parent) + '/' + extension.identifier.id : extension.identifier.id;
S
Sandeep Somavarapu 已提交
46 47
	}

48 49
	public hasChildren(tree: ITree, { hasChildren }: IExtensionData): boolean {
		return hasChildren;
S
Sandeep Somavarapu 已提交
50 51
	}

52
	public getChildren(tree: ITree, extensionData: IExtensionData): Promise<any> {
53
		return extensionData.getChildren();
S
Sandeep Somavarapu 已提交
54 55
	}

56 57
	public getParent(tree: ITree, { parent }: IExtensionData): Promise<any> {
		return Promise.resolve(parent);
S
Sandeep Somavarapu 已提交
58
	}
S
Sandeep Somavarapu 已提交
59 60 61 62
}

export class Renderer implements IRenderer {

63 64
	private static readonly EXTENSION_TEMPLATE_ID = 'extension-template';
	private static readonly UNKNOWN_EXTENSION_TEMPLATE_ID = 'unknown-extension-template';
S
Sandeep Somavarapu 已提交
65

66
	constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
S
Sandeep Somavarapu 已提交
67 68
	}

69
	public getHeight(tree: ITree, element: IExtensionData): number {
S
Sandeep Somavarapu 已提交
70 71 72
		return 62;
	}

73 74
	public getTemplateId(tree: ITree, { extension }: IExtensionData): string {
		return extension ? Renderer.EXTENSION_TEMPLATE_ID : Renderer.UNKNOWN_EXTENSION_TEMPLATE_ID;
75 76 77 78 79 80 81
	}

	public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
		if (Renderer.EXTENSION_TEMPLATE_ID === templateId) {
			return this.renderExtensionTemplate(tree, container);
		}
		return this.renderUnknownExtensionTemplate(tree, container);
S
Sandeep Somavarapu 已提交
82 83
	}

84
	private renderExtensionTemplate(tree: ITree, container: HTMLElement): IExtensionTemplateData {
85
		dom.addClass(container, 'extension');
S
Sandeep Somavarapu 已提交
86 87 88 89 90 91 92 93

		const icon = dom.append(container, dom.$<HTMLImageElement>('img.icon'));
		const details = dom.append(container, dom.$('.details'));

		const header = dom.append(details, dom.$('.header'));
		const name = dom.append(header, dom.$('span.name'));
		const openExtensionAction = this.instantiationService.createInstance(OpenExtensionAction);
		const extensionDisposables = [dom.addDisposableListener(name, 'click', (e: MouseEvent) => {
94 95
			tree.setFocus(openExtensionAction.extensionData);
			tree.setSelection([openExtensionAction.extensionData]);
S
Sandeep Somavarapu 已提交
96 97 98 99
			openExtensionAction.run(e.ctrlKey || e.metaKey);
			e.stopPropagation();
			e.preventDefault();
		})];
100
		const identifier = dom.append(header, dom.$('span.identifier'));
S
Sandeep Somavarapu 已提交
101 102

		const footer = dom.append(details, dom.$('.footer'));
103
		const author = dom.append(footer, dom.$('.author'));
S
Sandeep Somavarapu 已提交
104 105 106 107 108 109
		return {
			icon,
			name,
			identifier,
			author,
			extensionDisposables,
110 111
			set extensionData(extensionData: IExtensionData) {
				openExtensionAction.extensionData = extensionData;
S
Sandeep Somavarapu 已提交
112 113 114 115
			}
		};
	}

116
	private renderUnknownExtensionTemplate(tree: ITree, container: HTMLElement): IUnknownExtensionTemplateData {
117
		const messageContainer = dom.append(container, dom.$('div.unknown-extension'));
118
		dom.append(messageContainer, dom.$('span.error-marker')).textContent = localize('error', "Error");
119
		dom.append(messageContainer, dom.$('span.message')).textContent = localize('Unknown Extension', "Unknown Extension:");
120 121 122 123

		const identifier = dom.append(messageContainer, dom.$('span.message'));
		return { identifier };
	}
S
Sandeep Somavarapu 已提交
124

125
	public renderElement(tree: ITree, element: IExtensionData, templateId: string, templateData: any): void {
126 127 128 129 130 131 132
		if (templateId === Renderer.EXTENSION_TEMPLATE_ID) {
			this.renderExtension(tree, element, templateData);
			return;
		}
		this.renderUnknownExtension(tree, element, templateData);
	}

133 134
	private renderExtension(tree: ITree, extensionData: IExtensionData, data: IExtensionTemplateData): void {
		const extension = extensionData.extension;
J
Joao Moreno 已提交
135
		const onError = Event.once(domEvent(data.icon, 'error'));
S
Sandeep Somavarapu 已提交
136 137 138 139 140 141 142 143 144 145 146
		onError(() => data.icon.src = extension.iconUrlFallback, null, data.extensionDisposables);
		data.icon.src = extension.iconUrl;

		if (!data.icon.complete) {
			data.icon.style.visibility = 'hidden';
			data.icon.onload = () => data.icon.style.visibility = 'inherit';
		} else {
			data.icon.style.visibility = 'inherit';
		}

		data.name.textContent = extension.displayName;
S
Sandeep Somavarapu 已提交
147
		data.identifier.textContent = extension.identifier.id;
S
Sandeep Somavarapu 已提交
148
		data.author.textContent = extension.publisherDisplayName;
149
		data.extensionData = extensionData;
S
Sandeep Somavarapu 已提交
150 151
	}

152
	private renderUnknownExtension(tree: ITree, { extension }: IExtensionData, data: IUnknownExtensionTemplateData): void {
S
Sandeep Somavarapu 已提交
153
		data.identifier.textContent = extension.identifier.id;
154 155 156 157 158 159
	}

	public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
		if (templateId === Renderer.EXTENSION_TEMPLATE_ID) {
			templateData.extensionDisposables = dispose((<IExtensionTemplateData>templateData).extensionDisposables);
		}
S
Sandeep Somavarapu 已提交
160
	}
161 162
}

163
export class Controller extends WorkbenchTreeController {
164

165
	constructor(
166
		@IExtensionsWorkbenchService private readonly extensionsWorkdbenchService: IExtensionsWorkbenchService,
167 168
		@IConfigurationService configurationService: IConfigurationService
	) {
169
		super({}, configurationService);
170 171

		// TODO@Sandeep this should be a command
172
		this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, (tree: ITree, event: any) => this.openExtension(tree, true));
S
Sandeep Somavarapu 已提交
173 174
	}

175
	protected onLeftClick(tree: ITree, element: IExtensionData, event: IMouseEvent): boolean {
176
		let currentFocused = tree.getFocus();
S
Sandeep Somavarapu 已提交
177
		if (super.onLeftClick(tree, element, event)) {
178
			if (element.parent === null) {
179 180
				if (currentFocused) {
					tree.setFocus(currentFocused);
S
Sandeep Somavarapu 已提交
181 182 183 184 185 186 187 188 189
				} else {
					tree.focusFirst();
				}
				return true;
			}
		}
		return false;
	}

190
	public openExtension(tree: ITree, sideByside: boolean): boolean {
191
		const element: IExtensionData = tree.getFocus();
192 193 194 195 196
		if (element.extension) {
			this.extensionsWorkdbenchService.open(element.extension, sideByside);
			return true;
		}
		return false;
197
	}
198 199 200 201
}

class OpenExtensionAction extends Action {

S
Sandeep Somavarapu 已提交
202
	private _extensionData: IExtensionData;
203

204
	constructor(@IExtensionsWorkbenchService private readonly extensionsWorkdbenchService: IExtensionsWorkbenchService) {
205
		super('extensions.action.openExtension', '');
S
Sandeep Somavarapu 已提交
206
	}
207

208
	public set extensionData(extension: IExtensionData) {
S
Sandeep Somavarapu 已提交
209
		this._extensionData = extension;
S
Sandeep Somavarapu 已提交
210
	}
211

212
	public get extensionData(): IExtensionData {
S
Sandeep Somavarapu 已提交
213
		return this._extensionData;
S
Sandeep Somavarapu 已提交
214
	}
215

216
	run(sideByside: boolean): Promise<any> {
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
		return this.extensionsWorkdbenchService.open(this.extensionData.extension, sideByside);
	}
}

export class ExtensionsTree extends WorkbenchTree {

	constructor(
		input: IExtensionData,
		container: HTMLElement,
		@IContextKeyService contextKeyService: IContextKeyService,
		@IListService listService: IListService,
		@IThemeService themeService: IThemeService,
		@IInstantiationService instantiationService: IInstantiationService,
		@IConfigurationService configurationService: IConfigurationService
	) {
		const renderer = instantiationService.createInstance(Renderer);
		const controller = instantiationService.createInstance(Controller);

		super(
			container,
			{
				dataSource: new DataSource(),
				renderer,
				controller
			}, {
				indentPixels: 40,
				twistiePixels: 20
			},
			contextKeyService, listService, themeService, instantiationService, configurationService
		);

		this.setInput(input);

		this.disposables.push(this.onDidChangeSelection(event => {
			if (event && event.payload && event.payload.origin === 'keyboard') {
				controller.openExtension(this, false);
			}
		}));
S
Sandeep Somavarapu 已提交
255
	}
S
Sandeep Somavarapu 已提交
256
}