extensionEditor.ts 10.6 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Joao Moreno 已提交
8
import 'vs/css!./media/extensionEditor';
J
Joao Moreno 已提交
9
import { localize } from 'vs/nls';
J
Joao Moreno 已提交
10 11
import { TPromise } from 'vs/base/common/winjs.base';
import { marked } from 'vs/base/common/marked/marked';
J
Joao Moreno 已提交
12 13
import Event, { Emitter } from 'vs/base/common/event';
import { Action } from 'vs/base/common/actions';
14
import { onUnexpectedError } from 'vs/base/common/errors';
J
Joao Moreno 已提交
15
import { IDisposable, empty, dispose, toDisposable } from 'vs/base/common/lifecycle';
J
Joao Moreno 已提交
16
import { Builder } from 'vs/base/browser/builder';
J
Joao Moreno 已提交
17
import { append, emmet as $, addClass, removeClass, finalHandler } from 'vs/base/browser/dom';
J
Joao Moreno 已提交
18
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
19
import { IViewlet } from 'vs/workbench/common/viewlet';
J
Joao Moreno 已提交
20
import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService';
J
Joao Moreno 已提交
21 22
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
Joao Moreno 已提交
23
import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
J
Joao Moreno 已提交
24
import { IThemeService } from 'vs/workbench/services/themes/common/themeService';
25
import { ExtensionsInput } from './extensionsInput';
J
Joao Moreno 已提交
26
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension } from './extensions';
27
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
J
Joao Moreno 已提交
28
import { ITemplateData } from './extensionsList';
29
import { RatingsWidget, InstallWidget } from './extensionsWidgets';
J
Joao Moreno 已提交
30
import { EditorOptions } from 'vs/workbench/common/editor';
J
Joao Moreno 已提交
31 32
import { shell } from 'electron';
import product from 'vs/platform/product';
J
Joao Moreno 已提交
33
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
34
import { CombinedInstallAction, UpdateAction, EnableAction } from './extensionsActions';
J
Joao Moreno 已提交
35
import WebView from 'vs/workbench/parts/html/browser/webview';
36
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
J
Joao Moreno 已提交
37 38 39 40 41 42 43 44 45 46 47

function renderBody(body: string): string {
	return `<!DOCTYPE html>
		<html>
			<head>
				<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
				<link rel="stylesheet" type="text/css" href="${ require.toUrl('./media/markdown.css') }" >
			</head>
			<body>${ body }</body>
		</html>`;
}
J
Joao Moreno 已提交
48

J
Joao Moreno 已提交
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
class NavBar {

	private _onChange = new Emitter<string>();
	get onChange(): Event<string> { return this._onChange.event; }

	private actions: Action[];
	private actionbar: ActionBar;

	constructor(container: HTMLElement) {
		const element = append(container, $('.navbar'));
		this.actions = [];
		this.actionbar = new ActionBar(element, { animated: false });
	}

	push(id: string, label: string): void {
		const run = () => {
			this._onChange.fire(id);
			this.actions.forEach(a => a.enabled = a.id !== action.id);
			return TPromise.as(null);
		};

		const action = new Action(id, label, null, true, run);

		this.actions.push(action);
		this.actionbar.push(action);

		if (this.actions.length === 1) {
			run();
		}
	}

	clear(): void {
		this.actions = dispose(this.actions);
		this.actionbar.clear();
	}

	dispose(): void {
		this.actionbar = dispose(this.actionbar);
	}
}

const NavbarSection = {
	Readme: 'readme',
	Configuration: 'configuration'
};

J
Joao Moreno 已提交
95 96 97 98
export class ExtensionEditor extends BaseEditor {

	static ID: string = 'workbench.editor.extension';

J
Joao Moreno 已提交
99
	private icon: HTMLElement;
J
Joao Moreno 已提交
100
	private name: HTMLAnchorElement;
J
Joao Moreno 已提交
101
	private license: HTMLAnchorElement;
J
Joao Moreno 已提交
102
	private publisher: HTMLAnchorElement;
J
Joao Moreno 已提交
103
	private installCount: HTMLElement;
J
Joao Moreno 已提交
104
	private rating: HTMLAnchorElement;
J
Joao Moreno 已提交
105
	private description: HTMLElement;
J
Joao Moreno 已提交
106 107 108
	private extensionActionBar: ActionBar;
	private navbar: NavBar;
	private content: HTMLElement;
J
Joao Moreno 已提交
109 110 111 112

