/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/extension'; import { append, $, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { IAction } from 'vs/base/common/actions'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IExtension, ExtensionContainers, ExtensionState, IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionViewItem, StatusLabelAction, RemoteInstallAction, SystemDisabledWarningAction, ExtensionToolTipAction, LocalInstallAction, SyncIgnoredIconAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget, TooltipWidget, ExtensionPackCountWidget as ExtensionPackBadgeWidget } from 'vs/workbench/contrib/extensions/browser/extensionsWidgets'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; export interface IExtensionsViewState { onFocus: Event; onBlur: Event; } export interface ITemplateData { root: HTMLElement; element: HTMLElement; icon: HTMLImageElement; name: HTMLElement; installCount: HTMLElement; ratings: HTMLElement; author: HTMLElement; description: HTMLElement; extension: IExtension | null; disposables: IDisposable[]; extensionDisposables: IDisposable[]; actionbar: ActionBar; } export class Delegate implements IListVirtualDelegate { getHeight() { return 62; } getTemplateId() { return 'extension'; } } const actionOptions = { icon: true, label: true, tabOnlyOnFocus: true }; export class Renderer implements IPagedRenderer { constructor( private extensionViewState: IExtensionsViewState, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, @IExtensionService private readonly extensionService: IExtensionService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, ) { } get templateId() { return 'extension'; } renderTemplate(root: HTMLElement): ITemplateData { const recommendationWidget = this.instantiationService.createInstance(RecommendationWidget, append(root, $('.extension-bookmark-container'))); const element = append(root, $('.extension-list-item')); const iconContainer = append(element, $('.icon-container')); const icon = append(iconContainer, $('img.icon')); const iconRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, iconContainer, false); const extensionPackBadgeWidget = this.instantiationService.createInstance(ExtensionPackBadgeWidget, iconContainer); const details = append(element, $('.details')); const headerContainer = append(details, $('.header-container')); const header = append(headerContainer, $('.header')); const name = append(header, $('span.name')); const version = append(header, $('span.version')); const installCount = append(header, $('span.install-count')); const ratings = append(header, $('span.ratings')); const headerRemoteBadgeWidget = this.instantiationService.createInstance(RemoteBadgeWidget, header, false); const description = append(details, $('.description.ellipsis')); const footer = append(details, $('.footer')); const author = append(footer, $('.author.ellipsis')); const actionbar = new ActionBar(footer, { animated: false, actionViewItemProvider: (action: IAction) => { if (action.id === ManageExtensionAction.ID) { return (action).createActionViewItem(); } return new ExtensionActionViewItem(null, action, actionOptions); } }); actionbar.onDidRun(({ error }) => error && this.notificationService.error(error)); const systemDisabledWarningAction = this.instantiationService.createInstance(SystemDisabledWarningAction); const reloadAction = this.instantiationService.createInstance(ReloadAction); const actions = [ this.instantiationService.createInstance(StatusLabelAction), this.instantiationService.createInstance(SyncIgnoredIconAction), this.instantiationService.createInstance(UpdateAction), reloadAction, this.instantiationService.createInstance(InstallAction), this.instantiationService.createInstance(RemoteInstallAction, false), this.instantiationService.createInstance(LocalInstallAction), this.instantiationService.createInstance(MaliciousStatusLabelAction, false), systemDisabledWarningAction, this.instantiationService.createInstance(ManageExtensionAction) ]; const extensionTooltipAction = this.instantiationService.createInstance(ExtensionToolTipAction, systemDisabledWarningAction, reloadAction); const tooltipWidget = this.instantiationService.createInstance(TooltipWidget, root, extensionTooltipAction, recommendationWidget); const widgets = [ recommendationWidget, iconRemoteBadgeWidget, extensionPackBadgeWidget, headerRemoteBadgeWidget, tooltipWidget, this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version), this.instantiationService.createInstance(InstallCountWidget, installCount, true), this.instantiationService.createInstance(RatingsWidget, ratings, true) ]; const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets, extensionTooltipAction]); actionbar.push(actions, actionOptions); const disposables = combinedDisposable(...actions, ...widgets, actionbar, extensionContainers, extensionTooltipAction); return { root, element, icon, name, installCount, ratings, author, description, disposables: [disposables], actionbar, extensionDisposables: [], set extension(extension: IExtension) { extensionContainers.extension = extension; } }; } renderPlaceholder(index: number, data: ITemplateData): void { addClass(data.element, 'loading'); data.root.removeAttribute('aria-label'); data.extensionDisposables = dispose(data.extensionDisposables); data.icon.src = ''; data.name.textContent = ''; data.author.textContent = ''; data.description.textContent = ''; data.installCount.style.display = 'none'; data.ratings.style.display = 'none'; data.extension = null; } renderElement(extension: IExtension, index: number, data: ITemplateData): void { removeClass(data.element, 'loading'); if (extension.state !== ExtensionState.Uninstalled && !extension.server) { // Get the extension if it is installed and has no server information extension = this.extensionsWorkbenchService.local.filter(e => e.server === extension.server && areSameExtensions(e.identifier, extension.identifier))[0] || extension; } data.extensionDisposables = dispose(data.extensionDisposables); const updateEnablement = async () => { const runningExtensions = await this.extensionService.getExtensions(); if (extension.local && !isLanguagePackExtension(extension.local.manifest)) { const runningExtension = runningExtensions.filter(e => areSameExtensions({ id: e.identifier.value, uuid: e.uuid }, extension.identifier))[0]; const isSameExtensionRunning = runningExtension && extension.server === this.extensionManagementServerService.getExtensionManagementServer(runningExtension.extensionLocation); toggleClass(data.root, 'disabled', !isSameExtensionRunning); } else { removeClass(data.root, 'disabled'); } }; updateEnablement(); this.extensionService.onDidChangeExtensions(() => updateEnablement(), this, data.extensionDisposables); const onError = Event.once(domEvent(data.icon, 'error')); 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; data.author.textContent = extension.publisherDisplayName; data.description.textContent = extension.description; data.installCount.style.display = ''; data.ratings.style.display = ''; data.extension = extension; if (extension.gallery && extension.gallery.properties && extension.gallery.properties.localizedLanguages && extension.gallery.properties.localizedLanguages.length) { data.description.textContent = extension.gallery.properties.localizedLanguages.map(name => name[0].toLocaleUpperCase() + name.slice(1)).join(', '); } this.extensionViewState.onFocus(e => { if (areSameExtensions(extension.identifier, e.identifier)) { data.actionbar.viewItems.forEach(item => (item).setFocus(true)); } }, this, data.extensionDisposables); this.extensionViewState.onBlur(e => { if (areSameExtensions(extension.identifier, e.identifier)) { data.actionbar.viewItems.forEach(item => (item).setFocus(false)); } }, this, data.extensionDisposables); } disposeTemplate(data: ITemplateData): void { data.disposables = dispose(data.disposables); } }