mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Feature - Surface video tutorials on Welcome Page (#207293)
* Initial support for video tutorials * Experiment for surfacing video tutorials
This commit is contained in:
parent
12fc36731b
commit
c658210cce
|
@ -70,6 +70,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
||||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
import { ThemeSettings } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||||
import { GettingStartedIndexList } from './gettingStartedList';
|
import { GettingStartedIndexList } from './gettingStartedList';
|
||||||
|
import { IWorkbenchAssignmentService } from 'vs/workbench/services/assignment/common/assignmentService';
|
||||||
|
|
||||||
const SLIDE_TRANSITION_TIME_MS = 250;
|
const SLIDE_TRANSITION_TIME_MS = 250;
|
||||||
const configurationKey = 'workbench.startupEditor';
|
const configurationKey = 'workbench.startupEditor';
|
||||||
|
@ -148,6 +149,7 @@ export class GettingStartedPage extends EditorPane {
|
||||||
private recentlyOpenedList?: GettingStartedIndexList<RecentEntry>;
|
private recentlyOpenedList?: GettingStartedIndexList<RecentEntry>;
|
||||||
private startList?: GettingStartedIndexList<IWelcomePageStartEntry>;
|
private startList?: GettingStartedIndexList<IWelcomePageStartEntry>;
|
||||||
private gettingStartedList?: GettingStartedIndexList<IResolvedWalkthrough>;
|
private gettingStartedList?: GettingStartedIndexList<IResolvedWalkthrough>;
|
||||||
|
private videoList?: GettingStartedIndexList<IWelcomePageStartEntry>;
|
||||||
|
|
||||||
private stepsSlide!: HTMLElement;
|
private stepsSlide!: HTMLElement;
|
||||||
private categoriesSlide!: HTMLElement;
|
private categoriesSlide!: HTMLElement;
|
||||||
|
@ -160,6 +162,7 @@ export class GettingStartedPage extends EditorPane {
|
||||||
private detailsRenderer: GettingStartedDetailsRenderer;
|
private detailsRenderer: GettingStartedDetailsRenderer;
|
||||||
|
|
||||||
private categoriesSlideDisposables: DisposableStore;
|
private categoriesSlideDisposables: DisposableStore;
|
||||||
|
private showFeaturedWalkthrough = true;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
group: IEditorGroup,
|
group: IEditorGroup,
|
||||||
|
@ -185,7 +188,9 @@ export class GettingStartedPage extends EditorPane {
|
||||||
@IHostService private readonly hostService: IHostService,
|
@IHostService private readonly hostService: IHostService,
|
||||||
@IWebviewService private readonly webviewService: IWebviewService,
|
@IWebviewService private readonly webviewService: IWebviewService,
|
||||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
@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);
|
super(GettingStartedPage.ID, group, telemetryService, themeService, storageService);
|
||||||
|
|
||||||
|
@ -345,7 +350,13 @@ export class GettingStartedPage extends EditorPane {
|
||||||
this.dispatchListeners.clear();
|
this.dispatchListeners.clear();
|
||||||
|
|
||||||
this.container.querySelectorAll('[x-dispatch]').forEach(element => {
|
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) {
|
if (command) {
|
||||||
this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => {
|
this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -433,12 +444,12 @@ export class GettingStartedPage extends EditorPane {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'openExtensionPage': {
|
case 'hideVideos': {
|
||||||
this.commandService.executeCommand('extension.open', argument);
|
this.hideVideos();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'hideExtension': {
|
case 'openLink': {
|
||||||
this.hideExtension(argument);
|
this.openerService.open(argument);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
|
@ -455,9 +466,9 @@ export class GettingStartedPage extends EditorPane {
|
||||||
this.gettingStartedList?.rerender();
|
this.gettingStartedList?.rerender();
|
||||||
}
|
}
|
||||||
|
|
||||||
private hideExtension(extensionId: string) {
|
private hideVideos() {
|
||||||
this.setHiddenCategories([...this.getHiddenCategories().add(extensionId)]);
|
this.setHiddenCategories([...this.getHiddenCategories().add('getting-started-videos')]);
|
||||||
this.registerDispatchListeners();
|
this.videoList?.setEntries(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
private markAllStepsComplete() {
|
private markAllStepsComplete() {
|
||||||
|
@ -807,6 +818,29 @@ export class GettingStartedPage extends EditorPane {
|
||||||
|
|
||||||
const startList = this.buildStartList();
|
const startList = this.buildStartList();
|
||||||
const recentList = this.buildRecentlyOpenedList();
|
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 gettingStartedList = this.buildGettingStartedWalkthroughsList();
|
||||||
|
|
||||||
const footer = $('.footer', {},
|
const footer = $('.footer', {},
|
||||||
|
@ -818,19 +852,27 @@ export class GettingStartedPage extends EditorPane {
|
||||||
const layoutLists = () => {
|
const layoutLists = () => {
|
||||||
if (gettingStartedList.itemCount) {
|
if (gettingStartedList.itemCount) {
|
||||||
this.container.classList.remove('noWalkthroughs');
|
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 {
|
else {
|
||||||
this.container.classList.add('noWalkthroughs');
|
this.container.classList.add('noWalkthroughs');
|
||||||
reset(rightColumn);
|
if (videoList?.itemCount > 0) {
|
||||||
|
reset(rightColumn, videoList?.getDomElement());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reset(rightColumn);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
|
setTimeout(() => this.categoriesPageScrollbar?.scanDomNode(), 50);
|
||||||
layoutRecentList();
|
layoutRecentList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const layoutRecentList = () => {
|
const layoutRecentList = () => {
|
||||||
if (this.container.classList.contains('noWalkthroughs')) {
|
if (this.container.classList.contains('noWalkthroughs') && videoList?.itemCount === 0) {
|
||||||
recentList.setLimit(10);
|
recentList.setLimit(10);
|
||||||
reset(leftColumn, startList.getDomElement());
|
reset(leftColumn, startList.getDomElement());
|
||||||
reset(rightColumn, recentList.getDomElement());
|
reset(rightColumn, recentList.getDomElement());
|
||||||
|
@ -873,7 +915,7 @@ export class GettingStartedPage extends EditorPane {
|
||||||
const telemetryNotice = $('p.telemetry-notice');
|
const telemetryNotice = $('p.telemetry-notice');
|
||||||
this.buildTelemetryFooter(telemetryNotice);
|
this.buildTelemetryFooter(telemetryNotice);
|
||||||
footer.appendChild(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 firstSessionDateString = this.storageService.get(firstSessionDateStorageKey, StorageScope.APPLICATION) || new Date().toUTCString();
|
||||||
const daysSinceFirstSession = ((+new Date()) - (+new Date(firstSessionDateString))) / 1000 / 60 / 60 / 24;
|
const daysSinceFirstSession = ((+new Date()) - (+new Date(firstSessionDateString))) / 1000 / 60 / 60 / 24;
|
||||||
const fistContentBehaviour = daysSinceFirstSession < 1 ? 'openToFirstCategory' : 'index';
|
const fistContentBehaviour = daysSinceFirstSession < 1 ? 'openToFirstCategory' : 'index';
|
||||||
|
@ -1018,7 +1060,7 @@ export class GettingStartedPage extends EditorPane {
|
||||||
const featuredBadge = $('.featured-badge', {});
|
const featuredBadge = $('.featured-badge', {});
|
||||||
const descriptionContent = $('.description-content', {},);
|
const descriptionContent = $('.description-content', {},);
|
||||||
|
|
||||||
if (category.isFeatured) {
|
if (category.isFeatured && this.showFeaturedWalkthrough) {
|
||||||
reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-full')));
|
reset(featuredBadge, $('.featured', {}, $('span.featured-icon.codicon.codicon-star-full')));
|
||||||
reset(descriptionContent, ...renderLabelWithIcons(category.description));
|
reset(descriptionContent, ...renderLabelWithIcons(category.description));
|
||||||
}
|
}
|
||||||
|
@ -1026,7 +1068,7 @@ export class GettingStartedPage extends EditorPane {
|
||||||
const titleContent = $('h3.category-title.max-lines-3', { 'x-category-title-for': category.id });
|
const titleContent = $('h3.category-title.max-lines-3', { 'x-category-title-for': category.id });
|
||||||
reset(titleContent, ...renderLabelWithIcons(category.title));
|
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,
|
'x-dispatch': 'selectCategory:' + category.id,
|
||||||
'title': category.description
|
'title': category.description
|
||||||
|
@ -1090,6 +1132,69 @@ export class GettingStartedPage extends EditorPane {
|
||||||
return gettingStartedList;
|
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) {
|
layout(size: Dimension) {
|
||||||
this.detailsScrollbar?.scanDomNode();
|
this.detailsScrollbar?.scanDomNode();
|
||||||
|
|
||||||
|
@ -1099,6 +1204,7 @@ export class GettingStartedPage extends EditorPane {
|
||||||
this.startList?.layout(size);
|
this.startList?.layout(size);
|
||||||
this.gettingStartedList?.layout(size);
|
this.gettingStartedList?.layout(size);
|
||||||
this.recentlyOpenedList?.layout(size);
|
this.recentlyOpenedList?.layout(size);
|
||||||
|
this.videoList?.layout(size);
|
||||||
|
|
||||||
if (this.editorInput?.selectedStep && this.currentMediaType) {
|
if (this.editorInput?.selectedStep && this.currentMediaType) {
|
||||||
this.mediaDisposables.clear();
|
this.mediaDisposables.clear();
|
||||||
|
|
Loading…
Reference in a new issue