Skip to content

Commit

Permalink
Feature - Surface video tutorials on Welcome Page (#207293)
Browse files Browse the repository at this point in the history
* Initial support for video tutorials

* Experiment for surfacing video tutorials
  • Loading branch information
bhavyaus committed Mar 11, 2024
1 parent 12fc367 commit c658210
Showing 1 changed file with 122 additions and 16 deletions.
138 changes: 122 additions & 16 deletions src/vs/workbench/contrib/welcomeGettingStarted/browser/gettingStarted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { GettingStartedIndexList } from './gettingStartedList';
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';

const SLIDE_TRANSITION_TIME_MS = 250;
const configurationKey = 'workbench.startupEditor';
Expand Down Expand Up @@ -148,6 +149,7 @@ export class GettingStartedPage extends EditorPane {
private recentlyOpenedList?: GettingStartedIndexList<RecentEntry>;
private startList?: GettingStartedIndexList<IWelcomePageStartEntry>;
private gettingStartedList?: GettingStartedIndexList<IResolvedWalkthrough>;
private videoList?: GettingStartedIndexList<IWelcomePageStartEntry>;

private stepsSlide!: HTMLElement;
private categoriesSlide!: HTMLElement;
Expand All @@ -160,6 +162,7 @@ export class GettingStartedPage extends EditorPane {
private detailsRenderer: GettingStartedDetailsRenderer;

private categoriesSlideDisposables: DisposableStore;
private showFeaturedWalkthrough = true;

constructor(
group: IEditorGroup,
Expand All @@ -185,7 +188,9 @@ export class GettingStartedPage extends EditorPane {
@IHostService private readonly hostService: IHostService,
@IWebviewService private readonly webviewService: IWebviewService,
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService) {
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IWorkbenchAssignmentService private readonly tasExperimentService: IWorkbenchAssignmentService
) {

super(GettingStartedPage.ID, group, telemetryService, themeService, storageService);

Expand Down Expand Up @@ -345,7 +350,13 @@ export class GettingStartedPage extends EditorPane {
this.dispatchListeners.clear();

this.container.querySelectorAll('[x-dispatch]').forEach(element => {
const [command, argument] = (element.getAttribute('x-dispatch') ?? '').split(':');
const dispatch = element.getAttribute('x-dispatch') ?? '';
let command, argument;
if (dispatch.startsWith('openLink:https')) {
[command, argument] = ['openLink', dispatch.replace('openLink:', '')];
} else {
[command, argument] = dispatch.split(':');
}
if (command) {
this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => {
e.stopPropagation();
Expand Down Expand Up @@ -433,12 +444,12 @@ export class GettingStartedPage extends EditorPane {
}
break;
}
case 'openExtensionPage': {
this.commandService.executeCommand('extension.open', argument);
case 'hideVideos': {
this.hideVideos();
break;
}
case 'hideExtension': {
this.hideExtension(argument);
case 'openLink': {
this.openerService.open(argument);
break;
}
default: {
Expand All @@ -455,9 +466,9 @@ export class GettingStartedPage extends EditorPane {
this.gettingStartedList?.rerender();
}

private hideExtension(extensionId: string) {
this.setHiddenCategories([...this.getHiddenCategories().add(extensionId)]);
this.registerDispatchListeners();
private hideVideos() {
this.setHiddenCategories([...this.getHiddenCategories().add('getting-started-videos')]);
this.videoList?.setEntries(undefined);
}

private markAllStepsComplete() {
Expand Down Expand Up @@ -807,6 +818,29 @@ export class GettingStartedPage extends EditorPane {

const startList = this.buildStartList();
const recentList = this.buildRecentlyOpenedList();

const showVideoTutorials = await Promise.race([
this.tasExperimentService?.getTreatment<boolean>('gettingStarted.showVideoTutorials'),
new Promise<boolean | undefined>(resolve => setTimeout(() => resolve(false), 200))
]);

let videoList: GettingStartedIndexList<IWelcomePageStartEntry>;
if (showVideoTutorials === true) {
this.showFeaturedWalkthrough = false;
videoList = this.buildVideosList();
const layoutVideos = () => {
if (videoList?.itemCount > 0) {
reset(rightColumn, videoList?.getDomElement(), gettingStartedList.getDomElement());
}
else {
reset(rightColumn, gettingStartedList.getDomElement());
}
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
layoutRecentList();
};
videoList.onDidChange(layoutVideos);
}

const gettingStartedList = this.buildGettingStartedWalkthroughsList();

const footer = $('.footer', {},
Expand All @@ -818,19 +852,27 @@ export class GettingStartedPage extends EditorPane {
const layoutLists = () => {
if (gettingStartedList.itemCount) {
this.container.classList.remove('noWalkthroughs');
reset(rightColumn, gettingStartedList.getDomElement());
if (videoList?.itemCount > 0) {
reset(rightColumn, videoList?.getDomElement(), gettingStartedList.getDomElement());
} else {
reset(rightColumn, gettingStartedList.getDomElement());
}
}
else {
this.container.classList.add('noWalkthroughs');
reset(rightColumn);

if (videoList?.itemCount > 0) {
reset(rightColumn, videoList?.getDomElement());
}
else {
reset(rightColumn);
}
}
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
layoutRecentList();
};

const layoutRecentList = () => {
if (this.container.classList.contains('noWalkthroughs')) {
if (this.container.classList.contains('noWalkthroughs') && videoList?.itemCount === 0) {
recentList.setLimit(10);
reset(leftColumn, startList.getDomElement());
reset(rightColumn, recentList.getDomElement());
Expand Down Expand Up @@ -873,7 +915,7 @@ export class GettingStartedPage extends EditorPane {
const telemetryNotice = $('p.telemetry-notice');
this.buildTelemetryFooter(telemetryNotice);
footer.appendChild(telemetryNotice);
} else if (!this.productService.openToWelcomeMainPage && !someStepsComplete && !this.hasScrolledToFirstCategory) {
} else if (!this.productService.openToWelcomeMainPage && !someStepsComplete && !this.hasScrolledToFirstCategory && this.showFeaturedWalkthrough) {
const firstSessionDateString = this.storageService.get(firstSessionDateStorageKey, StorageScope.APPLICATION) || new Date().toUTCString();
const daysSinceFirstSession = ((+new Date()) - (+new Date(firstSessionDateString))) / 1000 / 60 / 60 / 24;
const fistContentBehaviour = daysSinceFirstSession < 1 ? 'openToFirstCategory' : 'index';
Expand Down Expand Up @@ -1018,15 +1060,15 @@ export class GettingStartedPage extends EditorPane {
const featuredBadge = $('.featured-badge', {});
const descriptionContent = $('.description-content', {},);

if (category.isFeatured) {
if (category.isFeatured && this.showFeaturedWalkthrough) {
reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-full')));
reset(descriptionContent, ...renderLabelWithIcons(category.description));
}

const titleContent = $('h3.category-title.max-lines-3', { 'x-category-title-for': category.id });
reset(titleContent, ...renderLabelWithIcons(category.title));

return $('button.getting-started-category' + (category.isFeatured ? '.featured' : ''),
return $('button.getting-started-category' + (category.isFeatured && this.showFeaturedWalkthrough ? '.featured' : ''),
{
'x-dispatch': 'selectCategory:' + category.id,
'title': category.description
Expand Down Expand Up @@ -1090,6 +1132,69 @@ export class GettingStartedPage extends EditorPane {
return gettingStartedList;
}

private buildVideosList(): GettingStartedIndexList<IWelcomePageStartEntry> {

const renderFeaturedExtensions = (entry: IWelcomePageStartEntry): HTMLElement => {

const featuredBadge = $('.featured-badge', {});
const descriptionContent = $('.description-content', {},);

reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-full')));
reset(descriptionContent, ...renderLabelWithIcons(entry.description));

const titleContent = $('h3.category-title.max-lines-3', { 'x-category-title-for': entry.id });
reset(titleContent, ...renderLabelWithIcons(entry.title));

return $('button.getting-started-category' + '.featured',
{
'x-dispatch': 'openLink:' + entry.command,
'title': entry.title
},
featuredBadge,
$('.main-content', {},
this.iconWidgetFor(entry),
titleContent,
$('a.codicon.codicon-close.hide-category-button', {
'tabindex': 0,
'x-dispatch': 'hideVideos',
'title': localize('close', "Hide"),
'role': 'button',
'aria-label': localize('closeAriaLabel', "Hide"),
}),
),
descriptionContent);
};

if (this.videoList) {
this.videoList.dispose();
}
const videoList = this.videoList = new GettingStartedIndexList(
{
title: '',
klass: 'getting-started-videos',
limit: 1,
renderElement: renderFeaturedExtensions,
contextService: this.contextService,
});

if (this.getHiddenCategories().has('getting-started-videos')) {
return videoList;
}

videoList.setEntries([{
id: 'getting-started-videos',
title: localize('videos-title', 'Discover Getting Started Tutorials'),
description: localize('videos-description', 'Learn VS Code\'s must-have features in short and practical videos'),
command: 'https://aka.ms/vscode-getting-started-tutorials',
order: 0,
icon: { type: 'icon', icon: Codicon.play },
when: ContextKeyExpr.true(),
}]);
videoList.onDidChange(() => this.registerDispatchListeners());

return videoList;
}

layout(size: Dimension) {
this.detailsScrollbar?.scanDomNode();

Expand All @@ -1099,6 +1204,7 @@ export class GettingStartedPage extends EditorPane {
this.startList?.layout(size);
this.gettingStartedList?.layout(size);
this.recentlyOpenedList?.layout(size);
this.videoList?.layout(size);

if (this.editorInput?.selectedStep && this.currentMediaType) {
this.mediaDisposables.clear();
Expand Down

0 comments on commit c658210

Please sign in to comment.