	private _highlight: ITemplateData;
	private highlightDisposable: IDisposable;

J
Joao Moreno 已提交
113 114
	private contentDisposables: IDisposable[] = [];
	private transientDisposables: IDisposable[] = [];
J
Joao Moreno 已提交
115
	private disposables: IDisposable[];
J
Joao Moreno 已提交
116 117 118

	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
119 120
		@IExtensionGalleryService private galleryService: IExtensionGalleryService,
		@IConfigurationService private configurationService: IConfigurationService,
J
Joao Moreno 已提交
121
		@IInstantiationService private instantiationService: IInstantiationService,
122
		@IViewletService private viewletService: IViewletService,
J
Joao Moreno 已提交
123
		@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
124 125
		@IThemeService private themeService: IThemeService,
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService
J
Joao Moreno 已提交
126 127 128 129
	) {
		super(ExtensionEditor.ID, telemetryService);
		this._highlight = null;
		this.highlightDisposable = empty;
J
Joao Moreno 已提交
130
		this.disposables = [];
131

132
		this.disposables.push(viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables));
J
Joao Moreno 已提交
133 134 135 136
	}

	createEditor(parent: Builder): void {
		const container = parent.getHTMLElement();
J
Joao Moreno 已提交
137 138 139

		const root = append(container, $('.extension-editor'));
		const header = append(root, $('.header'));
J
Joao Moreno 已提交
140 141 142

		this.icon = append(header, $('.icon'));

J
Joao Moreno 已提交
143
		const details = append(header, $('.details'));
J
Joao Moreno 已提交
144 145
		const title = append(details, $('.title'));
		this.name = append(title, $<HTMLAnchorElement>('a.name'));
J
Joao Moreno 已提交
146
		this.name.href = '#';
J
Joao Moreno 已提交
147 148

		const subtitle = append(details, $('.subtitle'));
J
Joao Moreno 已提交
149 150
		this.publisher = append(subtitle, $<HTMLAnchorElement>('a.publisher'));
		this.publisher.href = '#';
J
Joao Moreno 已提交
151

152
		this.installCount = append(subtitle, $('span.install'));
J
Joao Moreno 已提交
153

J
Joao Moreno 已提交
154 155
		this.rating = append(subtitle, $<HTMLAnchorElement>('a.rating'));
		this.rating.href = '#';
J
Joao Moreno 已提交
156

J
Joao Moreno 已提交
157 158 159
		this.license = append(subtitle, $<HTMLAnchorElement>('a.license'));
		this.license.href = '#';
		this.license.textContent = localize('license', 'License');
160
		this.license.style.display = 'none';
J
Joao Moreno 已提交
161

J
Joao Moreno 已提交
162 163
		this.description = append(details, $('.description'));

J
Joao Moreno 已提交
164 165 166 167 168 169
		const extensionActions = append(details, $('.actions'));
		this.extensionActionBar = new ActionBar(extensionActions, { animated: false });
		this.disposables.push(this.extensionActionBar);

		const body = append(root, $('.body'));
		this.navbar = new NavBar(body);
J
Joao Moreno 已提交
170

J
Joao Moreno 已提交
171
		this.content = append(body, $('.content'));
J
Joao Moreno 已提交
172 173 174
	}

	setInput(input: ExtensionsInput, options: EditorOptions): TPromise<void> {
J
Joao Moreno 已提交
175 176
		this.transientDisposables = dispose(this.transientDisposables);

J
Joao Moreno 已提交
177
		const extension = input.extension;
178
		this.telemetryService.publicLog('extensionGallery:openExtension', extension.telemetryData);
J
Joao Moreno 已提交
179

J
Joao Moreno 已提交
180
		this.icon.style.backgroundImage = `url("${ extension.iconUrl }")`;
J
Joao Moreno 已提交
181 182 183
		this.name.textContent = extension.displayName;
		this.publisher.textContent = extension.publisherDisplayName;
		this.description.textContent = extension.description;
J
Joao Moreno 已提交
184

J
Joao Moreno 已提交
185 186 187
		if (product.extensionsGallery) {
			const extensionUrl = `${ product.extensionsGallery.itemUrl }?itemName=${ extension.publisher }.${ extension.name }`;

J
Joao Moreno 已提交
188 189 190
			this.name.onclick = finalHandler(() => shell.openExternal(extensionUrl));
			this.rating.onclick = finalHandler(() => shell.openExternal(`${ extensionUrl }#review-details`));
			this.publisher.onclick = finalHandler(() => {
191
				this.viewletService.openViewlet(VIEWLET_ID, true)
J
Joao Moreno 已提交
192 193 194
					.then(viewlet => viewlet as IExtensionsViewlet)
					.done(viewlet => viewlet.search(`publisher:"${ extension.publisherDisplayName }"`, true));
			});
195 196 197 198 199 200 201 202

			if (extension.licenseUrl) {
				this.license.onclick = finalHandler(() => shell.openExternal(extension.licenseUrl));
				this.license.style.display = 'initial';
			} else {
				this.license.onclick = null;
				this.license.style.display = 'none';
			}
J
Joao Moreno 已提交
203 204
		}

J
Joao Moreno 已提交
205
		const install = this.instantiationService.createInstance(InstallWidget, this.installCount, { extension });
J
Joao Moreno 已提交
206 207
		this.transientDisposables.push(install);

J
Joao Moreno 已提交
208
		const ratings = this.instantiationService.createInstance(RatingsWidget, this.rating, { extension });
J
Joao Moreno 已提交
209
		this.transientDisposables.push(ratings);
J
Joao Moreno 已提交
210

J
Joao Moreno 已提交
211 212 213 214 215 216 217 218
		const installAction = this.instantiationService.createInstance(CombinedInstallAction);
		const updateAction = this.instantiationService.createInstance(UpdateAction);
		const enableAction = this.instantiationService.createInstance(EnableAction);

		installAction.extension = extension;
		updateAction.extension = extension;
		enableAction.extension = extension;

J
Joao Moreno 已提交
219 220
		this.extensionActionBar.clear();
		this.extensionActionBar.push([enableAction, updateAction, installAction], { icon: true, label: true });
221
		this.transientDisposables.push(enableAction, updateAction, installAction);
J
Joao Moreno 已提交
222

J
Joao Moreno 已提交
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
		this.navbar.clear();
		this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);

		this.navbar.push(NavbarSection.Readme, localize('readme', "Readme"));
		// this.navbar.push(NavbarSection.Configuration, localize('configuration', "Config"));

		this.content.innerHTML = '';

		return super.setInput(input, options);
	}

	private onNavbarChange(extension: IExtension, id: string): void {
		this.contentDisposables = dispose(this.contentDisposables);
		this.content.innerHTML = '';

		switch (id) {
			case NavbarSection.Readme: return this.openReadme(extension);
			case NavbarSection.Configuration: return this.openConfiguration(extension);
J
Joao Moreno 已提交
241
		}
J
Joao Moreno 已提交
242
	}
