2020-11-25 23:05:49 +01:00
/ * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
* 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!./gettingStarted' ;
import { localize } from 'vs/nls' ;
2021-06-08 21:57:59 +02:00
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
2021-07-07 14:11:22 +02:00
import { IEditorSerializer , IEditorOpenContext } from 'vs/workbench/common/editor' ;
2021-08-16 11:15:27 +02:00
import { DisposableStore , toDisposable } from 'vs/base/common/lifecycle' ;
2020-11-25 23:05:49 +01:00
import { assertIsDefined } from 'vs/base/common/types' ;
2021-04-24 00:01:54 +02:00
import { $ , addDisposableListener , append , clearNode , Dimension , reset } from 'vs/base/browser/dom' ;
2020-11-25 23:05:49 +01:00
import { ICommandService } from 'vs/platform/commands/common/commands' ;
import { IProductService } from 'vs/platform/product/common/productService' ;
2021-08-25 02:18:52 +02:00
import { hiddenEntriesConfigurationKey , IResolvedWalkthrough , IResolvedWalkthroughStep , IWalkthroughsService } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService' ;
2021-02-04 07:38:49 +01:00
import { IThemeService , registerThemingParticipant , ThemeIcon } from 'vs/platform/theme/common/themeService' ;
2021-08-03 00:53:52 +02:00
import { welcomePageBackground , welcomePageProgressBackground , welcomePageProgressForeground , welcomePageTileBackground , welcomePageTileHoverBackground , welcomePageTileShadow } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedColors' ;
2021-07-01 01:41:01 +02:00
import { activeContrastBorder , buttonBackground , buttonForeground , buttonHoverBackground , contrastBorder , descriptionForeground , focusBorder , foreground , textLinkActiveForeground , textLinkForeground } from 'vs/platform/theme/common/colorRegistry' ;
2020-11-25 23:05:49 +01:00
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding' ;
2021-06-08 21:57:59 +02:00
import { firstSessionDateStorageKey , ITelemetryService } from 'vs/platform/telemetry/common/telemetry' ;
2020-12-15 04:30:43 +01:00
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement' ;
2021-01-05 19:10:36 +01:00
import { gettingStartedCheckedCodicon , gettingStartedUncheckedCodicon } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedIcons' ;
2021-05-14 05:41:29 +02:00
import { IOpenerService , matchesScheme } from 'vs/platform/opener/common/opener' ;
2021-02-04 07:38:49 +01:00
import { URI } from 'vs/base/common/uri' ;
2021-02-11 05:57:10 +01:00
import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane' ;
2021-04-06 00:13:05 +02:00
import { IStorageService , StorageScope , StorageTarget } from 'vs/platform/storage/common/storage' ;
2021-02-11 05:57:10 +01:00
import { CancellationToken } from 'vs/base/common/cancellation' ;
2021-05-28 02:25:11 +02:00
import { ConfigurationTarget , IConfigurationService } from 'vs/platform/configuration/common/configuration' ;
2021-08-16 11:15:27 +02:00
import { ContextKeyExpr , ContextKeyExpression , IContextKeyService , RawContextKey } from 'vs/platform/contextkey/common/contextkey' ;
2021-03-11 07:11:21 +01:00
import { IRecentFolder , IRecentlyOpened , IRecentWorkspace , isRecentFolder , isRecentWorkspace , IWorkspacesService } from 'vs/platform/workspaces/common/workspaces' ;
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace' ;
2021-03-11 07:40:11 +01:00
import { onUnexpectedError } from 'vs/base/common/errors' ;
2021-03-11 07:11:21 +01:00
import { ILabelService } from 'vs/platform/label/common/label' ;
import { IWindowOpenable } from 'vs/platform/windows/common/windows' ;
import { splitName } from 'vs/base/common/labels' ;
import { IHostService } from 'vs/workbench/services/host/browser/host' ;
2021-05-11 05:52:05 +02:00
import { isMacintosh , locale } from 'vs/base/common/platform' ;
2021-03-22 23:03:15 +01:00
import { Throttler } from 'vs/base/common/async' ;
2021-03-23 03:06:33 +01:00
import { GettingStartedInput } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedInput' ;
2021-04-14 03:31:44 +02:00
import { GroupDirection , GroupsOrder , IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService' ;
2021-04-06 00:13:05 +02:00
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput' ;
2021-05-28 19:32:57 +02:00
import { ILink , LinkedText } from 'vs/base/common/linkedText' ;
2021-04-14 03:31:44 +02:00
import { Button } from 'vs/base/browser/ui/button/button' ;
2021-05-17 09:46:50 +02:00
import { attachButtonStyler } from 'vs/platform/theme/common/styler' ;
2021-04-14 03:31:44 +02:00
import { Link } from 'vs/platform/opener/browser/link' ;
2021-04-15 02:57:11 +02:00
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer' ;
2021-04-24 00:01:54 +02:00
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview' ;
2021-09-03 21:17:02 +02:00
import { DEFAULT_MARKDOWN_STYLES , renderMarkdownDocument } from 'vs/workbench/contrib/markdown/browser/markdownDocumentRenderer' ;
2021-04-24 00:01:54 +02:00
import { IModeService } from 'vs/editor/common/services/modeService' ;
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions' ;
import { generateUuid } from 'vs/base/common/uuid' ;
import { TokenizationRegistry } from 'vs/editor/common/modes' ;
import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization' ;
import { ResourceMap } from 'vs/base/common/map' ;
import { IFileService } from 'vs/platform/files/common/files' ;
import { joinPath } from 'vs/base/common/resources' ;
2021-05-07 01:46:12 +02:00
import { INotificationService } from 'vs/platform/notification/common/notification' ;
2021-05-12 02:30:59 +02:00
import { asWebviewUri } from 'vs/workbench/api/common/shared/webview' ;
2021-05-14 05:41:29 +02:00
import { Schemas } from 'vs/base/common/network' ;
2021-05-25 17:12:49 +02:00
import { IEditorOptions } from 'vs/platform/editor/common/editor' ;
2021-08-16 11:15:27 +02:00
import { coalesce , equals , flatten } from 'vs/base/common/arrays' ;
2021-05-28 02:25:11 +02:00
import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService' ;
2021-07-01 01:41:01 +02:00
import { ACTIVITY_BAR_BADGE_BACKGROUND , ACTIVITY_BAR_BADGE_FOREGROUND } from 'vs/workbench/common/theme' ;
2021-08-18 02:47:45 +02:00
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer' ;
2021-08-16 11:15:27 +02:00
import { startEntries } from 'vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent' ;
import { GettingStartedIndexList } from './gettingStartedList' ;
2021-08-18 02:47:45 +02:00
import product from 'vs/platform/product/common/product' ;
2021-08-25 20:29:45 +02:00
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent' ;
import { KeyCode } from 'vs/base/common/keyCodes' ;
2021-03-19 05:18:29 +01:00
2021-02-11 05:57:10 +01:00
const SLIDE_TRANSITION_TIME_MS = 250 ;
2021-02-12 03:11:20 +01:00
const configurationKey = 'workbench.startupEditor' ;
2020-11-25 23:05:49 +01:00
2021-08-18 04:03:16 +02:00
export const allWalkthroughsHiddenContext = new RawContextKey ( 'allWalkthroughsHidden' , false ) ;
2021-06-16 00:23:27 +02:00
export const inWelcomeContext = new RawContextKey ( 'inWelcome' , false ) ;
2021-02-24 20:33:05 +01:00
2021-08-16 11:15:27 +02:00
export interface IWelcomePageStartEntry {
id : string
title : string
description : string
command : string
order : number
icon : { type : 'icon' , icon : ThemeIcon }
when : ContextKeyExpression
}
const parsedStartEntries : IWelcomePageStartEntry [ ] = startEntries . map ( ( e , i ) = > ( {
command : e.content.command ,
description : e.description ,
icon : { type : 'icon' , icon : e.icon } ,
id : e.id ,
order : i ,
title : e.title ,
when : ContextKeyExpr.deserialize ( e . when ) ? ? ContextKeyExpr . true ( )
} ) ) ;
2021-03-11 07:11:21 +01:00
type GettingStartedActionClassification = {
command : { classification : 'PublicNonPersonalData' , purpose : 'FeatureInsight' } ;
argument : { classification : 'PublicNonPersonalData' , purpose : 'FeatureInsight' } ;
} ;
2021-08-16 11:15:27 +02:00
2021-03-11 07:11:21 +01:00
type GettingStartedActionEvent = {
command : string ;
argument : string | undefined ;
} ;
2021-08-16 11:15:27 +02:00
type RecentEntry = ( IRecentFolder | IRecentWorkspace ) & { id : string } ;
2021-07-14 02:33:52 +02:00
const REDUCED_MOTION_KEY = 'workbench.welcomePage.preferReducedMotion' ;
2021-02-11 05:57:10 +01:00
export class GettingStartedPage extends EditorPane {
2021-01-19 20:40:20 +01:00
2021-05-03 10:53:20 +02:00
public static readonly ID = 'gettingStartedPage' ;
2021-01-19 20:40:20 +01:00
2021-02-11 05:57:10 +01:00
private editorInput ! : GettingStartedInput ;
2020-11-25 23:05:49 +01:00
private inProgressScroll = Promise . resolve ( ) ;
2021-01-19 20:42:27 +01:00
private dispatchListeners : DisposableStore = new DisposableStore ( ) ;
2021-04-24 01:34:52 +02:00
private stepDisposables : DisposableStore = new DisposableStore ( ) ;
2021-04-14 03:31:44 +02:00
private detailsPageDisposables : DisposableStore = new DisposableStore ( ) ;
2020-11-25 23:05:49 +01:00
2021-08-16 11:15:27 +02:00
private gettingStartedCategories : IResolvedWalkthrough [ ] ;
private currentWalkthrough : IResolvedWalkthrough | undefined ;
2020-11-25 23:05:49 +01:00
2021-04-10 00:32:12 +02:00
private categoriesPageScrollbar : DomScrollableElement | undefined ;
private detailsPageScrollbar : DomScrollableElement | undefined ;
2021-04-09 23:53:07 +02:00
2020-12-16 01:46:26 +01:00
private detailsScrollbar : DomScrollableElement | undefined ;
2021-04-10 00:32:12 +02:00
2021-03-22 23:03:15 +01:00
private buildSlideThrottle : Throttler = new Throttler ( ) ;
2020-12-15 04:30:43 +01:00
2021-02-11 05:57:10 +01:00
private container : HTMLElement ;
2021-02-24 20:33:05 +01:00
private contextService : IContextKeyService ;
2021-08-16 11:15:27 +02:00
2021-03-11 07:11:21 +01:00
private recentlyOpened : Promise < IRecentlyOpened > ;
2021-03-26 02:21:56 +01:00
private hasScrolledToFirstCategory = false ;
2021-08-16 11:15:27 +02:00
private recentlyOpenedList? : GettingStartedIndexList < RecentEntry > ;
private startList? : GettingStartedIndexList < IWelcomePageStartEntry > ;
private gettingStartedList? : GettingStartedIndexList < IResolvedWalkthrough > ;
2021-04-09 20:46:04 +02:00
2021-04-24 01:34:52 +02:00
private stepsSlide ! : HTMLElement ;
2021-04-09 20:46:04 +02:00
private categoriesSlide ! : HTMLElement ;
2021-04-24 01:34:52 +02:00
private stepsContent ! : HTMLElement ;
private stepMediaComponent ! : HTMLElement ;
2021-04-24 00:01:54 +02:00
2021-05-29 19:18:09 +02:00
private layoutMarkdown : ( ( ) = > void ) | undefined ;
2021-04-24 00:01:54 +02:00
private webviewID = generateUuid ( ) ;
2021-02-24 20:33:05 +01:00
2020-11-25 23:05:49 +01:00
constructor (
@ICommandService private readonly commandService : ICommandService ,
@IProductService private readonly productService : IProductService ,
@IKeybindingService private readonly keybindingService : IKeybindingService ,
2021-08-16 11:15:27 +02:00
@IWalkthroughsService private readonly gettingStartedService : IWalkthroughsService ,
2021-02-12 03:11:20 +01:00
@IConfigurationService private readonly configurationService : IConfigurationService ,
2021-02-11 05:57:10 +01:00
@ITelemetryService telemetryService : ITelemetryService ,
2021-04-24 00:01:54 +02:00
@IModeService private readonly modeService : IModeService ,
@IFileService private readonly fileService : IFileService ,
2021-01-19 20:57:21 +01:00
@IOpenerService private readonly openerService : IOpenerService ,
2021-02-11 05:57:10 +01:00
@IThemeService themeService : IThemeService ,
2021-03-26 02:27:04 +01:00
@IStorageService private storageService : IStorageService ,
2021-04-24 00:01:54 +02:00
@IExtensionService private readonly extensionService : IExtensionService ,
2021-04-14 03:31:44 +02:00
@IInstantiationService private readonly instantiationService : IInstantiationService ,
2021-05-07 01:46:12 +02:00
@INotificationService private readonly notificationService : INotificationService ,
2021-04-05 21:07:50 +02:00
@IEditorGroupsService private readonly groupsService : IEditorGroupsService ,
2021-02-24 20:33:05 +01:00
@IContextKeyService contextService : IContextKeyService ,
2021-04-06 00:13:05 +02:00
@IQuickInputService private quickInputService : IQuickInputService ,
2021-03-11 07:11:21 +01:00
@IWorkspacesService workspacesService : IWorkspacesService ,
@ILabelService private readonly labelService : ILabelService ,
@IHostService private readonly hostService : IHostService ,
2021-04-24 00:01:54 +02:00
@IWebviewService private readonly webviewService : IWebviewService ,
2021-03-11 07:11:21 +01:00
@IWorkspaceContextService private readonly workspaceContextService : IWorkspaceContextService ,
2020-11-30 23:53:00 +01:00
) {
2020-11-25 23:05:49 +01:00
2021-02-11 05:57:10 +01:00
super ( GettingStartedPage . ID , telemetryService , themeService , storageService ) ;
2021-03-13 06:09:13 +01:00
this . container = $ ( '.gettingStartedContainer' ,
{
role : 'document' ,
tabindex : 0 ,
2021-06-16 00:23:27 +02:00
'aria-label' : localize ( 'welcomeAriaLabel' , "Overview of how to get up to speed with your editor." )
2021-03-13 06:09:13 +01:00
} ) ;
2021-04-24 01:34:52 +02:00
this . stepMediaComponent = $ ( '.getting-started-media' ) ;
this . stepMediaComponent . id = generateUuid ( ) ;
2021-04-10 04:35:34 +02:00
2021-02-24 20:33:05 +01:00
this . contextService = this . _register ( contextService . createScoped ( this . container ) ) ;
2021-06-16 00:23:27 +02:00
inWelcomeContext . bindTo ( this . contextService ) . set ( true ) ;
2021-02-24 20:33:05 +01:00
2021-08-16 11:15:27 +02:00
this . gettingStartedCategories = this . gettingStartedService . getWalkthroughs ( ) ;
2021-01-19 20:42:27 +01:00
this . _register ( this . dispatchListeners ) ;
2021-03-22 23:03:15 +01:00
this . buildSlideThrottle = new Throttler ( ) ;
2021-04-16 05:10:57 +02:00
const rerender = ( ) = > {
2021-08-16 11:15:27 +02:00
this . gettingStartedCategories = this . gettingStartedService . getWalkthroughs ( ) ;
if ( this . currentWalkthrough ) {
const existingSteps = this . currentWalkthrough . steps . map ( step = > step . id ) ;
const newCategory = this . gettingStartedCategories . find ( category = > this . currentWalkthrough ? . id === category . id ) ;
if ( newCategory ) {
const newSteps = newCategory . steps . map ( step = > step . id ) ;
if ( ! equals ( newSteps , existingSteps ) ) {
2021-05-27 02:44:59 +02:00
this . buildSlideThrottle . queue ( ( ) = > this . buildCategoriesSlide ( ) ) ;
}
}
} else {
this . buildSlideThrottle . queue ( ( ) = > this . buildCategoriesSlide ( ) ) ;
}
2021-04-16 05:10:57 +02:00
} ;
2021-08-16 11:15:27 +02:00
this . _register ( this . gettingStartedService . onDidAddWalkthrough ( rerender ) ) ;
this . _register ( this . gettingStartedService . onDidRemoveWalkthrough ( rerender ) ) ;
2021-04-24 01:34:52 +02:00
2021-08-16 11:15:27 +02:00
this . _register ( this . gettingStartedService . onDidChangeWalkthrough ( category = > {
2021-03-19 05:18:29 +01:00
const ourCategory = this . gettingStartedCategories . find ( c = > c . id === category . id ) ;
2021-03-22 23:03:15 +01:00
if ( ! ourCategory ) { return ; }
2021-03-19 05:18:29 +01:00
ourCategory . title = category . title ;
ourCategory . description = category . description ;
2021-04-17 02:22:23 +02:00
2021-04-24 01:34:52 +02:00
this . container . querySelectorAll < HTMLDivElement > ( ` [x-category-title-for=" ${ category . id } "] ` ) . forEach ( step = > ( step as HTMLDivElement ) . innerText = ourCategory . title ) ;
this . container . querySelectorAll < HTMLDivElement > ( ` [x-category-description-for=" ${ category . id } "] ` ) . forEach ( step = > ( step as HTMLDivElement ) . innerText = ourCategory . description ) ;
2021-03-19 05:18:29 +01:00
} ) ) ;
2021-04-24 01:34:52 +02:00
this . _register ( this . gettingStartedService . onDidProgressStep ( step = > {
const category = this . gettingStartedCategories . find ( category = > category . id === step . category ) ;
if ( ! category ) { throw Error ( 'Could not find category with ID: ' + step . category ) ; }
2021-08-16 11:15:27 +02:00
const ourStep = category . steps . find ( _step = > _step . id === step . id ) ;
2021-04-24 01:34:52 +02:00
if ( ! ourStep ) {
throw Error ( 'Could not find step with ID: ' + step . id ) ;
2020-11-25 23:05:49 +01:00
}
2021-06-03 19:54:28 +02:00
2021-08-16 11:15:27 +02:00
const stats = this . getWalkthroughCompletionStats ( category ) ;
if ( ! ourStep . done && stats . stepsComplete === stats . stepsTotal - 1 ) {
2021-06-03 19:54:28 +02:00
this . hideCategory ( category . id ) ;
}
2021-07-14 02:33:52 +02:00
this . _register ( this . configurationService . onDidChangeConfiguration ( e = > {
if ( e . affectsConfiguration ( REDUCED_MOTION_KEY ) ) {
this . container . classList . toggle ( 'animatable' , this . shouldAnimate ( ) ) ;
}
} ) ) ;
2021-04-24 01:34:52 +02:00
ourStep . done = step . done ;
2021-08-16 11:15:27 +02:00
if ( category . id === this . currentWalkthrough ? . id ) {
2021-04-24 01:34:52 +02:00
const badgeelements = assertIsDefined ( document . querySelectorAll ( ` [data-done-step-id=" ${ step . id } "] ` ) ) ;
2021-01-19 20:40:20 +01:00
badgeelements . forEach ( badgeelement = > {
2021-04-24 01:34:52 +02:00
if ( step . done ) {
2021-02-24 20:15:40 +01:00
badgeelement . parentElement ? . setAttribute ( 'aria-checked' , 'true' ) ;
2021-01-19 20:40:20 +01:00
badgeelement . classList . remove ( . . . ThemeIcon . asClassNameArray ( gettingStartedUncheckedCodicon ) ) ;
badgeelement . classList . add ( 'complete' , . . . ThemeIcon . asClassNameArray ( gettingStartedCheckedCodicon ) ) ;
}
else {
2021-02-24 20:15:40 +01:00
badgeelement . parentElement ? . setAttribute ( 'aria-checked' , 'false' ) ;
2021-01-19 20:40:20 +01:00
badgeelement . classList . remove ( 'complete' , . . . ThemeIcon . asClassNameArray ( gettingStartedCheckedCodicon ) ) ;
2021-03-17 09:30:09 +01:00
badgeelement . classList . add ( . . . ThemeIcon . asClassNameArray ( gettingStartedUncheckedCodicon ) ) ;
2021-01-19 20:40:20 +01:00
}
} ) ;
2020-11-25 23:05:49 +01:00
}
this . updateCategoryProgress ( ) ;
} ) ) ;
2021-03-11 07:11:21 +01:00
this . recentlyOpened = workspacesService . getRecentlyOpened ( ) ;
2020-11-25 23:05:49 +01:00
}
2021-07-14 02:33:52 +02:00
private shouldAnimate() {
return ! this . configurationService . getValue ( REDUCED_MOTION_KEY ) ;
}
2021-08-16 11:15:27 +02:00
private getWalkthroughCompletionStats ( walkthrough : IResolvedWalkthrough ) : { stepsComplete : number , stepsTotal : number } {
const activeSteps = walkthrough . steps . filter ( s = > this . contextService . contextMatchesRules ( s . when ) ) ;
return {
stepsComplete : activeSteps.filter ( s = > s . done ) . length ,
stepsTotal : activeSteps.length ,
} ;
}
2021-05-25 17:12:49 +02:00
override async setInput ( newInput : GettingStartedInput , options : IEditorOptions | undefined , context : IEditorOpenContext , token : CancellationToken ) {
2021-07-14 02:33:52 +02:00
this . container . classList . remove ( 'animatable' ) ;
2021-02-11 05:57:10 +01:00
this . editorInput = newInput ;
await super . setInput ( newInput , options , context , token ) ;
2021-02-24 21:06:15 +01:00
await this . buildCategoriesSlide ( ) ;
2021-07-14 02:33:52 +02:00
if ( this . shouldAnimate ( ) ) {
setTimeout ( ( ) = > this . container . classList . add ( 'animatable' ) , 0 ) ;
}
2021-02-11 05:57:10 +01:00
}
2021-05-26 02:22:09 +02:00
async makeCategoryVisibleWhenAvailable ( categoryID : string , stepId? : string ) {
2021-05-22 01:03:36 +02:00
await this . gettingStartedService . installedExtensionsRegistered ;
2021-08-16 11:15:27 +02:00
this . gettingStartedCategories = this . gettingStartedService . getWalkthroughs ( ) ;
2021-03-23 03:06:33 +01:00
const ourCategory = this . gettingStartedCategories . find ( c = > c . id === categoryID ) ;
if ( ! ourCategory ) {
throw Error ( 'Could not find category with ID: ' + categoryID ) ;
}
2021-08-16 11:15:27 +02:00
2021-05-26 02:22:09 +02:00
this . scrollToCategory ( categoryID , stepId ) ;
2021-03-23 03:06:33 +01:00
}
2021-02-11 05:57:10 +01:00
private registerDispatchListeners() {
2021-01-19 20:42:27 +01:00
this . dispatchListeners . clear ( ) ;
2020-11-25 23:05:49 +01:00
2021-02-11 05:57:10 +01:00
this . container . querySelectorAll ( '[x-dispatch]' ) . forEach ( element = > {
2020-11-25 23:05:49 +01:00
const [ command , argument ] = ( element . getAttribute ( 'x-dispatch' ) ? ? '' ) . split ( ':' ) ;
if ( command ) {
2021-01-19 20:42:27 +01:00
this . dispatchListeners . add ( addDisposableListener ( element , 'click' , ( e ) = > {
2021-03-13 06:09:13 +01:00
e . stopPropagation ( ) ;
2021-06-16 02:41:53 +02:00
this . runDispatchCommand ( command , argument ) ;
2020-11-25 23:05:49 +01:00
} ) ) ;
}
} ) ;
}
2021-06-16 02:41:53 +02:00
private async runDispatchCommand ( command : string , argument : string ) {
this . commandService . executeCommand ( 'workbench.action.keepEditor' ) ;
this . telemetryService . publicLog2 < GettingStartedActionEvent , GettingStartedActionClassification > ( 'gettingStarted.ActionExecuted' , { command , argument } ) ;
switch ( command ) {
case 'scrollPrev' : {
this . scrollPrev ( ) ;
break ;
}
case 'skip' : {
this . runSkip ( ) ;
break ;
}
case 'showMoreRecents' : {
this . commandService . executeCommand ( 'workbench.action.openRecent' ) ;
break ;
}
2021-06-22 02:41:10 +02:00
case 'seeAllWalkthroughs' : {
await this . openWalkthroughSelector ( ) ;
2021-06-16 02:41:53 +02:00
break ;
}
case 'openFolder' : {
this . commandService . executeCommand ( isMacintosh ? 'workbench.action.files.openFileFolder' : 'workbench.action.files.openFolder' ) ;
break ;
}
case 'selectCategory' : {
const selectedCategory = this . gettingStartedCategories . find ( category = > category . id === argument ) ;
if ( ! selectedCategory ) { throw Error ( 'Could not find category with ID ' + argument ) ; }
2021-08-16 11:15:27 +02:00
this . gettingStartedService . markWalkthroughOpened ( argument ) ;
this . gettingStartedList ? . setEntries ( this . gettingStartedService . getWalkthroughs ( ) ) ;
this . scrollToCategory ( argument ) ;
break ;
}
case 'selectStartEntry' : {
const selected = startEntries . find ( e = > e . id === argument ) ;
if ( selected ) {
this . commandService . executeCommand ( selected . content . command ) ;
2021-06-16 02:41:53 +02:00
} else {
2021-08-16 11:15:27 +02:00
throw Error ( 'could not find start entry with id: ' + argument ) ;
2021-06-16 02:41:53 +02:00
}
break ;
}
case 'hideCategory' : {
this . hideCategory ( argument ) ;
break ;
}
// Use selectTask over selectStep to keep telemetry consistant:https://github.com/microsoft/vscode/issues/122256
case 'selectTask' : {
this . selectStep ( argument ) ;
break ;
}
case 'toggleStepCompletion' : {
this . toggleStepCompletion ( argument ) ;
break ;
}
case 'allDone' : {
this . markAllStepsComplete ( ) ;
break ;
}
case 'nextSection' : {
2021-08-16 11:15:27 +02:00
const next = this . currentWalkthrough ? . next ;
2021-06-16 02:41:53 +02:00
if ( next ) {
this . scrollToCategory ( next ) ;
} else {
2021-08-16 11:15:27 +02:00
console . error ( 'Error scrolling to next section of' , this . currentWalkthrough ) ;
2021-06-16 02:41:53 +02:00
}
break ;
}
default : {
console . error ( 'Dispatch to' , command , argument , 'not defined' ) ;
break ;
}
}
}
2021-05-22 03:27:34 +02:00
private hideCategory ( categoryId : string ) {
const selectedCategory = this . gettingStartedCategories . find ( category = > category . id === categoryId ) ;
if ( ! selectedCategory ) { throw Error ( 'Could not find category with ID ' + categoryId ) ; }
this . setHiddenCategories ( [ . . . this . getHiddenCategories ( ) . add ( categoryId ) ] ) ;
this . gettingStartedList ? . rerender ( ) ;
}
private markAllStepsComplete() {
2021-08-16 11:15:27 +02:00
if ( this . currentWalkthrough ) {
this . currentWalkthrough ? . steps . forEach ( step = > {
if ( ! step . done ) {
this . gettingStartedService . progressStep ( step . id ) ;
}
} ) ;
this . hideCategory ( this . currentWalkthrough ? . id ) ;
this . scrollPrev ( ) ;
} else {
throw Error ( 'No walkthrough opened' ) ;
2021-05-22 03:27:34 +02:00
}
}
2021-04-24 01:34:52 +02:00
private toggleStepCompletion ( argument : string ) {
2021-08-16 11:15:27 +02:00
const stepToggle = assertIsDefined ( this . currentWalkthrough ? . steps . find ( step = > step . id === argument ) ) ;
2021-04-24 01:34:52 +02:00
if ( stepToggle . done ) {
this . gettingStartedService . deprogressStep ( argument ) ;
2021-04-10 05:36:48 +02:00
} else {
2021-04-24 01:34:52 +02:00
this . gettingStartedService . progressStep ( argument ) ;
2021-04-10 05:36:48 +02:00
}
}
2021-06-22 02:41:10 +02:00
private async openWalkthroughSelector() {
2021-08-16 11:15:27 +02:00
const selection = await this . quickInputService . pick ( this . gettingStartedCategories . map ( x = > ( {
2021-04-06 00:13:05 +02:00
id : x.id ,
label : x.title ,
detail : x.description ,
2021-07-14 01:57:41 +02:00
description : x.source ,
2021-07-14 01:59:42 +02:00
} ) ) , { canPickMany : false , matchOnDescription : true , matchOnDetail : true , title : localize ( 'pickWalkthroughs' , "Open Walkthrough..." ) } ) ;
2021-06-22 02:41:10 +02:00
if ( selection ) {
2021-06-26 00:01:56 +02:00
this . runDispatchCommand ( 'selectCategory' , selection . id ) ;
2021-04-06 00:13:05 +02:00
}
}
2021-06-26 03:01:06 +02:00
private svgCache = new ResourceMap < Promise < string > > ( ) ;
private readAndCacheSVGFile ( path : URI ) : Promise < string > {
if ( ! this . svgCache . has ( path ) ) {
this . svgCache . set ( path , ( async ( ) = > {
try {
const bytes = await this . fileService . readFile ( path ) ;
return bytes . value . toString ( ) ;
} catch ( e ) {
this . notificationService . error ( 'Error reading svg document at `' + path + '`: ' + e ) ;
return '' ;
}
} ) ( ) ) ;
}
return assertIsDefined ( this . svgCache . get ( path ) ) ;
}
2021-04-24 00:01:54 +02:00
private mdCache = new ResourceMap < Promise < string > > ( ) ;
2021-04-24 01:34:52 +02:00
private async readAndCacheStepMarkdown ( path : URI ) : Promise < string > {
2021-04-24 00:01:54 +02:00
if ( ! this . mdCache . has ( path ) ) {
this . mdCache . set ( path , ( async ( ) = > {
2021-05-28 02:25:11 +02:00
try {
const module Id = JSON . parse ( path . query ) . module Id ;
if ( module Id ) {
return new Promise < string > ( resolve = > {
require ( [ module Id ] , content = > {
const markdown = content . default ( ) ;
resolve ( renderMarkdownDocument ( markdown , this . extensionService , this . modeService ) ) ;
} ) ;
} ) ;
}
} catch { }
2021-05-07 01:46:12 +02:00
try {
2021-05-11 05:52:05 +02:00
const localizedPath = path . with ( { path : path.path.replace ( /\.md$/ , ` .nls. ${ locale } .md ` ) } ) ;
const generalizedLocale = locale ? . replace ( /-.*$/ , '' ) ;
const generalizedLocalizedPath = path . with ( { path : path.path.replace ( /\.md$/ , ` .nls. ${ generalizedLocale } .md ` ) } ) ;
const fileExists = ( file : URI ) = > this . fileService . resolve ( file ) . then ( ( ) = > true ) . catch ( ( ) = > false ) ;
const [ localizedFileExists , generalizedLocalizedFileExists ] = await Promise . all ( [
fileExists ( localizedPath ) ,
fileExists ( generalizedLocalizedPath ) ,
] ) ;
const bytes = await this . fileService . readFile (
localizedFileExists
? localizedPath
: generalizedLocalizedFileExists
? generalizedLocalizedPath
: path ) ;
2021-05-07 01:46:12 +02:00
const markdown = bytes . value . toString ( ) ;
return renderMarkdownDocument ( markdown , this . extensionService , this . modeService ) ;
} catch ( e ) {
this . notificationService . error ( 'Error reading markdown document at `' + path + '`: ' + e ) ;
return '' ;
}
2021-04-24 00:01:54 +02:00
} ) ( ) ) ;
}
return assertIsDefined ( this . mdCache . get ( path ) ) ;
}
2021-04-06 00:13:05 +02:00
private getHiddenCategories ( ) : Set < string > {
return new Set ( JSON . parse ( this . storageService . get ( hiddenEntriesConfigurationKey , StorageScope . GLOBAL , '[]' ) ) ) ;
}
private setHiddenCategories ( hidden : string [ ] ) {
this . storageService . store (
hiddenEntriesConfigurationKey ,
JSON . stringify ( hidden ) ,
StorageScope . GLOBAL ,
StorageTarget . USER ) ;
}
2021-05-08 18:58:56 +02:00
private async buildMediaComponent ( stepId : string ) {
2021-08-16 11:15:27 +02:00
if ( ! this . currentWalkthrough ) {
throw Error ( 'no walkthrough selected' ) ;
2021-05-08 18:58:56 +02:00
}
2021-08-16 11:15:27 +02:00
const stepToExpand = assertIsDefined ( this . currentWalkthrough . steps . find ( step = > step . id === stepId ) ) ;
2021-05-07 00:23:17 +02:00
2021-04-24 01:34:52 +02:00
this . stepDisposables . clear ( ) ;
clearNode ( this . stepMediaComponent ) ;
2021-04-24 00:01:54 +02:00
2021-05-08 18:58:56 +02:00
if ( stepToExpand . media . type === 'image' ) {
2021-07-19 22:42:47 +02:00
this . stepsContent . classList . add ( 'image' ) ;
this . stepsContent . classList . remove ( 'markdown' ) ;
2021-05-08 18:58:56 +02:00
const media = stepToExpand . media ;
const mediaElement = $ < HTMLImageElement > ( 'img' ) ;
this . stepMediaComponent . appendChild ( mediaElement ) ;
mediaElement . setAttribute ( 'alt' , media . altText ) ;
this . updateMediaSourceForColorMode ( mediaElement , media . path ) ;
2021-06-03 22:07:33 +02:00
this . stepDisposables . add ( addDisposableListener ( this . stepMediaComponent , 'click' , ( ) = > {
2021-05-28 19:32:57 +02:00
const hrefs = flatten ( stepToExpand . description . map ( lt = > lt . nodes . filter ( ( node ) : node is ILink = > typeof node !== 'string' ) . map ( node = > node . href ) ) ) ;
if ( hrefs . length === 1 ) {
const href = hrefs [ 0 ] ;
if ( href . startsWith ( 'http' ) ) {
this . telemetryService . publicLog2 < GettingStartedActionEvent , GettingStartedActionClassification > ( 'gettingStarted.ActionExecuted' , { command : 'runStepAction' , argument : href } ) ;
this . openerService . open ( href ) ;
}
}
} ) ) ;
2021-05-08 18:58:56 +02:00
this . stepDisposables . add ( this . themeService . onDidColorThemeChange ( ( ) = > this . updateMediaSourceForColorMode ( mediaElement , media . path ) ) ) ;
2021-06-26 03:01:06 +02:00
}
else if ( stepToExpand . media . type === 'svg' ) {
2021-07-19 22:42:47 +02:00
this . stepsContent . classList . add ( 'image' ) ;
this . stepsContent . classList . remove ( 'markdown' ) ;
2021-06-26 03:01:06 +02:00
const media = stepToExpand . media ;
const webview = this . stepDisposables . add ( this . webviewService . createWebviewElement ( this . webviewID , { } , { } , undefined ) ) ;
webview . mountTo ( this . stepMediaComponent ) ;
webview . html = await this . renderSVG ( media . path ) ;
let isDisposed = false ;
this . stepDisposables . add ( toDisposable ( ( ) = > { isDisposed = true ; } ) ) ;
this . stepDisposables . add ( this . themeService . onDidColorThemeChange ( async ( ) = > {
// Render again since color vars change
const body = await this . renderSVG ( media . path ) ;
if ( ! isDisposed ) { // Make sure we weren't disposed of in the meantime
webview . html = body ;
}
} ) ) ;
this . stepDisposables . add ( addDisposableListener ( this . stepMediaComponent , 'click' , ( ) = > {
const hrefs = flatten ( stepToExpand . description . map ( lt = > lt . nodes . filter ( ( node ) : node is ILink = > typeof node !== 'string' ) . map ( node = > node . href ) ) ) ;
if ( hrefs . length === 1 ) {
const href = hrefs [ 0 ] ;
if ( href . startsWith ( 'http' ) ) {
this . telemetryService . publicLog2 < GettingStartedActionEvent , GettingStartedActionClassification > ( 'gettingStarted.ActionExecuted' , { command : 'runStepAction' , argument : href } ) ;
this . openerService . open ( href ) ;
}
}
} ) ) ;
2021-07-14 02:10:29 +02:00
this . stepDisposables . add ( webview . onDidClickLink ( link = > {
if ( matchesScheme ( link , Schemas . https ) || matchesScheme ( link , Schemas . http ) || ( matchesScheme ( link , Schemas . command ) ) ) {
this . openerService . open ( link , { allowCommands : true } ) ;
}
} ) ) ;
2021-06-26 03:01:06 +02:00
}
else if ( stepToExpand . media . type === 'markdown' ) {
2021-05-08 18:58:56 +02:00
2021-07-19 22:42:47 +02:00
this . stepsContent . classList . remove ( 'image' ) ;
this . stepsContent . classList . add ( 'markdown' ) ;
2021-05-08 18:58:56 +02:00
const media = stepToExpand . media ;
2021-05-28 02:25:11 +02:00
const webview = this . stepDisposables . add ( this . webviewService . createWebviewElement ( this . webviewID , { } , { localResourceRoots : [ media . root ] , allowScripts : true } , undefined ) ) ;
2021-05-08 18:58:56 +02:00
webview . mountTo ( this . stepMediaComponent ) ;
2021-05-28 02:25:11 +02:00
const rawHTML = await this . renderMarkdown ( media . path , media . base ) ;
webview . html = rawHTML ;
2021-05-29 03:56:25 +02:00
const serializedContextKeyExprs = rawHTML . match ( /checked-on=\"([^'][^"]*)\"/g ) ? . map ( attr = > attr . slice ( 'checked-on="' . length , - 1 )
. replace ( /'/g , '\'' )
. replace ( /&/g , '&' ) ) ;
2021-05-28 02:25:11 +02:00
const postTrueKeysMessage = ( ) = > {
const enabledContextKeys = serializedContextKeyExprs ? . filter ( expr = > this . contextService . contextMatchesRules ( ContextKeyExpr . deserialize ( expr ) ) ) ;
2021-05-29 19:18:09 +02:00
if ( enabledContextKeys ) {
webview . postMessage ( {
enabledContextKeys
} ) ;
}
2021-05-28 02:25:11 +02:00
} ;
2021-05-08 18:58:56 +02:00
let isDisposed = false ;
this . stepDisposables . add ( toDisposable ( ( ) = > { isDisposed = true ; } ) ) ;
2021-05-14 05:41:29 +02:00
this . stepDisposables . add ( webview . onDidClickLink ( link = > {
2021-06-04 19:27:55 +02:00
if ( matchesScheme ( link , Schemas . https ) || matchesScheme ( link , Schemas . http ) || ( matchesScheme ( link , Schemas . command ) ) ) {
2021-05-14 05:41:29 +02:00
this . openerService . open ( link , { allowCommands : true } ) ;
}
} ) ) ;
2021-05-08 18:58:56 +02:00
this . stepDisposables . add ( this . themeService . onDidColorThemeChange ( async ( ) = > {
// Render again since syntax highlighting of code blocks may have changed
const body = await this . renderMarkdown ( media . path , media . base ) ;
if ( ! isDisposed ) { // Make sure we weren't disposed of in the meantime
webview . html = body ;
2021-05-28 02:25:11 +02:00
postTrueKeysMessage ( ) ;
2021-05-08 18:58:56 +02:00
}
} ) ) ;
2021-05-28 02:25:11 +02:00
if ( serializedContextKeyExprs ) {
const contextKeyExprs = coalesce ( serializedContextKeyExprs . map ( expr = > ContextKeyExpr . deserialize ( expr ) ) ) ;
const watchingKeys = new Set ( flatten ( contextKeyExprs . map ( expr = > expr . keys ( ) ) ) ) ;
this . stepDisposables . add ( this . contextService . onDidChangeContext ( e = > {
if ( e . affectsSome ( watchingKeys ) ) { postTrueKeysMessage ( ) ; }
} ) ) ;
2021-05-29 19:18:09 +02:00
this . layoutMarkdown = ( ) = > { webview . postMessage ( { layout : true } ) ; } ;
this . stepDisposables . add ( { dispose : ( ) = > this . layoutMarkdown = undefined } ) ;
this . layoutMarkdown ( ) ;
2021-05-28 02:25:11 +02:00
postTrueKeysMessage ( ) ;
webview . onMessage ( e = > {
const message : string = e . message as string ;
if ( message . startsWith ( 'command:' ) ) {
this . openerService . open ( message , { allowCommands : true } ) ;
} else if ( message . startsWith ( 'setTheme:' ) ) {
this . configurationService . updateValue ( ThemeSettings . COLOR_THEME , message . slice ( 'setTheme:' . length ) , ConfigurationTarget . USER ) ;
} else {
console . error ( 'Unexpected message' , message ) ;
}
} ) ;
}
2021-05-08 18:58:56 +02:00
}
}
2021-05-22 01:03:36 +02:00
async selectStepLoose ( id : string ) {
const toSelect = this . editorInput . selectedCategory + '#' + id ;
this . selectStep ( toSelect ) ;
}
2021-05-08 18:58:56 +02:00
private async selectStep ( id : string | undefined , delayFocus = true , forceRebuild = false ) {
if ( id && this . editorInput . selectedStep === id && ! forceRebuild ) { return ; }
2020-11-25 23:05:49 +01:00
if ( id ) {
2021-08-25 02:18:52 +02:00
let stepElement = this . container . querySelector < HTMLDivElement > ( ` [data-step-id=" ${ id } "] ` ) ;
if ( ! stepElement ) {
// Selected an element that is not in-context, just fallback to whatever.
stepElement = assertIsDefined ( this . container . querySelector < HTMLDivElement > ( ` [data-step-id] ` ) ) ;
id = assertIsDefined ( stepElement . getAttribute ( 'data-step-id' ) ) ;
}
2021-04-24 01:34:52 +02:00
stepElement . parentElement ? . querySelectorAll < HTMLElement > ( '.expanded' ) . forEach ( node = > {
2021-06-03 22:45:21 +02:00
if ( node . getAttribute ( 'data-step-id' ) !== id ) {
node . classList . remove ( 'expanded' ) ;
node . setAttribute ( 'aria-expanded' , 'false' ) ;
}
2021-02-11 05:57:10 +01:00
} ) ;
2021-04-24 01:34:52 +02:00
setTimeout ( ( ) = > ( stepElement as HTMLElement ) . focus ( ) , delayFocus ? SLIDE_TRANSITION_TIME_MS : 0 ) ;
2021-05-07 00:23:17 +02:00
2021-04-24 01:34:52 +02:00
this . editorInput . selectedStep = id ;
2021-04-24 00:01:54 +02:00
2021-04-24 01:34:52 +02:00
stepElement . classList . add ( 'expanded' ) ;
stepElement . setAttribute ( 'aria-expanded' , 'true' ) ;
2021-05-08 18:58:56 +02:00
this . buildMediaComponent ( id ) ;
2021-05-13 02:48:51 +02:00
this . gettingStartedService . progressByEvent ( 'stepSelected:' + id ) ;
2020-11-25 23:05:49 +01:00
} else {
2021-04-24 01:34:52 +02:00
this . editorInput . selectedStep = undefined ;
2020-11-25 23:05:49 +01:00
}
2021-08-25 18:22:30 +02:00
2021-04-10 00:32:12 +02:00
this . detailsPageScrollbar ? . scanDomNode ( ) ;
2020-12-16 01:46:26 +01:00
this . detailsScrollbar ? . scanDomNode ( ) ;
2020-11-25 23:05:49 +01:00
}
2021-02-04 07:38:49 +01:00
private updateMediaSourceForColorMode ( element : HTMLImageElement , sources : { hc : URI , dark : URI , light : URI } ) {
const themeType = this . themeService . getColorTheme ( ) . type ;
2021-05-28 21:12:47 +02:00
const src = sources [ themeType ] . toString ( true ) . replace ( / /g , '%20' ) ;
element . srcset = src . toLowerCase ( ) . endsWith ( '.svg' ) ? src : ( src + ' 1.5x' ) ;
2021-04-24 00:01:54 +02:00
}
2021-06-26 03:01:06 +02:00
private async renderSVG ( path : URI ) : Promise < string > {
const content = await this . readAndCacheSVGFile ( path ) ;
const nonce = generateUuid ( ) ;
const colorMap = TokenizationRegistry . getColorMap ( ) ;
const css = colorMap ? generateTokensCSSForColorMap ( colorMap ) : '' ;
return ` <!DOCTYPE html>
< html >
< head >
< meta http - equiv = "Content-type" content = "text/html;charset=UTF-8" >
2021-08-25 02:18:52 +02:00
< meta http - equiv = "Content-Security-Policy" content = "default-src 'none'; img-src data:; style-src 'nonce-${nonce}';" >
2021-06-26 03:01:06 +02:00
< style nonce = "${nonce}" >
$ { DEFAULT_MARKDOWN_STYLES }
$ { css }
2021-07-01 03:24:57 +02:00
svg {
position : fixed ;
2021-07-08 00:43:33 +02:00
height : 100 % ;
width : 80 % ;
left : 50 % ;
top : 50 % ;
2021-07-13 20:20:24 +02:00
max - width : 530px ;
min - width : 350px ;
2021-07-08 00:43:33 +02:00
transform : translate ( - 50 % , - 50 % ) ;
2021-07-01 03:24:57 +02:00
}
2021-06-26 03:01:06 +02:00
< / style >
< / head >
< body >
$ { content }
< / body >
< / html > ` ;
}
2021-04-24 00:01:54 +02:00
private async renderMarkdown ( path : URI , base : URI ) : Promise < string > {
2021-04-24 01:34:52 +02:00
const content = await this . readAndCacheStepMarkdown ( path ) ;
2021-04-24 00:01:54 +02:00
const nonce = generateUuid ( ) ;
const colorMap = TokenizationRegistry . getColorMap ( ) ;
2021-05-10 19:17:34 +02:00
const uriTranformedContent = content . replace ( /src="([^"]*)"/g , ( _ , src : string ) = > {
if ( src . startsWith ( 'https://' ) ) { return ` src=" ${ src } " ` ; }
2021-04-24 00:01:54 +02:00
const path = joinPath ( base , src ) ;
2021-05-21 18:28:28 +02:00
const transformed = asWebviewUri ( path ) . toString ( ) ;
2021-04-24 00:01:54 +02:00
return ` src=" ${ transformed } " ` ;
} ) ;
const css = colorMap ? generateTokensCSSForColorMap ( colorMap ) : '' ;
return ` <!DOCTYPE html>
< html >
< head >
< meta http - equiv = "Content-type" content = "text/html;charset=UTF-8" >
2021-05-28 02:25:11 +02:00
< meta http - equiv = "Content-Security-Policy" content = "default-src 'none'; img-src https: data:; media-src https:; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}';" >
2021-04-24 00:01:54 +02:00
< style nonce = "${nonce}" >
2021-05-28 02:25:11 +02:00
$ { DEFAULT_MARKDOWN_STYLES }
$ { css }
2021-06-04 00:00:29 +02:00
body > img {
align - self : flex - start ;
}
body > img [ centered ] {
align - self : center ;
2021-05-08 03:01:26 +02:00
}
body {
display : flex ;
flex - direction : column ;
2021-05-28 02:25:11 +02:00
padding : 0 ;
height : inherit ;
}
checklist {
display : flex ;
flex - wrap : wrap ;
justify - content : space - around ;
}
checkbox {
display : flex ;
flex - direction : column ;
align - items : center ;
2021-05-29 19:18:09 +02:00
margin : 5px ;
2021-05-28 02:25:11 +02:00
cursor : pointer ;
}
checkbox . checked > img {
box - sizing : border - box ;
2021-06-03 23:03:34 +02:00
margin - bottom : 4px ;
2021-05-28 02:25:11 +02:00
}
checkbox . checked > img {
outline : 2px solid var ( -- vscode - focusBorder ) ;
outline - offset : 2px ;
}
2021-06-02 20:44:55 +02:00
blockquote > p :first - child {
margin - top : 0 ;
}
2021-06-02 20:50:07 +02:00
body > * {
2021-06-04 00:00:29 +02:00
margin - block - end : 0.25em ;
margin - block - start : 0.25em ;
2021-06-02 20:50:07 +02:00
}
2021-05-28 02:25:11 +02:00
html {
height : 100 % ;
2021-05-08 03:01:26 +02:00
}
2021-04-24 00:01:54 +02:00
< / style >
< / head >
< body >
$ { uriTranformedContent }
< / body >
2021-05-28 02:25:11 +02:00
< script nonce = "${nonce}" >
const vscode = acquireVsCodeApi ( ) ;
document . querySelectorAll ( '[on-checked]' ) . forEach ( el = > {
el . addEventListener ( 'click' , ( ) = > {
vscode . postMessage ( el . getAttribute ( 'on-checked' ) ) ;
} ) ;
} ) ;
window . addEventListener ( 'message' , event = > {
2021-05-29 19:18:09 +02:00
document . querySelectorAll ( 'vertically-centered' ) . forEach ( element = > {
element . style . marginTop = Math . max ( ( document . body . scrollHeight - element . scrollHeight ) * 2 / 5 , 10 ) + 'px' ;
} )
if ( event . data . enabledContextKeys ) {
document . querySelectorAll ( '.checked' ) . forEach ( element = > element . classList . remove ( 'checked' ) )
for ( const key of event . data . enabledContextKeys ) {
document . querySelectorAll ( '[checked-on="' + key + '"]' ) . forEach ( element = > element . classList . add ( 'checked' ) )
}
2021-05-28 02:25:11 +02:00
}
} ) ;
< / script >
2021-04-24 00:01:54 +02:00
< / html > ` ;
2021-02-04 07:38:49 +01:00
}
2021-02-11 05:57:10 +01:00
createEditor ( parent : HTMLElement ) {
2021-04-10 00:32:12 +02:00
if ( this . detailsPageScrollbar ) { this . detailsPageScrollbar . dispose ( ) ; }
if ( this . categoriesPageScrollbar ) { this . categoriesPageScrollbar . dispose ( ) ; }
this . categoriesSlide = $ ( '.gettingStartedSlideCategories.gettingStartedSlide' ) ;
2021-07-01 17:23:47 +02:00
const prevButton = $ ( 'button.prev-button.button-link' , { 'x-dispatch' : 'scrollPrev' } , $ ( 'span.scroll-button.codicon.codicon-chevron-left' ) , $ ( 'span.moreText' , { } , localize ( 'welcome' , "Welcome" ) ) ) ;
2021-04-24 01:34:52 +02:00
this . stepsSlide = $ ( '.gettingStartedSlideDetails.gettingStartedSlide' , { } , prevButton ) ;
2021-04-10 00:32:12 +02:00
2021-04-24 01:34:52 +02:00
this . stepsContent = $ ( '.gettingStartedDetailsContent' , { } ) ;
2021-04-10 04:35:34 +02:00
2021-04-24 01:34:52 +02:00
this . detailsPageScrollbar = this . _register ( new DomScrollableElement ( this . stepsContent , { className : 'full-height-scrollable' } ) ) ;
2021-04-10 04:35:34 +02:00
this . categoriesPageScrollbar = this . _register ( new DomScrollableElement ( this . categoriesSlide , { className : 'full-height-scrollable categoriesScrollbar' } ) ) ;
2021-02-11 05:57:10 +01:00
2021-04-24 01:34:52 +02:00
this . stepsSlide . appendChild ( this . detailsPageScrollbar . getDomNode ( ) ) ;
2021-04-10 00:32:12 +02:00
2021-04-24 01:34:52 +02:00
const gettingStartedPage = $ ( '.gettingStarted' , { } , this . categoriesPageScrollbar . getDomNode ( ) , this . stepsSlide ) ;
2021-04-10 00:32:12 +02:00
this . container . appendChild ( gettingStartedPage ) ;
2021-04-09 23:53:07 +02:00
2021-04-10 00:32:12 +02:00
this . categoriesPageScrollbar . scanDomNode ( ) ;
this . detailsPageScrollbar . scanDomNode ( ) ;
2021-02-11 05:57:10 +01:00
parent . appendChild ( this . container ) ;
}
2021-02-24 21:06:15 +01:00
private async buildCategoriesSlide() {
2021-02-12 03:11:20 +01:00
const showOnStartupCheckbox = $ ( 'input.checkbox' , { id : 'showOnStartup' , type : 'checkbox' } ) as HTMLInputElement ;
2021-06-16 02:41:53 +02:00
showOnStartupCheckbox . checked = this . configurationService . getValue ( configurationKey ) === 'welcomePage' ;
2021-02-12 03:11:20 +01:00
this . _register ( addDisposableListener ( showOnStartupCheckbox , 'click' , ( ) = > {
2021-03-11 07:11:21 +01:00
if ( showOnStartupCheckbox . checked ) {
this . telemetryService . publicLog2 < GettingStartedActionEvent , GettingStartedActionClassification > ( 'gettingStarted.ActionExecuted' , { command : 'showOnStartupChecked' , argument : undefined } ) ;
2021-06-16 02:41:53 +02:00
this . configurationService . updateValue ( configurationKey , 'welcomePage' ) ;
2021-03-11 07:11:21 +01:00
} else {
this . telemetryService . publicLog2 < GettingStartedActionEvent , GettingStartedActionClassification > ( 'gettingStarted.ActionExecuted' , { command : 'showOnStartupUnchecked' , argument : undefined } ) ;
this . configurationService . updateValue ( configurationKey , 'none' ) ;
}
2021-02-12 03:11:20 +01:00
} ) ) ;
2020-12-15 19:52:35 +01:00
2021-04-09 20:46:04 +02:00
const header = $ ( '.header' , { } ,
$ ( 'h1.product-name.caption' , { } , this . productService . nameLong ) ,
$ ( 'p.subtitle.description' , { } , localize ( { key : 'gettingStarted.editingEvolved' , comment : [ 'Shown as subtitle on the Welcome page.' ] } , "Editing evolved" ) )
2021-02-11 05:57:10 +01:00
) ;
2020-12-15 04:30:43 +01:00
2020-11-25 23:05:49 +01:00
2021-04-09 20:46:04 +02:00
const leftColumn = $ ( '.categories-column.categories-column-left' , { } , ) ;
const rightColumn = $ ( '.categories-column.categories-column-right' , { } , ) ;
2020-11-30 23:53:00 +01:00
2021-04-09 20:46:04 +02:00
const startList = this . buildStartList ( ) ;
const recentList = this . buildRecentlyOpenedList ( ) ;
const gettingStartedList = this . buildGettingStartedWalkthroughsList ( ) ;
2021-07-18 05:34:11 +02:00
const footer = $ ( '.footer' , $ ( 'p.showOnStartup' , { } , showOnStartupCheckbox , $ ( 'label.caption' , { for : 'showOnStartup' } , localize ( 'welcomePage.showOnStartup' , "Show welcome page on startup" ) ) ) ) ;
2021-06-16 02:41:53 +02:00
2021-04-09 20:46:04 +02:00
const layoutLists = ( ) = > {
if ( gettingStartedList . itemCount ) {
2021-07-18 05:34:11 +02:00
this . container . classList . remove ( 'noWalkthroughs' ) ;
2021-04-09 20:46:04 +02:00
reset ( leftColumn , startList . getDomElement ( ) , recentList . getDomElement ( ) ) ;
reset ( rightColumn , gettingStartedList . getDomElement ( ) ) ;
recentList . setLimit ( 5 ) ;
}
else {
2021-07-18 05:34:11 +02:00
this . container . classList . add ( 'noWalkthroughs' ) ;
2021-04-09 20:46:04 +02:00
reset ( leftColumn , startList . getDomElement ( ) ) ;
reset ( rightColumn , recentList . getDomElement ( ) ) ;
recentList . setLimit ( 10 ) ;
}
2021-04-10 04:35:34 +02:00
setTimeout ( ( ) = > this . categoriesPageScrollbar ? . scanDomNode ( ) , 50 ) ;
2021-04-09 20:46:04 +02:00
} ;
gettingStartedList . onDidChange ( layoutLists ) ;
layoutLists ( ) ;
reset ( this . categoriesSlide , $ ( '.gettingStartedCategoriesContainer' , { } , header , leftColumn , rightColumn , footer , ) ) ;
2021-04-10 00:32:12 +02:00
this . categoriesPageScrollbar ? . scanDomNode ( ) ;
2021-04-09 20:46:04 +02:00
this . updateCategoryProgress ( ) ;
this . registerDispatchListeners ( ) ;
2020-11-30 23:53:00 +01:00
if ( this . editorInput . selectedCategory ) {
2021-08-16 11:15:27 +02:00
this . currentWalkthrough = this . gettingStartedCategories . find ( category = > category . id === this . editorInput . selectedCategory ) ;
if ( ! this . currentWalkthrough ) {
this . container . classList . add ( 'loading' ) ;
await this . gettingStartedService . installedExtensionsRegistered ;
this . container . classList . remove ( 'loading' ) ;
this . gettingStartedCategories = this . gettingStartedService . getWalkthroughs ( ) ;
}
this . currentWalkthrough = this . gettingStartedCategories . find ( category = > category . id === this . editorInput . selectedCategory ) ;
if ( ! this . currentWalkthrough ) {
2021-03-20 02:54:24 +01:00
console . error ( 'Could not restore to category ' + this . editorInput . selectedCategory + ' as it was not found' ) ;
this . editorInput . selectedCategory = undefined ;
2021-04-24 01:34:52 +02:00
this . editorInput . selectedStep = undefined ;
2021-03-20 02:54:24 +01:00
} else {
2021-04-24 01:34:52 +02:00
this . buildCategorySlide ( this . editorInput . selectedCategory , this . editorInput . selectedStep ) ;
2021-03-20 02:54:24 +01:00
this . setSlide ( 'details' ) ;
return ;
2020-11-30 23:53:00 +01:00
}
}
2021-02-24 21:06:15 +01:00
2021-08-16 11:15:27 +02:00
const someStepsComplete = this . gettingStartedCategories . some ( category = > category . steps . find ( s = > s . done ) ) ;
2021-04-24 01:34:52 +02:00
if ( ! someStepsComplete && ! this . hasScrolledToFirstCategory ) {
2021-03-26 02:27:04 +01:00
2021-06-08 21:57:59 +02:00
const firstSessionDateString = this . storageService . get ( firstSessionDateStorageKey , StorageScope . GLOBAL ) || new Date ( ) . toUTCString ( ) ;
const daysSinceFirstSession = ( ( + new Date ( ) ) - ( + new Date ( firstSessionDateString ) ) ) / 1000 / 60 / 60 / 24 ;
const fistContentBehaviour = daysSinceFirstSession < 1 ? 'openToFirstCategory' : 'index' ;
if ( fistContentBehaviour === 'openToFirstCategory' ) {
2021-08-16 11:15:27 +02:00
const first = this . gettingStartedCategories [ 0 ] ;
2021-06-08 21:57:59 +02:00
this . hasScrolledToFirstCategory = true ;
if ( first ) {
2021-08-16 11:15:27 +02:00
this . currentWalkthrough = first ;
this . editorInput . selectedCategory = this . currentWalkthrough ? . id ;
2021-08-18 02:47:45 +02:00
this . buildCategorySlide ( this . editorInput . selectedCategory , undefined ) ;
2021-06-08 21:57:59 +02:00
this . setSlide ( 'details' ) ;
return ;
2021-03-20 02:54:24 +01:00
}
2021-02-24 21:06:15 +01:00
}
}
this . setSlide ( 'categories' ) ;
2020-11-25 23:05:49 +01:00
}
2021-08-16 11:15:27 +02:00
private buildRecentlyOpenedList ( ) : GettingStartedIndexList < RecentEntry > {
const renderRecent = ( recent : RecentEntry ) = > {
2021-03-11 07:11:21 +01:00
let fullPath : string ;
let windowOpenable : IWindowOpenable ;
if ( isRecentFolder ( recent ) ) {
windowOpenable = { folderUri : recent.folderUri } ;
fullPath = recent . label || this . labelService . getWorkspaceLabel ( recent . folderUri , { verbose : true } ) ;
} else {
fullPath = recent . label || this . labelService . getWorkspaceLabel ( recent . workspace , { verbose : true } ) ;
windowOpenable = { workspaceUri : recent.workspace.configPath } ;
}
const { name , parentPath } = splitName ( fullPath ) ;
2021-03-25 01:51:57 +01:00
const li = $ ( 'li' ) ;
const link = $ ( 'button.button-link' ) ;
link . innerText = name ;
link . title = fullPath ;
link . setAttribute ( 'aria-label' , localize ( 'welcomePage.openFolderWithPath' , "Open folder {0} with path {1}" , name , parentPath ) ) ;
link . addEventListener ( 'click' , e = > {
2021-04-09 20:46:04 +02:00
this . telemetryService . publicLog2 < GettingStartedActionEvent , GettingStartedActionClassification > ( 'gettingStarted.ActionExecuted' , { command : 'openRecent' , argument : undefined } ) ;
2021-03-11 07:11:21 +01:00
this . hostService . openWindow ( [ windowOpenable ] , { forceNewWindow : e.ctrlKey || e . metaKey , remoteAuthority : recent.remoteAuthority } ) ;
e . preventDefault ( ) ;
e . stopPropagation ( ) ;
} ) ;
2021-03-25 01:51:57 +01:00
li . appendChild ( link ) ;
2021-03-11 07:11:21 +01:00
2021-03-25 01:51:57 +01:00
const span = $ ( 'span' ) ;
2021-03-11 07:11:21 +01:00
span . classList . add ( 'path' ) ;
span . classList . add ( 'detail' ) ;
span . innerText = parentPath ;
span . title = fullPath ;
li . appendChild ( span ) ;
return li ;
} ;
2021-04-09 20:46:04 +02:00
if ( this . recentlyOpenedList ) { this . recentlyOpenedList . dispose ( ) ; }
const recentlyOpenedList = this . recentlyOpenedList = new GettingStartedIndexList (
2021-08-16 11:15:27 +02:00
{
title : localize ( 'recent' , "Recent" ) ,
klass : 'recently-opened' ,
limit : 5 ,
empty : $ ( '.empty-recent' , { } , 'You have no recent folders,' , $ ( 'button.button-link' , { 'x-dispatch' : 'openFolder' } , 'open a folder' ) , 'to start.' ) ,
more : $ ( '.more' , { } ,
$ ( 'button.button-link' ,
{
'x-dispatch' : 'showMoreRecents' ,
title : localize ( 'show more recents' , "Show All Recent Folders {0}" , this . getKeybindingLabel ( 'workbench.action.openRecent' ) )
} , 'More...' ) ) ,
renderElement : renderRecent ,
contextService : this.contextService
} ) ;
2021-04-09 20:46:04 +02:00
recentlyOpenedList . onDidChange ( ( ) = > this . registerDispatchListeners ( ) ) ;
2021-03-11 07:11:21 +01:00
this . recentlyOpened . then ( ( { workspaces } ) = > {
// Filter out the current workspace
2021-08-16 11:15:27 +02:00
const workspacesWithID = workspaces
. filter ( recent = > ! this . workspaceContextService . isCurrentWorkspace ( isRecentWorkspace ( recent ) ? recent.workspace : recent.folderUri ) )
. map ( recent = > ( { . . . recent , id : isRecentWorkspace ( recent ) ? recent.workspace.id : recent.folderUri.toString ( ) } ) ) ;
const updateEntries = ( ) = > { recentlyOpenedList . setEntries ( workspacesWithID ) ; } ;
2021-03-11 07:11:21 +01:00
updateEntries ( ) ;
2021-08-16 11:15:27 +02:00
2021-04-09 20:46:04 +02:00
recentlyOpenedList . register ( this . labelService . onDidChangeFormatters ( ( ) = > updateEntries ( ) ) ) ;
} ) . catch ( onUnexpectedError ) ;
2021-03-11 07:11:21 +01:00
2021-04-09 20:46:04 +02:00
return recentlyOpenedList ;
2021-03-11 07:11:21 +01:00
}
2021-08-16 11:15:27 +02:00
private buildStartList ( ) : GettingStartedIndexList < IWelcomePageStartEntry > {
const renderStartEntry = ( entry : IWelcomePageStartEntry ) : HTMLElement = >
2021-08-25 20:40:46 +02:00
$ ( 'li' ,
{ } , $ ( 'button.button-link' ,
{
'x-dispatch' : 'selectStartEntry:' + entry . id ,
title : entry.description + ' ' + this . getKeybindingLabel ( entry . command ) ,
} ,
this . iconWidgetFor ( entry ) ,
$ ( 'span' , { } , entry . title ) ) ) ;
2021-04-09 20:46:04 +02:00
if ( this . startList ) { this . startList . dispose ( ) ; }
const startList = this . startList = new GettingStartedIndexList (
2021-08-16 11:15:27 +02:00
{
title : localize ( 'start' , "Start" ) ,
klass : 'start-container' ,
limit : 10 ,
renderElement : renderStartEntry ,
2021-08-18 04:03:16 +02:00
rankElement : e = > - e . order ,
2021-08-16 11:15:27 +02:00
contextService : this.contextService
} ) ;
2021-07-18 05:34:11 +02:00
2021-08-16 11:15:27 +02:00
startList . setEntries ( parsedStartEntries ) ;
2021-04-09 20:46:04 +02:00
startList . onDidChange ( ( ) = > this . registerDispatchListeners ( ) ) ;
return startList ;
}
2021-03-11 07:11:21 +01:00
2021-08-16 11:15:27 +02:00
private buildGettingStartedWalkthroughsList ( ) : GettingStartedIndexList < IResolvedWalkthrough > {
2021-03-11 07:11:21 +01:00
2021-08-16 11:15:27 +02:00
const renderGetttingStaredWalkthrough = ( category : IResolvedWalkthrough ) : HTMLElement = > {
2021-04-09 20:46:04 +02:00
2021-08-16 11:15:27 +02:00
const renderNewBadge = ( category . newItems || category . newEntry ) && ! category . isFeatured ;
2021-06-25 21:01:04 +02:00
const newBadge = $ ( '.new-badge' , { } ) ;
2021-08-16 11:15:27 +02:00
if ( category . newEntry ) {
2021-06-25 21:01:04 +02:00
reset ( newBadge , $ ( '.new-category' , { } , localize ( 'new' , "New" ) ) ) ;
2021-08-16 11:15:27 +02:00
} else if ( category . newItems ) {
2021-06-25 21:01:04 +02:00
reset ( newBadge , $ ( '.new-items' , { } , localize ( 'newItems' , "New Items" ) ) ) ;
}
const featuredBadge = $ ( '.featured-badge' , { } ) ;
const descriptionContent = $ ( '.description-content' , { } , ) ;
2021-08-16 11:15:27 +02:00
if ( category . isFeatured ) {
2021-06-25 21:01:04 +02:00
reset ( featuredBadge , $ ( '.featured' , { } , $ ( 'span.featured-icon.codicon.codicon-star-empty' ) ) ) ;
reset ( descriptionContent , category . description ) ;
}
2021-08-16 11:15:27 +02:00
return $ ( 'button.getting-started-category' + ( category . isFeatured ? '.featured' : '' ) ,
2021-04-09 20:46:04 +02:00
{
'x-dispatch' : 'selectCategory:' + category . id ,
'role' : 'listitem' ,
'title' : category . description
} ,
2021-06-25 21:01:04 +02:00
featuredBadge ,
$ ( '.main-content' , { } ,
this . iconWidgetFor ( category ) ,
$ ( 'h3.category-title.max-lines-3' , { 'x-category-title-for' : category . id } , category . title , ) ,
2021-07-01 15:45:08 +02:00
renderNewBadge ? newBadge : $ ( '.no-badge' ) ,
2021-06-25 21:01:04 +02:00
$ ( 'a.codicon.codicon-close.hide-category-button' , {
'x-dispatch' : 'hideCategory:' + category . id ,
'title' : localize ( 'close' , "Hide" ) ,
} ) ,
) ,
descriptionContent ,
2021-04-09 20:46:04 +02:00
$ ( '.category-progress' , { 'x-data-category-id' : category . id , } ,
$ ( '.progress-bar-outer' , { 'role' : 'progressbar' } ,
$ ( '.progress-bar-inner' ) ) ) ) ;
2021-03-11 07:11:21 +01:00
} ;
2021-04-09 20:46:04 +02:00
if ( this . gettingStartedList ) { this . gettingStartedList . dispose ( ) ; }
2021-03-11 07:11:21 +01:00
2021-08-16 11:15:27 +02:00
const rankWalkthrough = ( e : IResolvedWalkthrough ) = > {
let rank : number | null = e . order ;
2021-08-18 03:51:23 +02:00
if ( e . isFeatured ) { rank += 7 ; }
2021-08-16 11:15:27 +02:00
if ( e . newEntry ) { rank += 3 ; }
if ( e . newItems ) { rank += 2 ; }
if ( e . recencyBonus ) { rank += 4 * e . recencyBonus ; }
if ( this . getHiddenCategories ( ) . has ( e . id ) ) { rank = null ; }
return rank ;
} ;
2021-04-09 20:46:04 +02:00
const gettingStartedList = this . gettingStartedList = new GettingStartedIndexList (
2021-08-16 11:15:27 +02:00
{
title : localize ( 'walkthroughs' , "Walkthroughs" ) ,
klass : 'getting-started' ,
limit : 5 ,
empty : undefined , more : undefined ,
footer : $ ( 'span.button-link.see-all-walkthroughs' , { 'x-dispatch' : 'seeAllWalkthroughs' } , localize ( 'showAll' , "More..." ) ) ,
renderElement : renderGetttingStaredWalkthrough ,
rankElement : rankWalkthrough ,
contextService : this.contextService ,
} ) ;
2021-03-11 07:11:21 +01:00
2021-04-09 20:46:04 +02:00
gettingStartedList . onDidChange ( ( ) = > {
2021-06-22 02:41:10 +02:00
const hidden = this . getHiddenCategories ( ) ;
2021-08-16 11:15:27 +02:00
const someWalkthroughsHidden = hidden . size || gettingStartedList . itemCount < this . gettingStartedCategories . filter ( c = > this . contextService . contextMatchesRules ( c . when ) ) . length ;
2021-06-22 02:41:10 +02:00
this . container . classList . toggle ( 'someWalkthroughsHidden' , ! ! someWalkthroughsHidden ) ;
2021-04-09 20:46:04 +02:00
this . registerDispatchListeners ( ) ;
2021-08-18 04:03:16 +02:00
allWalkthroughsHiddenContext . bindTo ( this . contextService ) . set ( gettingStartedList . itemCount === 0 ) ;
2021-04-09 20:46:04 +02:00
this . updateCategoryProgress ( ) ;
} ) ;
2021-08-18 04:03:16 +02:00
2021-04-09 20:46:04 +02:00
gettingStartedList . setEntries ( this . gettingStartedCategories ) ;
2021-08-18 04:03:16 +02:00
allWalkthroughsHiddenContext . bindTo ( this . contextService ) . set ( gettingStartedList . itemCount === 0 ) ;
2021-04-09 20:46:04 +02:00
return gettingStartedList ;
}
2021-03-11 07:11:21 +01:00
2021-03-17 08:03:18 +01:00
layout ( size : Dimension ) {
2021-04-10 04:35:34 +02:00
this . detailsScrollbar ? . scanDomNode ( ) ;
2021-04-09 20:46:04 +02:00
2021-04-10 00:32:12 +02:00
this . categoriesPageScrollbar ? . scanDomNode ( ) ;
this . detailsPageScrollbar ? . scanDomNode ( ) ;
2021-04-09 20:46:04 +02:00
this . startList ? . layout ( size ) ;
this . gettingStartedList ? . layout ( size ) ;
this . recentlyOpenedList ? . layout ( size ) ;
2021-03-17 08:27:26 +01:00
2021-05-29 19:18:09 +02:00
this . layoutMarkdown ? . ( ) ;
2021-04-09 20:46:04 +02:00
this . container . classList [ size . height <= 600 ? 'add' : 'remove' ] ( 'height-constrained' ) ;
this . container . classList [ size . width <= 400 ? 'add' : 'remove' ] ( 'width-constrained' ) ;
2021-04-10 04:35:34 +02:00
this . container . classList [ size . width <= 800 ? 'add' : 'remove' ] ( 'width-semi-constrained' ) ;
2020-12-15 04:30:43 +01:00
}
2020-11-25 23:05:49 +01:00
private updateCategoryProgress() {
document . querySelectorAll ( '.category-progress' ) . forEach ( element = > {
const categoryID = element . getAttribute ( 'x-data-category-id' ) ;
const category = this . gettingStartedCategories . find ( category = > category . id === categoryID ) ;
2020-12-16 01:46:26 +01:00
if ( ! category ) { throw Error ( 'Could not find category with ID ' + categoryID ) ; }
2021-08-16 11:15:27 +02:00
const stats = this . getWalkthroughCompletionStats ( category ) ;
2020-11-25 23:05:49 +01:00
2021-01-13 20:42:38 +01:00
const bar = assertIsDefined ( element . querySelector ( '.progress-bar-inner' ) ) as HTMLDivElement ;
2021-02-12 20:37:20 +01:00
bar . setAttribute ( 'aria-valuemin' , '0' ) ;
2021-08-16 11:15:27 +02:00
bar . setAttribute ( 'aria-valuenow' , '' + stats . stepsComplete ) ;
bar . setAttribute ( 'aria-valuemax' , '' + stats . stepsTotal ) ;
const progress = ( stats . stepsComplete / stats . stepsTotal ) * 100 ;
2021-03-13 06:09:13 +01:00
bar . style . width = ` ${ progress } % ` ;
2021-01-13 20:42:38 +01:00
2021-05-21 01:15:41 +02:00
2021-08-16 11:15:27 +02:00
( element . parentElement as HTMLElement ) . classList [ stats . stepsComplete === 0 ? 'add' : 'remove' ] ( 'no-progress' ) ;
2021-05-21 01:15:41 +02:00
2021-08-16 11:15:27 +02:00
if ( stats . stepsTotal === stats . stepsComplete ) {
bar . title = localize ( 'gettingStarted.allStepsComplete' , "All {0} steps complete!" , stats . stepsComplete ) ;
2020-11-25 23:05:49 +01:00
}
else {
2021-08-16 11:15:27 +02:00
bar . title = localize ( 'gettingStarted.someStepsComplete' , "{0} of {1} steps complete" , stats . stepsTotal , stats . stepsComplete ) ;
2020-11-25 23:05:49 +01:00
}
} ) ;
}
2021-05-26 02:22:09 +02:00
private async scrollToCategory ( categoryID : string , stepId? : string ) {
2020-11-25 23:05:49 +01:00
this . inProgressScroll = this . inProgressScroll . then ( async ( ) = > {
2021-04-24 01:34:52 +02:00
reset ( this . stepsContent ) ;
2020-11-30 23:53:00 +01:00
this . editorInput . selectedCategory = categoryID ;
2021-05-26 02:22:09 +02:00
this . editorInput . selectedStep = stepId ;
2021-08-16 11:15:27 +02:00
this . currentWalkthrough = this . gettingStartedCategories . find ( category = > category . id === categoryID ) ;
2021-02-11 05:57:10 +01:00
this . buildCategorySlide ( categoryID ) ;
this . setSlide ( 'details' ) ;
2020-11-25 23:05:49 +01:00
} ) ;
}
2021-08-16 11:15:27 +02:00
private iconWidgetFor ( category : IResolvedWalkthrough | { icon : { type : 'icon' , icon : ThemeIcon } } ) {
2021-06-25 21:01:04 +02:00
const widget = category . icon . type === 'icon' ? $ ( ThemeIcon . asCSSSelector ( category . icon . icon ) ) : $ ( 'img.category-icon' , { src : category.icon.path } ) ;
widget . classList . add ( 'icon-widget' ) ;
return widget ;
2021-02-19 00:18:00 +01:00
}
2021-04-24 01:34:52 +02:00
private buildStepMarkdownDescription ( container : HTMLElement , text : LinkedText [ ] ) {
2021-04-17 02:22:23 +02:00
while ( container . firstChild ) { container . removeChild ( container . firstChild ) ; }
2021-04-14 03:31:44 +02:00
2021-04-17 02:22:23 +02:00
for ( const linkedText of text ) {
if ( linkedText . nodes . length === 1 && typeof linkedText . nodes [ 0 ] !== 'string' ) {
const node = linkedText . nodes [ 0 ] ;
const buttonContainer = append ( container , $ ( '.button-container' ) ) ;
const button = new Button ( buttonContainer , { title : node.title , supportIcons : true } ) ;
2021-04-14 03:31:44 +02:00
2021-04-17 02:22:23 +02:00
const isCommand = node . href . startsWith ( 'command:' ) ;
const toSide = node . href . startsWith ( 'command:toSide:' ) ;
const command = node . href . replace ( /command:(toSide:)?/ , 'command:' ) ;
2021-04-14 03:31:44 +02:00
2021-04-17 02:22:23 +02:00
button . label = node . label ;
button . onDidClick ( async e = > {
e . stopPropagation ( ) ;
e . preventDefault ( ) ;
2021-04-14 03:31:44 +02:00
2021-04-24 01:34:52 +02:00
this . telemetryService . publicLog2 < GettingStartedActionEvent , GettingStartedActionClassification > ( 'gettingStarted.ActionExecuted' , { command : 'runStepAction' , argument : node.href } ) ;
2021-04-14 03:31:44 +02:00
2021-04-17 02:22:23 +02:00
const fullSize = this . groupsService . contentDimension ;
2021-04-14 03:31:44 +02:00
2021-04-17 02:22:23 +02:00
if ( toSide && fullSize . width > 700 ) {
if ( this . groupsService . count === 1 ) {
this . groupsService . addGroup ( this . groupsService . groups [ 0 ] , GroupDirection . LEFT , { activate : true } ) ;
2021-04-14 03:31:44 +02:00
2021-04-17 02:22:23 +02:00
let gettingStartedSize : number ;
if ( fullSize . width > 1600 ) {
gettingStartedSize = 800 ;
} else if ( fullSize . width > 800 ) {
gettingStartedSize = 400 ;
} else {
gettingStartedSize = 350 ;
2021-04-14 03:31:44 +02:00
}
2021-04-17 02:22:23 +02:00
const gettingStartedGroup = this . groupsService . getGroups ( GroupsOrder . MOST_RECENTLY_ACTIVE ) . find ( group = > ( group . activeEditor instanceof GettingStartedInput ) ) ;
this . groupsService . setSize ( assertIsDefined ( gettingStartedGroup ) , { width : gettingStartedSize , height : fullSize.height } ) ;
2021-04-14 03:31:44 +02:00
}
2021-04-17 02:22:23 +02:00
const nonGettingStartedGroup = this . groupsService . getGroups ( GroupsOrder . MOST_RECENTLY_ACTIVE ) . find ( group = > ! ( group . activeEditor instanceof GettingStartedInput ) ) ;
if ( nonGettingStartedGroup ) {
this . groupsService . activateGroup ( nonGettingStartedGroup ) ;
2021-04-19 21:15:19 +02:00
nonGettingStartedGroup . focus ( ) ;
2021-04-14 03:31:44 +02:00
}
}
2021-04-17 02:22:23 +02:00
this . openerService . open ( command , { allowCommands : true } ) ;
2021-04-14 03:31:44 +02:00
2021-06-04 19:25:11 +02:00
if ( ! isCommand && ( node . href . startsWith ( 'https://' ) || node . href . startsWith ( 'http://' ) ) ) {
2021-05-13 02:48:51 +02:00
this . gettingStartedService . progressByEvent ( 'onLink:' + node . href ) ;
}
2021-04-17 02:22:23 +02:00
} , null , this . detailsPageDisposables ) ;
if ( isCommand ) {
const keybindingLabel = this . getKeybindingLabel ( command ) ;
if ( keybindingLabel ) {
container . appendChild ( $ ( 'span.shortcut-message' , { } , 'Tip: Use keyboard shortcut ' , $ ( 'span.keybinding' , { } , keybindingLabel ) ) ) ;
}
}
this . detailsPageDisposables . add ( button ) ;
this . detailsPageDisposables . add ( attachButtonStyler ( button , this . themeService ) ) ;
} else {
const p = append ( container , $ ( 'p' ) ) ;
for ( const node of linkedText . nodes ) {
if ( typeof node === 'string' ) {
2021-08-19 04:03:33 +02:00
append ( p , renderFormattedText ( node , { inline : true , renderCodeSegments : true } ) ) ;
2021-04-17 02:22:23 +02:00
} else {
2021-05-13 06:43:32 +02:00
const link = this . instantiationService . createInstance ( Link , node , { } ) ;
2021-04-17 02:22:23 +02:00
append ( p , link . el ) ;
this . detailsPageDisposables . add ( link ) ;
2021-04-14 03:31:44 +02:00
}
}
}
2021-04-17 02:22:23 +02:00
}
return container ;
}
2021-04-24 00:01:54 +02:00
override clearInput() {
2021-04-24 01:34:52 +02:00
this . stepDisposables . clear ( ) ;
2021-04-24 00:01:54 +02:00
super . clearInput ( ) ;
}
2021-04-24 01:34:52 +02:00
private buildCategorySlide ( categoryID : string , selectedStep? : string ) {
2021-04-17 02:22:23 +02:00
if ( this . detailsScrollbar ) { this . detailsScrollbar . dispose ( ) ; }
2021-07-18 20:31:47 +02:00
this . extensionService . whenInstalledExtensionsRegistered ( ) . then ( ( ) = > {
// Remove internal extension id specifier from exposed id's
this . extensionService . activateByEvent ( ` onWalkthrough: ${ categoryID . replace ( /[^#]+#/ , '' ) } ` ) ;
} ) ;
2021-04-17 02:22:23 +02:00
this . detailsPageDisposables . clear ( ) ;
2021-04-14 03:31:44 +02:00
2020-11-30 23:53:00 +01:00
const category = this . gettingStartedCategories . find ( category = > category . id === categoryID ) ;
if ( ! category ) { throw Error ( 'could not find category with ID ' + categoryID ) ; }
2021-04-10 04:35:34 +02:00
const categoryDescriptorComponent =
2020-11-30 23:53:00 +01:00
$ ( '.getting-started-category' ,
{ } ,
2021-02-19 00:18:00 +01:00
this . iconWidgetFor ( category ) ,
2020-11-30 23:53:00 +01:00
$ ( '.category-description-container' , { } ,
2021-04-24 02:39:02 +02:00
$ ( 'h2.category-title.max-lines-3' , { 'x-category-title-for' : category . id } , category . title ) ,
$ ( '.category-description.description.max-lines-3' , { 'x-category-description-for' : category . id } , category . description ) ) ) ;
2020-11-30 23:53:00 +01:00
2021-08-25 02:18:52 +02:00
const stepListContainer = $ ( '.step-list-container' ) ;
2021-08-25 20:29:45 +02:00
this . detailsPageDisposables . add ( addDisposableListener ( stepListContainer , 'keydown' , ( e ) = > {
const event = new StandardKeyboardEvent ( e ) ;
const currentStepIndex = ( ) = >
category . steps . findIndex ( e = > e . id === this . editorInput . selectedStep ) ;
if ( event . keyCode === KeyCode . UpArrow ) {
const toExpand = category . steps . filter ( ( step , index ) = > index < currentStepIndex ( ) && this . contextService . contextMatchesRules ( step . when ) ) ;
if ( toExpand . length ) {
this . selectStep ( toExpand [ toExpand . length - 1 ] . id , false , false ) ;
}
}
if ( event . keyCode === KeyCode . DownArrow ) {
const toExpand = category . steps . find ( ( step , index ) = > index > currentStepIndex ( ) && this . contextService . contextMatchesRules ( step . when ) ) ;
if ( toExpand ) {
this . selectStep ( toExpand . id , false , false ) ;
}
}
} ) ) ;
2021-08-25 02:18:52 +02:00
let renderedSteps : IResolvedWalkthroughStep [ ] | undefined = undefined ;
const contextKeysToWatch = new Set ( category . steps . flatMap ( step = > step . when . keys ( ) ) ) ;
const buildStepList = ( ) = > {
const toRender = category . steps
. filter ( step = > this . contextService . contextMatchesRules ( step . when ) ) ;
if ( equals ( renderedSteps , toRender , ( a , b ) = > a . id === b . id ) ) {
return ;
}
renderedSteps = toRender ;
reset ( stepListContainer , . . . renderedSteps
. map ( step = > {
2021-08-16 11:15:27 +02:00
const codicon = $ ( '.codicon' + ( step . done ? '.complete' + ThemeIcon . asCSSSelector ( gettingStartedCheckedCodicon ) : ThemeIcon . asCSSSelector ( gettingStartedUncheckedCodicon ) ) ,
{
'data-done-step-id' : step . id ,
'x-dispatch' : 'toggleStepCompletion:' + step . id ,
} ) ;
2021-04-17 02:22:23 +02:00
2021-08-16 11:15:27 +02:00
const container = $ ( '.step-description-container' , { 'x-step-description-for' : step . id } ) ;
this . buildStepMarkdownDescription ( container , step . description ) ;
2021-03-17 08:54:55 +01:00
2021-08-16 11:15:27 +02:00
const stepDescription = $ ( '.step-container' , { } ,
$ ( 'h3.step-title.max-lines-3' , { 'x-step-title-for' : step . id } , step . title ) ,
container ,
2021-04-24 00:01:54 +02:00
) ;
2021-08-16 11:15:27 +02:00
if ( step . media . type === 'image' ) {
stepDescription . appendChild (
$ ( '.image-description' , { 'aria-label' : localize ( 'imageShowing' , "Image showing {0}" , step . media . altText ) } ) ,
) ;
}
return $ ( 'button.getting-started-step' ,
{
'x-dispatch' : 'selectTask:' + step . id ,
'data-step-id' : step . id ,
'aria-expanded' : 'false' ,
'aria-checked' : '' + step . done ,
'role' : 'listitem' ,
} ,
codicon ,
stepDescription ) ;
2021-08-25 02:18:52 +02:00
} ) ) ;
} ;
buildStepList ( ) ;
this . detailsPageDisposables . add ( this . contextService . onDidChangeContext ( e = > {
if ( e . affectsSome ( contextKeysToWatch ) ) {
buildStepList ( ) ;
this . registerDispatchListeners ( ) ;
this . selectStep ( this . editorInput . selectedStep , false , true ) ;
}
} ) ) ;
2020-11-30 23:53:00 +01:00
2021-08-16 11:15:27 +02:00
const showNextCategory = this . gettingStartedCategories . find ( _category = > _category . id === category . next ) ;
2021-05-22 03:27:34 +02:00
const stepsContainer = $ (
'.getting-started-detail-container' , { 'role' : 'list' } ,
2021-08-25 02:18:52 +02:00
stepListContainer ,
2021-05-22 03:27:34 +02:00
$ ( '.done-next-container' , { } ,
$ ( 'button.button-link.all-done' , { 'x-dispatch' : 'allDone' } , $ ( 'span.codicon.codicon-check-all' ) , localize ( 'allDone' , "Mark Done" ) ) ,
. . . ( showNextCategory
? [ $ ( 'button.button-link.next' , { 'x-dispatch' : 'nextSection' } , localize ( 'nextOne' , "Next Section" ) , $ ( 'span.codicon.codicon-arrow-small-right' ) ) ]
: [ ] ) ,
)
) ;
2021-04-24 01:34:52 +02:00
this . detailsScrollbar = this . _register ( new DomScrollableElement ( stepsContainer , { className : 'steps-container' } ) ) ;
const stepListComponent = this . detailsScrollbar . getDomNode ( ) ;
2021-04-10 04:35:34 +02:00
2021-08-18 02:47:45 +02:00
const categoryFooter = $ ( '.getting-started-footer' ) ;
if ( this . editorInput . showTelemetryNotice && this . configurationService . getValue ( 'telemetry.enableTelemetry' ) && product . enableTelemetry ) {
const mdRenderer = this . _register ( this . instantiationService . createInstance ( MarkdownRenderer , { } ) ) ;
const privacyStatementCopy = localize ( 'privacy statement' , "privacy statement" ) ;
const privacyStatementButton = ` [ ${ privacyStatementCopy } ](command:workbench.action.openPrivacyStatementUrl) ` ;
const optOutCopy = localize ( 'optOut' , "opt out" ) ;
const optOutButton = ` [ ${ optOutCopy } ](command:settings.filterByTelemetry) ` ;
const text = localize ( { key : 'footer' , comment : [ 'fist substitution is "vs code", second is "privacy statement", third is "opt out".' ] } ,
"{0} collects usage data. Read our {1} and learn how to {2}." , product . nameShort , privacyStatementButton , optOutButton ) ;
categoryFooter . append ( mdRenderer . render ( { value : text , isTrusted : true } ) . element ) ;
}
reset ( this . stepsContent , categoryDescriptorComponent , stepListComponent , this . stepMediaComponent , categoryFooter ) ;
2020-11-30 23:53:00 +01:00
2021-08-16 11:15:27 +02:00
const toExpand = category . steps . find ( step = > this . contextService . contextMatchesRules ( step . when ) && ! step . done ) ? ? category . steps [ 0 ] ;
2021-05-08 18:58:56 +02:00
this . selectStep ( selectedStep ? ? toExpand . id , ! selectedStep , true ) ;
2021-04-10 04:35:34 +02:00
2020-12-16 01:46:26 +01:00
this . detailsScrollbar . scanDomNode ( ) ;
2021-04-10 00:32:12 +02:00
this . detailsPageScrollbar ? . scanDomNode ( ) ;
2021-02-11 05:57:10 +01:00
this . registerDispatchListeners ( ) ;
2020-11-30 23:53:00 +01:00
}
2020-11-25 23:05:49 +01:00
private getKeybindingLabel ( command : string ) {
2021-04-30 04:44:44 +02:00
command = command . replace ( /^command:/ , '' ) ;
2021-04-09 20:46:04 +02:00
const label = this . keybindingService . lookupKeybinding ( command ) ? . getLabel ( ) ;
if ( ! label ) { return '' ; }
else {
return ` ( ${ label } ) ` ;
}
2020-11-25 23:05:49 +01:00
}
2021-02-11 05:57:10 +01:00
private async scrollPrev() {
2020-11-25 23:05:49 +01:00
this . inProgressScroll = this . inProgressScroll . then ( async ( ) = > {
2021-08-16 11:15:27 +02:00
this . currentWalkthrough = undefined ;
2020-11-30 23:53:00 +01:00
this . editorInput . selectedCategory = undefined ;
2021-04-24 01:34:52 +02:00
this . editorInput . selectedStep = undefined ;
2021-08-18 02:47:45 +02:00
this . editorInput . showTelemetryNotice = false ;
2021-04-24 01:34:52 +02:00
this . selectStep ( undefined ) ;
2021-02-11 05:57:10 +01:00
this . setSlide ( 'categories' ) ;
2021-06-03 02:43:34 +02:00
this . container . focus ( ) ;
2020-11-25 23:05:49 +01:00
} ) ;
}
2021-02-04 00:45:35 +01:00
2021-02-11 05:57:10 +01:00
private runSkip() {
this . commandService . executeCommand ( 'workbench.action.closeActiveEditor' ) ;
}
escape() {
if ( this . editorInput . selectedCategory ) {
this . scrollPrev ( ) ;
} else {
this . runSkip ( ) ;
}
}
private setSlide ( toEnable : 'details' | 'categories' ) {
const slideManager = assertIsDefined ( this . container . querySelector ( '.gettingStarted' ) ) ;
2021-02-04 00:45:35 +01:00
if ( toEnable === 'categories' ) {
2021-02-11 05:57:10 +01:00
slideManager . classList . remove ( 'showDetails' ) ;
slideManager . classList . add ( 'showCategories' ) ;
this . container . querySelector ( '.gettingStartedSlideDetails' ) ! . querySelectorAll ( 'button' ) . forEach ( button = > button . disabled = true ) ;
2021-04-09 20:46:04 +02:00
this . container . querySelector ( '.gettingStartedSlideCategories' ) ! . querySelectorAll ( 'button' ) . forEach ( button = > button . disabled = false ) ;
this . container . querySelector ( '.gettingStartedSlideCategories' ) ! . querySelectorAll ( 'input' ) . forEach ( button = > button . disabled = false ) ;
2021-02-04 00:45:35 +01:00
} else {
2021-02-11 05:57:10 +01:00
slideManager . classList . add ( 'showDetails' ) ;
slideManager . classList . remove ( 'showCategories' ) ;
this . container . querySelector ( '.gettingStartedSlideDetails' ) ! . querySelectorAll ( 'button' ) . forEach ( button = > button . disabled = false ) ;
2021-04-09 20:46:04 +02:00
this . container . querySelector ( '.gettingStartedSlideCategories' ) ! . querySelectorAll ( 'button' ) . forEach ( button = > button . disabled = true ) ;
this . container . querySelector ( '.gettingStartedSlideCategories' ) ! . querySelectorAll ( 'input' ) . forEach ( button = > button . disabled = true ) ;
2021-02-04 00:45:35 +01:00
}
}
2021-06-03 02:43:34 +02:00
override focus() {
this . container . focus ( ) ;
}
2020-11-25 23:05:49 +01:00
}
2021-07-07 14:11:22 +02:00
export class GettingStartedInputSerializer implements IEditorSerializer {
2020-11-30 23:53:00 +01:00
public canSerialize ( editorInput : GettingStartedInput ) : boolean {
2020-11-25 23:05:49 +01:00
return true ;
}
2020-11-30 23:53:00 +01:00
public serialize ( editorInput : GettingStartedInput ) : string {
2021-04-24 01:34:52 +02:00
return JSON . stringify ( { selectedCategory : editorInput.selectedCategory , selectedStep : editorInput.selectedStep } ) ;
2020-11-25 23:05:49 +01:00
}
2020-11-30 23:53:00 +01:00
public deserialize ( instantiationService : IInstantiationService , serializedEditorInput : string ) : GettingStartedInput {
try {
2021-04-24 01:34:52 +02:00
const { selectedCategory , selectedStep } = JSON . parse ( serializedEditorInput ) ;
return new GettingStartedInput ( { selectedCategory , selectedStep } ) ;
2020-11-30 23:53:00 +01:00
} catch { }
2021-02-11 05:57:10 +01:00
return new GettingStartedInput ( { } ) ;
2020-11-25 23:05:49 +01:00
}
}
registerThemingParticipant ( ( theme , collector ) = > {
2021-02-03 00:12:38 +01:00
2020-11-25 23:05:49 +01:00
const backgroundColor = theme . getColor ( welcomePageBackground ) ;
if ( backgroundColor ) {
2021-04-09 20:46:04 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer { background-color: ${ backgroundColor } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
2021-02-03 00:12:38 +01:00
2020-11-25 23:05:49 +01:00
const foregroundColor = theme . getColor ( foreground ) ;
if ( foregroundColor ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer { color: ${ foregroundColor } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
2021-02-03 00:12:38 +01:00
2020-11-25 23:05:49 +01:00
const descriptionColor = theme . getColor ( descriptionForeground ) ;
if ( descriptionColor ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .description { color: ${ descriptionColor } ; } ` ) ;
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .category-progress .message { color: ${ descriptionColor } ; } ` ) ;
2021-08-19 06:41:58 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .gettingStartedDetailsContent > .getting-started-footer { color: ${ descriptionColor } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
2021-02-03 00:12:38 +01:00
const iconColor = theme . getColor ( textLinkForeground ) ;
if ( iconColor ) {
2021-03-18 22:48:22 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .getting-started-category .codicon:not(.codicon-close) { color: ${ iconColor } } ` ) ;
2021-04-24 01:34:52 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .codicon.complete { color: ${ iconColor } } ` ) ;
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step.expanded .codicon { color: ${ iconColor } } ` ) ;
2021-02-03 00:12:38 +01:00
}
const buttonColor = theme . getColor ( welcomePageTileBackground ) ;
2020-11-25 23:05:49 +01:00
if ( buttonColor ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button { background: ${ buttonColor } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
2021-03-24 23:21:03 +01:00
const shadowColor = theme . getColor ( welcomePageTileShadow ) ;
if ( shadowColor ) {
2021-04-09 20:46:04 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .getting-started-category { filter: drop-shadow(2px 2px 2px ${ buttonColor } ); } ` ) ;
2021-03-24 23:21:03 +01:00
}
2021-02-03 00:12:38 +01:00
const buttonHoverColor = theme . getColor ( welcomePageTileHoverBackground ) ;
2020-11-25 23:05:49 +01:00
if ( buttonHoverColor ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button:hover { background: ${ buttonHoverColor } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
if ( buttonColor && buttonHoverColor ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button.expanded:hover { background: ${ buttonColor } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
const emphasisButtonForeground = theme . getColor ( buttonForeground ) ;
if ( emphasisButtonForeground ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button.emphasis { color: ${ emphasisButtonForeground } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
const emphasisButtonBackground = theme . getColor ( buttonBackground ) ;
if ( emphasisButtonBackground ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button.emphasis { background: ${ emphasisButtonBackground } ; } ` ) ;
2020-12-10 19:57:00 +01:00
}
2021-04-24 01:34:52 +02:00
const pendingStepColor = theme . getColor ( descriptionForeground ) ;
if ( pendingStepColor ) {
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideDetails .getting-started-step .codicon { color: ${ pendingStepColor } } ` ) ;
2020-11-25 23:05:49 +01:00
}
const emphasisButtonHoverBackground = theme . getColor ( buttonHoverBackground ) ;
if ( emphasisButtonHoverBackground ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button.emphasis:hover { background: ${ emphasisButtonHoverBackground } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
const link = theme . getColor ( textLinkForeground ) ;
if ( link ) {
2021-07-16 18:33:37 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer a:not(.hide-category-button) { color: ${ link } ; } ` ) ;
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link { color: ${ link } ; } ` ) ;
2021-05-22 03:27:34 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .button-link .codicon { color: ${ link } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
const activeLink = theme . getColor ( textLinkActiveForeground ) ;
if ( activeLink ) {
2021-07-16 18:33:37 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer a:not(.hide-category-button):hover { color: ${ activeLink } ; } ` ) ;
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer a:not(.hide-category-button):active { color: ${ activeLink } ; } ` ) ;
2021-06-16 01:56:08 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button.button-link:hover { color: ${ activeLink } ; } ` ) ;
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button.button-link:hover .codicon { color: ${ activeLink } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
const focusColor = theme . getColor ( focusBorder ) ;
if ( focusColor ) {
2021-03-18 22:48:22 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer a:not(.codicon-close):focus { outline-color: ${ focusColor } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
const border = theme . getColor ( contrastBorder ) ;
if ( border ) {
2021-03-12 18:48:56 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button { border: 1px solid ${ border } ; } ` ) ;
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button.button-link { border: inherit; } ` ) ;
2020-11-25 23:05:49 +01:00
}
const activeBorder = theme . getColor ( activeContrastBorder ) ;
if ( activeBorder ) {
2021-02-11 05:57:10 +01:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer button:hover { outline-color: ${ activeBorder } ; } ` ) ;
2020-11-25 23:05:49 +01:00
}
2021-01-13 20:42:38 +01:00
const progressBackground = theme . getColor ( welcomePageProgressBackground ) ;
if ( progressBackground ) {
2021-04-09 20:46:04 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .progress-bar-outer { background-color: ${ progressBackground } ; } ` ) ;
2021-01-13 20:42:38 +01:00
}
const progressForeground = theme . getColor ( welcomePageProgressForeground ) ;
if ( progressForeground ) {
2021-04-09 20:46:04 +02:00
collector . addRule ( ` .monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories .progress-bar-inner { background-color: ${ progressForeground } ; } ` ) ;
2021-01-13 20:42:38 +01:00
}
2021-06-25 21:01:04 +02:00
const newBadgeForeground = theme . getColor ( ACTIVITY_BAR_BADGE_FOREGROUND ) ;
if ( newBadgeForeground ) {
collector . addRule ( ` .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .new-badge { color: ${ newBadgeForeground } ; } ` ) ;
collector . addRule ( ` .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .featured .featured-icon { color: ${ newBadgeForeground } ; } ` ) ;
}
2021-07-01 01:41:01 +02:00
const newBadgeBackground = theme . getColor ( ACTIVITY_BAR_BADGE_BACKGROUND ) ;
2021-06-25 21:01:04 +02:00
if ( newBadgeBackground ) {
collector . addRule ( ` .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .new-badge { background-color: ${ newBadgeBackground } ; } ` ) ;
collector . addRule ( ` .monaco-workbench .part.editor>.content .gettingStartedContainer .gettingStartedSlide .getting-started-category .featured { border-top-color: ${ newBadgeBackground } ; } ` ) ;
}
2020-11-25 23:05:49 +01:00
} ) ;