@@ -32,7 +32,7 @@ import { TasksResource, TasksResourceTreeItem } from 'vs/workbench/services/user
3232import { ExtensionsResource , ExtensionsResourceExportTreeItem , ExtensionsResourceImportTreeItem , ExtensionsResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/extensionsResource' ;
3333import { GlobalStateResource , GlobalStateResourceExportTreeItem , GlobalStateResourceImportTreeItem , GlobalStateResourceTreeItem } from 'vs/workbench/services/userDataProfile/browser/globalStateResource' ;
3434import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider' ;
35- import { Button , ButtonWithDropdown } from 'vs/base/browser/ui/button/button' ;
35+ import { Button } from 'vs/base/browser/ui/button/button' ;
3636import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet' ;
3737import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding' ;
3838import { IContextMenuService } from 'vs/platform/contextview/browser/contextView' ;
@@ -44,7 +44,7 @@ import { defaultButtonStyles } from 'vs/platform/theme/browser/defaultStyles';
4444import { generateUuid } from 'vs/base/common/uuid' ;
4545import { IEditorService } from 'vs/workbench/services/editor/common/editorService' ;
4646import { EditorsOrder } from 'vs/workbench/common/editor' ;
47- import { getErrorMessage } from 'vs/base/common/errors' ;
47+ import { getErrorMessage , onUnexpectedError } from 'vs/base/common/errors' ;
4848import { IProgressService , ProgressLocation } from 'vs/platform/progress/common/progress' ;
4949import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions' ;
5050import { IQuickInputService , QuickPickItem } from 'vs/platform/quickinput/common/quickInput' ;
@@ -68,6 +68,8 @@ import { Barrier } from 'vs/base/common/async';
6868import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement' ;
6969import { ExtensionType } from 'vs/platform/extensions/common/extensions' ;
7070import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil' ;
71+ import { MarkdownString } from 'vs/base/common/htmlContent' ;
72+ import { renderMarkdown } from 'vs/base/browser/markdownRenderer' ;
7173
7274interface IUserDataProfileTemplate {
7375 readonly name : string ;
@@ -231,7 +233,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
231233 return this . doExportProfile ( userDataProfilesExportState ) ;
232234 } ) ) ;
233235 const closeAction = new BarrierAction ( barrier , new Action ( 'close' , localize ( 'close' , "Close" ) ) ) ;
234- await this . showProfilePreviewView ( EXPORT_PROFILE_PREVIEW_VIEW , userDataProfilesExportState . profile . name , [ exportAction ] , closeAction , true , userDataProfilesExportState ) ;
236+ await this . showProfilePreviewView ( EXPORT_PROFILE_PREVIEW_VIEW , userDataProfilesExportState . profile . name , exportAction , closeAction , true , userDataProfilesExportState ) ;
235237 disposables . add ( this . userDataProfileService . onDidChangeCurrentProfile ( e => barrier . open ( ) ) ) ;
236238 await barrier . wait ( ) ;
237239 await this . hideProfilePreviewView ( EXPORT_PROFILE_PREVIEW_VIEW ) ;
@@ -327,19 +329,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
327329 const userDataProfileImportState = disposables . add ( this . instantiationService . createInstance ( UserDataProfileImportState , profileTemplate ) ) ;
328330 profileTemplate = await userDataProfileImportState . getProfileTemplateToImport ( ) ;
329331
330- let extensions = false ;
331- if ( profileTemplate . extensions ) {
332- const result = await this . dialogService . confirm ( {
333- title : localize ( 'preview profile' , "Preview Profile" ) ,
334- message : localize ( 'apply extensions' , "Would you like to apply the extensions from the profile you are previewing or go through them manually?" ) ,
335- type : 'info' ,
336- primaryButton : localize ( 'apply extensions automatically' , "Apply Extensions" ) ,
337- secondaryButton : localize ( 'apply extensions manually' , "Apply Extensions (Manually)" ) ,
338- } ) ;
339- extensions = result . confirmed ;
340- }
341-
342- const importedProfile = await this . importAndSwitch ( profileTemplate , true , extensions , localize ( 'preview profile' , "Preview Profile" ) ) ;
332+ const importedProfile = await this . importAndSwitch ( profileTemplate , true , false , localize ( 'preview profile' , "Preview Profile" ) ) ;
343333
344334 if ( ! importedProfile ) {
345335 return ;
@@ -348,55 +338,53 @@ export class UserDataProfileImportExportService extends Disposable implements IU
348338 const barrier = new Barrier ( ) ;
349339 const importAction = this . getImportAction ( barrier , userDataProfileImportState ) ;
350340 const secondaryAction = isWeb
351- ? new Action ( 'importInDesktop' , localize ( 'import in desktop' , "Import {0} profile in {1}" , importedProfile . name , this . productService . nameLong ) , undefined , true , async ( ) => this . openerService . open ( uri , { openExternal : true } ) )
341+ ? new Action ( 'importInDesktop' , localize ( 'import in desktop' , "Import Profile in {1}" , importedProfile . name , this . productService . nameLong ) , undefined , true , async ( ) => this . openerService . open ( uri , { openExternal : true } ) )
352342 : new BarrierAction ( barrier , new Action ( 'close' , localize ( 'close' , "Close" ) ) ) ;
353343
354- const view = await this . showProfilePreviewView ( IMPORT_PROFILE_PREVIEW_VIEW , importedProfile . name , [ importAction ] , secondaryAction , false , userDataProfileImportState ) ;
355- if ( ! extensions ) {
356- userDataProfileImportState . setDescription ( ProfileResourceType . Extensions , localize ( 'not applied' , "Not Applied" ) ) ;
357- const that = this ;
358- const disposable = disposables . add ( registerAction2 ( class extends Action2 {
359- constructor ( ) {
360- super ( {
361- id : 'previewProfile.applyExtensions' ,
362- title : localize ( 'apply extensions title' , "Apply Extensions" ) ,
363- icon : Codicon . cloudDownload ,
364- menu : {
365- id : MenuId . ViewItemContext ,
366- group : 'inline' ,
367- when : ContextKeyExpr . and ( ContextKeyExpr . equals ( 'view' , IMPORT_PROFILE_PREVIEW_VIEW ) , ContextKeyExpr . equals ( 'viewItem' , ProfileResourceType . Extensions ) ) ,
368- }
369- } ) ;
370- }
371- override async run ( ) : Promise < void > {
372- return that . progressService . withProgress ( {
373- location : IMPORT_PROFILE_PREVIEW_VIEW ,
374- } , async progress => {
375- disposable . dispose ( ) ;
376- userDataProfileImportState . setDescription ( ProfileResourceType . Extensions , localize ( 'applying' , "Applying..." ) ) ;
377- view . refresh ( ) ;
378- const profileTemplate = await userDataProfileImportState . getProfileTemplateToImport ( ) ;
379- if ( profileTemplate . extensions ) {
380- await that . instantiationService . createInstance ( ExtensionsResource ) . apply ( profileTemplate . extensions , importedProfile ) ;
381- userDataProfileImportState . setDescription ( ProfileResourceType . Extensions , undefined ) ;
382- await view . refresh ( ) ;
383- }
384- } ) ;
385- }
386- } ) ) ;
387- disposables . add ( Event . debounce ( this . extensionManagementService . onDidInstallExtensions , ( ) => undefined , 100 ) ( async ( ) => {
388- const profileTemplate = await userDataProfileImportState . getProfileTemplateToImport ( ) ;
389- if ( profileTemplate . extensions ) {
390- const profileExtensions = await that . instantiationService . createInstance ( ExtensionsResource ) . getProfileExtensions ( profileTemplate . extensions ! ) ;
391- const installed = await this . extensionManagementService . getInstalled ( ExtensionType . User ) ;
392- if ( profileExtensions . every ( e => installed . some ( i => areSameExtensions ( e . identifier , i . identifier ) ) ) ) {
393- disposable . dispose ( ) ;
394- userDataProfileImportState . setDescription ( ProfileResourceType . Extensions , undefined ) ;
395- await view . refresh ( ) ;
344+ const view = await this . showProfilePreviewView ( IMPORT_PROFILE_PREVIEW_VIEW , importedProfile . name , importAction , secondaryAction , false , userDataProfileImportState ) ;
345+ const message = new MarkdownString ( ) ;
346+ message . appendMarkdown ( localize ( 'preview profile message' , "By default, extensions aren't installed when previewing a profile on the web. You can still install them manually before importing the profile. " ) ) ;
347+ message . appendMarkdown ( `[${ localize ( 'learn more' , "Learn more" ) } ](https://aka.ms/vscode-extension-marketplace#_can-i-trust-extensions-from-the-marketplace).` ) ;
348+ view . setMessage ( message ) ;
349+
350+ const that = this ;
351+ const disposable = disposables . add ( registerAction2 ( class extends Action2 {
352+ constructor ( ) {
353+ super ( {
354+ id : 'previewProfile.installExtensions' ,
355+ title : localize ( 'install extensions title' , "Install Extensions" ) ,
356+ icon : Codicon . cloudDownload ,
357+ menu : {
358+ id : MenuId . ViewItemContext ,
359+ group : 'inline' ,
360+ when : ContextKeyExpr . and ( ContextKeyExpr . equals ( 'view' , IMPORT_PROFILE_PREVIEW_VIEW ) , ContextKeyExpr . equals ( 'viewItem' , ProfileResourceType . Extensions ) ) ,
361+ }
362+ } ) ;
363+ }
364+ override async run ( ) : Promise < void > {
365+ return that . progressService . withProgress ( {
366+ location : IMPORT_PROFILE_PREVIEW_VIEW ,
367+ } , async progress => {
368+ disposable . dispose ( ) ;
369+ view . setMessage ( undefined ) ;
370+ const profileTemplate = await userDataProfileImportState . getProfileTemplateToImport ( ) ;
371+ if ( profileTemplate . extensions ) {
372+ await that . instantiationService . createInstance ( ExtensionsResource ) . apply ( profileTemplate . extensions , importedProfile ) ;
396373 }
374+ } ) ;
375+ }
376+ } ) ) ;
377+ disposables . add ( Event . debounce ( this . extensionManagementService . onDidInstallExtensions , ( ) => undefined , 100 ) ( async ( ) => {
378+ const profileTemplate = await userDataProfileImportState . getProfileTemplateToImport ( ) ;
379+ if ( profileTemplate . extensions ) {
380+ const profileExtensions = await that . instantiationService . createInstance ( ExtensionsResource ) . getProfileExtensions ( profileTemplate . extensions ! ) ;
381+ const installed = await this . extensionManagementService . getInstalled ( ExtensionType . User ) ;
382+ if ( profileExtensions . every ( e => installed . some ( i => areSameExtensions ( e . identifier , i . identifier ) ) ) ) {
383+ disposable . dispose ( ) ;
397384 }
398- } ) ) ;
399- }
385+ }
386+ } ) ) ;
387+
400388 await barrier . wait ( ) ;
401389 await this . hideProfilePreviewView ( IMPORT_PROFILE_PREVIEW_VIEW ) ;
402390 } finally {
@@ -414,7 +402,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
414402 if ( userDataProfileImportState . isEmpty ( ) ) {
415403 await importAction . run ( ) ;
416404 } else {
417- await this . showProfilePreviewView ( IMPORT_PROFILE_PREVIEW_VIEW , profileTemplate . name , [ importAction ] , new BarrierAction ( barrier , new Action ( 'cancel' , localize ( 'cancel' , "Cancel" ) ) ) , false , userDataProfileImportState ) ;
405+ await this . showProfilePreviewView ( IMPORT_PROFILE_PREVIEW_VIEW , profileTemplate . name , importAction , new BarrierAction ( barrier , new Action ( 'cancel' , localize ( 'cancel' , "Cancel" ) ) ) , false , userDataProfileImportState ) ;
418406 }
419407 await barrier . wait ( ) ;
420408 await this . hideProfilePreviewView ( IMPORT_PROFILE_PREVIEW_VIEW ) ;
@@ -424,7 +412,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
424412 }
425413
426414 private getImportAction ( barrier : Barrier , userDataProfileImportState : UserDataProfileImportState ) : IAction {
427- const title = localize ( 'import' , "Import {0} profile " , userDataProfileImportState . profile . name ) ;
415+ const title = localize ( 'import' , "Import Profile " , userDataProfileImportState . profile . name ) ;
428416 const importAction = new BarrierAction ( barrier , new Action ( 'import' , title , undefined , true , ( ) => {
429417 const importProfileFn = async ( ) => {
430418 importAction . enabled = false ;
@@ -599,7 +587,7 @@ export class UserDataProfileImportExportService extends Disposable implements IU
599587 return nameIndex + 1 ;
600588 }
601589
602- private async showProfilePreviewView ( id : string , name : string , primary : IAction [ ] , secondary : IAction , refreshAction : boolean , userDataProfilesData : UserDataProfileImportExportState ) : Promise < UserDataProfilePreviewViewPane > {
590+ private async showProfilePreviewView ( id : string , name : string , primary : IAction , secondary : IAction , refreshAction : boolean , userDataProfilesData : UserDataProfileImportExportState ) : Promise < UserDataProfilePreviewViewPane > {
603591 const viewsRegistry = Registry . as < IViewsRegistry > ( Extensions . ViewsRegistry ) ;
604592 const treeView = this . instantiationService . createInstance ( TreeView , id , name ) ;
605593 if ( refreshAction ) {
@@ -705,15 +693,16 @@ class FileUserDataProfileContentHandler implements IUserDataProfileContentHandle
705693class UserDataProfilePreviewViewPane extends TreeViewPane {
706694
707695 private buttonsContainer ! : HTMLElement ;
708- private confirmButton ! : Button | ButtonWithDropdown ;
709- private cancelButton ! : Button ;
696+ private primaryButton ! : Button ;
697+ private secondaryButton ! : Button ;
698+ private messageContainer ! : HTMLElement ;
710699 private dimension : DOM . Dimension | undefined ;
711700 private totalTreeItemsCount : number = 0 ;
712701
713702 constructor (
714703 private readonly userDataProfileData : UserDataProfileImportExportState ,
715- private readonly confirmActions : Action [ ] ,
716- private readonly cancelAction : Action ,
704+ private readonly primaryAction : Action ,
705+ private readonly secondaryAction : Action ,
717706 private readonly actionRunner : IActionRunner ,
718707 options : IViewletViewOptions ,
719708 @IKeybindingService keybindingService : IKeybindingService ,
@@ -732,7 +721,8 @@ class UserDataProfilePreviewViewPane extends TreeViewPane {
732721
733722 protected override renderTreeView ( container : HTMLElement ) : void {
734723 this . treeView . dataProvider = this . userDataProfileData ;
735- super . renderTreeView ( DOM . append ( container , DOM . $ ( '' ) ) ) ;
724+ super . renderTreeView ( DOM . append ( container , DOM . $ ( '.profile-view-tree-container' ) ) ) ;
725+ this . messageContainer = DOM . append ( container , DOM . $ ( '.profile-view-message-container.hide' ) ) ;
736726 this . createButtons ( container ) ;
737727 this . _register ( this . treeView . onDidChangeCheckboxState ( items => {
738728 this . treeView . refresh ( this . userDataProfileData . onDidChangeCheckboxState ( items ) ) ;
@@ -765,42 +755,64 @@ class UserDataProfilePreviewViewPane extends TreeViewPane {
765755 private createButtons ( container : HTMLElement ) : void {
766756 this . buttonsContainer = DOM . append ( container , DOM . $ ( '.profile-view-buttons-container' ) ) ;
767757
768- this . confirmButton = this . _register ( this . confirmActions . length > 1
769- ? new ButtonWithDropdown ( this . buttonsContainer , { ...defaultButtonStyles , actions : this . confirmActions . slice ( 1 ) , contextMenuProvider : this . contextMenuService , actionRunner : this . actionRunner , addPrimaryActionToDropdown : false } )
770- : new Button ( this . buttonsContainer , { ...defaultButtonStyles } ) ) ;
771- this . confirmButton . element . classList . add ( 'profile-view-button' ) ;
772- this . confirmButton . label = this . confirmActions [ 0 ] . label ;
773- this . confirmButton . enabled = this . confirmActions [ 0 ] . enabled ;
774- this . _register ( this . confirmButton . onDidClick ( ( ) => this . actionRunner . run ( this . confirmActions [ 0 ] ) ) ) ;
775- this . _register ( this . confirmActions [ 0 ] . onDidChange ( e => {
758+ this . primaryButton = this . _register ( new Button ( this . buttonsContainer , { ...defaultButtonStyles } ) ) ;
759+ this . primaryButton . element . classList . add ( 'profile-view-button' ) ;
760+ this . primaryButton . label = this . primaryAction . label ;
761+ this . primaryButton . enabled = this . primaryAction . enabled ;
762+ this . _register ( this . primaryButton . onDidClick ( ( ) => this . actionRunner . run ( this . primaryAction ) ) ) ;
763+ this . _register ( this . primaryAction . onDidChange ( e => {
776764 if ( e . enabled !== undefined ) {
777- this . confirmButton . enabled = e . enabled ;
765+ this . primaryButton . enabled = e . enabled ;
778766 }
779767 } ) ) ;
780768
781- this . cancelButton = this . _register ( new Button ( this . buttonsContainer , { secondary : true , ...defaultButtonStyles } ) ) ;
782- this . cancelButton . label = this . cancelAction . label ;
783- this . cancelButton . element . classList . add ( 'profile-view-button' ) ;
784- this . cancelButton . enabled = this . cancelAction . enabled ;
785- this . _register ( this . cancelButton . onDidClick ( ( ) => this . actionRunner . run ( this . cancelAction ) ) ) ;
786- this . _register ( this . cancelAction . onDidChange ( e => {
769+ this . secondaryButton = this . _register ( new Button ( this . buttonsContainer , { secondary : true , ...defaultButtonStyles } ) ) ;
770+ this . secondaryButton . label = this . secondaryAction . label ;
771+ this . secondaryButton . element . classList . add ( 'profile-view-button' ) ;
772+ this . secondaryButton . enabled = this . secondaryAction . enabled ;
773+ this . _register ( this . secondaryButton . onDidClick ( ( ) => this . actionRunner . run ( this . secondaryAction ) ) ) ;
774+ this . _register ( this . secondaryAction . onDidChange ( e => {
787775 if ( e . enabled !== undefined ) {
788- this . cancelButton . enabled = e . enabled ;
776+ this . secondaryButton . enabled = e . enabled ;
789777 }
790778 } ) ) ;
791779 }
792780
793781 protected override layoutTreeView ( height : number , width : number ) : void {
794782 this . dimension = new DOM . Dimension ( width , height ) ;
783+
784+ let messageContainerHeight = 0 ;
785+ if ( ! this . messageContainer . classList . contains ( 'hide' ) ) {
786+ messageContainerHeight = DOM . getClientArea ( this . messageContainer ) . height ;
787+ }
788+
795789 const buttonContainerHeight = 108 ;
796790 this . buttonsContainer . style . height = `${ buttonContainerHeight } px` ;
797791 this . buttonsContainer . style . width = `${ width } px` ;
798792
799- super . layoutTreeView ( Math . min ( height - buttonContainerHeight , 22 * this . totalTreeItemsCount ) , width ) ;
793+ super . layoutTreeView ( Math . min ( height - buttonContainerHeight - messageContainerHeight , 22 * this . totalTreeItemsCount ) , width ) ;
800794 }
801795
802796 private updateConfirmButtonEnablement ( ) : void {
803- this . confirmButton . enabled = this . confirmActions [ 0 ] . enabled && this . userDataProfileData . isEnabled ( ) ;
797+ this . primaryButton . enabled = this . primaryAction . enabled && this . userDataProfileData . isEnabled ( ) ;
798+ }
799+
800+ private readonly renderDisposables = this . _register ( new DisposableStore ( ) ) ;
801+ setMessage ( message : MarkdownString | undefined ) : void {
802+ this . messageContainer . classList . toggle ( 'hide' , ! message ) ;
803+ DOM . clearNode ( this . messageContainer ) ;
804+ if ( message ) {
805+ this . renderDisposables . clear ( ) ;
806+ const rendered = this . renderDisposables . add ( renderMarkdown ( message , {
807+ actionHandler : {
808+ callback : ( content ) => {
809+ this . openerService . open ( content , { allowCommands : true } ) . catch ( onUnexpectedError ) ;
810+ } ,
811+ disposables : this . renderDisposables
812+ }
813+ } ) ) ;
814+ DOM . append ( this . messageContainer , rendered . element ) ;
815+ }
804816 }
805817
806818 refresh ( ) : Promise < void > {
0 commit comments