J
Joao Moreno 已提交
243

J
Joao Moreno 已提交
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
	private openReadme(extension: IExtension) {
		if (!extension.readmeUrl) {
			const p = append(this.content, $('p'));
			p.textContent = localize('noReadme', "No README available.");
			return;
		}

		addClass(this.content, 'loading');

		const promise = this.extensionsWorkbenchService.getReadmeContents(extension)
			.then(marked.parse)
			.then<void>(body => {
				const webview = new WebView(
					this.content,
					document.querySelector('.monaco-editor-background')
				);

				webview.style(this.themeService.getColorTheme());
				webview.contents = [renderBody(body)];

				const linkListener = webview.onDidClickLink(link => shell.openExternal(link.toString()));
				const themeListener = this.themeService.onDidColorThemeChange(themeId => webview.style(themeId));
				this.contentDisposables.push(webview, linkListener, themeListener);
			})
			.then(null, () => null)
			.then(() => removeClass(this.content, 'loading'));

		this.contentDisposables.push(toDisposable(() => promise.cancel()));
	}
J
Joao Moreno 已提交
273

J
Joao Moreno 已提交
274 275 276
	private openConfiguration(extension: IExtension) {
		this.content.innerHTML = '';
		this.content.innerText = 'configuration';
J
Joao Moreno 已提交
277 278 279 280 281 282
	}

	layout(): void {
		return;
	}

283
	private onViewletOpen(viewlet: IViewlet): void {
284 285 286 287 288 289 290
		if (!viewlet || viewlet.getId() === VIEWLET_ID) {
			return;
		}

		this.editorService.closeEditor(this.position, this.input).done(null, onUnexpectedError);
	}

J
Joao Moreno 已提交
291 292
	dispose(): void {
		this._highlight = null;
J
Joao Moreno 已提交
293 294
		this.transientDisposables = dispose(this.transientDisposables);
		this.disposables = dispose(this.disposables);
J
Joao Moreno 已提交
295 296 297
		super.dispose();
	}
}