Add GettingStartedService/Registry and initial getting started UI (#111175)
* WIP -Getting Started Registry * Initial idea of how registry/service will work * Getting started with Getting Started * Add telemetry and touch up enablement flag * Add contrib as startup editor * Move to allowing static Getting Start blob * No newline * Remove unused enum
This commit is contained in:
parent
ef03adf3a0
commit
0921f711c3
|
@ -373,6 +373,10 @@
|
|||
{
|
||||
"name": "vs/workbench/services/extensionRecommendations",
|
||||
"project": "vscode-workbench"
|
||||
},
|
||||
{
|
||||
"name": "vs/workbench/services/gettingStarted",
|
||||
"project": "vscode-workbench"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1373,8 +1373,8 @@ export function safeInnerHtml(node: HTMLElement, value: string): void {
|
|||
const options = _extInsaneOptions({
|
||||
allowedTags: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
|
||||
allowedAttributes: {
|
||||
'a': ['href'],
|
||||
'button': ['data-href'],
|
||||
'a': ['href', 'x-dispatch'],
|
||||
'button': ['data-href', 'x-dispatch'],
|
||||
'input': ['type', 'placeholder', 'checked', 'required'],
|
||||
'label': ['for'],
|
||||
'select': ['required'],
|
||||
|
|
|
@ -22,6 +22,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
|
|||
import { IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
|
||||
import { DEFAULT_PRODUCT_ICON_THEME_ID } from 'vs/workbench/services/themes/browser/productIconThemeData';
|
||||
import { IGettingStartedService } from 'vs/workbench/services/gettingStarted/common/gettingStartedService';
|
||||
|
||||
export class SelectColorThemeAction extends Action {
|
||||
|
||||
|
@ -34,6 +35,7 @@ export class SelectColorThemeAction extends Action {
|
|||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService,
|
||||
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
|
||||
@IGettingStartedService private readonly gettingStartedService: IGettingStartedService,
|
||||
@IViewletService private readonly viewletService: IViewletService
|
||||
) {
|
||||
super(id, label);
|
||||
|
@ -84,6 +86,7 @@ export class SelectColorThemeAction extends Action {
|
|||
openExtensionViewlet(this.viewletService, `category:themes ${quickpick.value}`);
|
||||
} else {
|
||||
selectTheme(theme, true);
|
||||
this.gettingStartedService.progressByEvent('themeSelected');
|
||||
}
|
||||
isCompleted = true;
|
||||
quickpick.hide();
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
import { GettingStartedInputFactory, GettingStartedPage } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor';
|
||||
import { MenuId, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'workbench.action.showGettingStarted',
|
||||
title: localize('Getting Started', "Getting Started"),
|
||||
category: localize('help', "Help"),
|
||||
f1: true,
|
||||
precondition: ContextKeyExpr.has('config.workbench.experimental.gettingStarted'),
|
||||
menu: {
|
||||
id: MenuId.MenubarHelpMenu,
|
||||
when: ContextKeyExpr.has('config.workbench.experimental.gettingStarted'),
|
||||
group: '1_welcome',
|
||||
order: 2,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor) {
|
||||
return accessor.get(IInstantiationService).createInstance(GettingStartedPage).openEditor();
|
||||
}
|
||||
});
|
||||
|
||||
Registry.as<IEditorInputFactoryRegistry>(EditorInputExtensions.EditorInputFactories).registerEditorInputFactory(GettingStartedInputFactory.ID, GettingStartedInputFactory);
|
||||
|
||||
if (product.quality !== 'stable') {
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).registerConfiguration({
|
||||
...workbenchConfigurationNodeBase,
|
||||
properties: {
|
||||
'workbench.experimental.gettingStarted': {
|
||||
type: 'boolean',
|
||||
description: localize('gettingStartedDescription', "Enables an experimental Getting Started page, accesible via the Help menu."),
|
||||
default: false,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.file-icons-enabled .show-file-icons .vs_code_editor_getting_started\.md-name-file-icon.md-ext-file-icon.ext-file-icon.markdown-lang-file-icon.file-icon::before {
|
||||
content: ' ';
|
||||
background-image: url('../../../../browser/media/code-icon.svg');
|
||||
}
|
||||
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
transition: left 0.25s;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .header {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .title {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-title {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-description-container {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-description {
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .category-progress {
|
||||
margin-top: 6px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories progress {
|
||||
font-size: 12pt;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories #getting-started-categories-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
width: 70%;
|
||||
max-width: 900px;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .getting-started-category {
|
||||
width: 330px;
|
||||
height: 100px;
|
||||
text-align: left;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.categories .getting-started-category {
|
||||
padding-right: 46px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .getting-started-category .codicon {
|
||||
margin: 10px 8px 0 0;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category {
|
||||
width: 330px;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
margin-left: 12px;
|
||||
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-category .codicon {
|
||||
margin-left:0;
|
||||
margin-top: 28px;
|
||||
font-size: 22pt;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail #getting-started-detail-columns {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task {
|
||||
width: 100%;
|
||||
height: 26pt;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task.expanded {
|
||||
width: 100%;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description-container {
|
||||
padding-left: 30px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-title {
|
||||
margin-bottom: 4px;
|
||||
font-size: 14pt;
|
||||
}
|
||||
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-description {
|
||||
font-size: 11pt;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .task-next {
|
||||
float: right;
|
||||
margin-top: 16px;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon {
|
||||
float: left;
|
||||
font-size: 20pt;
|
||||
position: relative;
|
||||
top: -4px;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task-action {
|
||||
margin: 10px 0 0;
|
||||
padding: 4px 8px;
|
||||
font-size: 11pt;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail #getting-started-detail-left {
|
||||
min-width: 330px;
|
||||
width: 33%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail #getting-started-detail-right {
|
||||
width: 66%;
|
||||
text-align: center;
|
||||
padding: 35px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button {
|
||||
border: none;
|
||||
margin: 10px;
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
font-size: 12pt;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .prev-button .codicon {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .product-icon {
|
||||
background-image: url('../../../../browser/media/code-icon.svg');
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .skip {
|
||||
display: block;
|
||||
width: 150px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h1 {
|
||||
font-size: 32pt;
|
||||
font-weight: normal;
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h2 {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide h3 {
|
||||
font-weight: normal;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide .subtitle {
|
||||
font-size: 18pt;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .footer {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.next {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.prev {
|
||||
left: -100%;
|
||||
}
|
|
@ -0,0 +1,394 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 'vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughInput';
|
||||
import { FileAccess, Schemas } from 'vs/base/common/network';
|
||||
import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { $, addDisposableListener } from 'vs/base/browser/dom';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IGettingStartedCategoryWithProgress, IGettingStartedService } from 'vs/workbench/services/gettingStarted/common/gettingStartedService';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { buttonBackground as welcomeButtonBackground, buttonHoverBackground as welcomeButtonHoverBackground, welcomePageBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors';
|
||||
import { activeContrastBorder, buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, descriptionForeground, focusBorder, foreground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
|
||||
export const gettingStartedInputTypeId = 'workbench.editors.gettingStartedInput';
|
||||
const telemetryFrom = 'gettingStartedPage';
|
||||
|
||||
export class GettingStartedPage extends Disposable {
|
||||
readonly editorInput: WalkThroughInput;
|
||||
private inProgressScroll = Promise.resolve();
|
||||
|
||||
private dispatchListeners = new DisposableStore();
|
||||
|
||||
private gettingStartedCategories: IGettingStartedCategoryWithProgress[];
|
||||
private currentCategory: IGettingStartedCategoryWithProgress | undefined;
|
||||
|
||||
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IGettingStartedService private readonly gettingStartedService: IGettingStartedService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService) {
|
||||
super();
|
||||
|
||||
const resource = FileAccess.asBrowserUri('./vs_code_editor_getting_started.md', require)
|
||||
.with({
|
||||
scheme: Schemas.walkThrough,
|
||||
query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/gettingStarted/browser/vs_code_editor_getting_started' })
|
||||
});
|
||||
|
||||
|
||||
this.editorInput = this.instantiationService.createInstance(WalkThroughInput, {
|
||||
typeId: gettingStartedInputTypeId,
|
||||
name: localize('editorGettingStarted.title', "Getting Started"),
|
||||
resource,
|
||||
telemetryFrom,
|
||||
onReady: (container: HTMLElement) => this.onReady(container)
|
||||
});
|
||||
|
||||
this.gettingStartedCategories = this.gettingStartedService.getCategories();
|
||||
this._register(this.dispatchListeners);
|
||||
this._register(this.gettingStartedService.onDidAddTask(task => console.log('added new task', task, 'that isnt being rendered yet')));
|
||||
this._register(this.gettingStartedService.onDidAddCategory(category => console.log('added new category', category, 'that isnt being rendered yet')));
|
||||
this._register(this.gettingStartedService.onDidProgressTask(task => {
|
||||
const category = this.gettingStartedCategories.find(category => category.id === task.category);
|
||||
if (!category) { throw Error('Could not find category with ID: ' + task.category); }
|
||||
if (category.content.type !== 'items') { throw Error('internaal error: progressing task in a non-items category'); }
|
||||
const ourTask = category.content.items.find(_task => _task.id === task.id);
|
||||
if (!ourTask) {
|
||||
throw Error('Could not find task with ID: ' + task.id);
|
||||
}
|
||||
ourTask.done = task.done;
|
||||
if (category.id === this.currentCategory?.id) {
|
||||
const badgeelement = assertIsDefined(document.getElementById('done-task-' + task.id));
|
||||
if (task.done) {
|
||||
badgeelement.classList.remove('codicon-star-empty');
|
||||
badgeelement.classList.add('codicon-star-full');
|
||||
}
|
||||
else {
|
||||
badgeelement.classList.add('codicon-star-empty');
|
||||
badgeelement.classList.remove('codicon-star-full');
|
||||
}
|
||||
}
|
||||
this.updateCategoryProgress();
|
||||
}));
|
||||
}
|
||||
|
||||
public openEditor(options: IEditorOptions = { pinned: true }) {
|
||||
return this.editorService.openEditor(this.editorInput, options);
|
||||
}
|
||||
|
||||
private registerDispatchListeners(container: HTMLElement) {
|
||||
this.dispatchListeners.clear();
|
||||
|
||||
container.querySelectorAll('[x-dispatch]').forEach(element => {
|
||||
const [command, argument] = (element.getAttribute('x-dispatch') ?? '').split(':');
|
||||
if (command) {
|
||||
this.dispatchListeners.add(addDisposableListener(element, 'click', (e) => {
|
||||
|
||||
type GettingStartedActionClassification = {
|
||||
command: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
argument: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
};
|
||||
type GettingStartedActionEvent = {
|
||||
command: string;
|
||||
argument: string | undefined;
|
||||
};
|
||||
this.telemetryService.publicLog2<GettingStartedActionEvent, GettingStartedActionClassification>('gettingStarted.ActionExecuted', { command, argument });
|
||||
|
||||
switch (command) {
|
||||
case 'scrollPrev': {
|
||||
this.scrollPrev(container);
|
||||
break;
|
||||
}
|
||||
case 'skip': {
|
||||
this.commandService.executeCommand('workbench.action.closeActiveEditor');
|
||||
break;
|
||||
}
|
||||
case 'selectCategory': {
|
||||
const selectedCategory = this.gettingStartedCategories.find(category => category.id === argument);
|
||||
if (!selectedCategory) { throw Error('Could not find category with ID ' + argument); }
|
||||
if (selectedCategory.content.type === 'command') {
|
||||
this.commandService.executeCommand(selectedCategory.content.command);
|
||||
} else {
|
||||
this.scrollToCategory(container, argument);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'selectTask': {
|
||||
this.selectTask(argument);
|
||||
e.stopPropagation();
|
||||
break;
|
||||
}
|
||||
case 'runTaskAction': {
|
||||
if (!this.currentCategory || this.currentCategory.content.type !== 'items') {
|
||||
throw Error('cannot run task action for category of non items type' + this.currentCategory?.id);
|
||||
}
|
||||
const taskToRun = assertIsDefined(this.currentCategory?.content.items.find(task => task.id === argument));
|
||||
const commandToRun = assertIsDefined(taskToRun.button?.command);
|
||||
this.commandService.executeCommand(commandToRun);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
console.error('Dispatch to', command, argument, 'not defined');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private selectTask(id: string | undefined) {
|
||||
const mediaElement = assertIsDefined(document.getElementById('getting-started-media'));
|
||||
if (id) {
|
||||
const taskElement = assertIsDefined(document.getElementById('getting-started-task-' + id));
|
||||
if (!this.currentCategory || this.currentCategory.content.type !== 'items') {
|
||||
throw Error('cannot expand task for category of non items type' + this.currentCategory?.id);
|
||||
}
|
||||
const taskToExpand = assertIsDefined(this.currentCategory.content.items.find(task => task.id === id));
|
||||
mediaElement.setAttribute('src', taskToExpand.media.toString());
|
||||
taskElement.parentElement?.querySelectorAll('.expanded').forEach(node => node.classList.remove('expanded'));
|
||||
taskElement.classList.add('expanded');
|
||||
} else {
|
||||
mediaElement.setAttribute('src', '');
|
||||
}
|
||||
}
|
||||
|
||||
private onReady(container: HTMLElement) {
|
||||
const categoryElements = this.gettingStartedCategories.map(
|
||||
category => {
|
||||
const categoryDescriptionElement =
|
||||
category.content.type === 'items' ?
|
||||
$('.category-description-container', {},
|
||||
$('h3.category-title', {}, category.title),
|
||||
$('.category-description.description', {}, category.description),
|
||||
$('.category-progress', { 'x-data-category-id': category.id, }, $('.message'), $('progress'))) :
|
||||
$('.category-description-container', {},
|
||||
$('h3.category-title', {}, category.title),
|
||||
$('.category-description.description', {}, category.description));
|
||||
|
||||
return $('button.getting-started-category',
|
||||
{ 'x-dispatch': 'selectCategory:' + category.id },
|
||||
$('.codicon.codicon-' + category.codicon, {}), categoryDescriptionElement);
|
||||
});
|
||||
|
||||
const rightColumn = assertIsDefined(container.querySelector('#getting-started-detail-right'));
|
||||
rightColumn.appendChild($('img#getting-started-media'));
|
||||
|
||||
categoryElements.forEach(element => {
|
||||
assertIsDefined(document.getElementById('getting-started-categories-container')).appendChild(element);
|
||||
});
|
||||
|
||||
this.updateCategoryProgress();
|
||||
|
||||
assertIsDefined(document.getElementById('product-name')).textContent = this.productService.nameLong;
|
||||
this.registerDispatchListeners(container);
|
||||
}
|
||||
|
||||
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);
|
||||
if (!category) { throw Error('Could not find c=ategory with ID ' + categoryID); }
|
||||
if (category.content.type !== 'items') { throw Error('Category with ID ' + categoryID + ' is not of items type'); }
|
||||
const numDone = category.content.items.filter(task => task.done).length;
|
||||
const numTotal = category.content.items.length;
|
||||
|
||||
const message = assertIsDefined(element.firstChild);
|
||||
const bar = assertIsDefined(element.lastChild) as HTMLProgressElement;
|
||||
bar.value = numDone;
|
||||
bar.max = numTotal;
|
||||
if (numTotal === numDone) {
|
||||
message.textContent = `All items complete!`;
|
||||
}
|
||||
else {
|
||||
message.textContent = `${numDone} of ${numTotal} items complete`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async scrollToCategory(container: HTMLElement, categoryID: string) {
|
||||
this.inProgressScroll = this.inProgressScroll.then(async () => {
|
||||
this.clearDetialView();
|
||||
this.currentCategory = this.gettingStartedCategories.find(category => category.id === categoryID);
|
||||
if (!this.currentCategory) { throw Error('could not find category with ID ' + categoryID); }
|
||||
if (this.currentCategory.content.type !== 'items') { throw Error('category with ID ' + categoryID + ' is not of items type'); }
|
||||
const slides = [...container.querySelectorAll('.gettingStartedSlide').values()];
|
||||
const currentSlide = slides.findIndex(element =>
|
||||
!element.classList.contains('prev') && !element.classList.contains('next'));
|
||||
if (currentSlide < slides.length - 1) {
|
||||
slides[currentSlide].classList.add('prev');
|
||||
|
||||
const detailSlide = assertIsDefined(slides[currentSlide + 1]);
|
||||
detailSlide.classList.remove('next');
|
||||
const detailTitle = assertIsDefined(document.getElementById('getting-started-detail-title'));
|
||||
detailTitle.appendChild(
|
||||
$('.getting-started-category',
|
||||
{},
|
||||
$('.codicon.codicon-' + this.currentCategory.codicon, {}),
|
||||
$('.category-description-container', {},
|
||||
$('h2.category-title', {}, this.currentCategory.title),
|
||||
$('.category-description.description', {}, this.currentCategory.description))));
|
||||
|
||||
const categoryElements = this.currentCategory.content.items.map(
|
||||
(task, i, arr) =>
|
||||
$('button.getting-started-task',
|
||||
{ 'x-dispatch': 'selectTask:' + task.id, id: 'getting-started-task-' + task.id },
|
||||
$('.codicon' + (task.done ? '.codicon-star-full' : '.codicon-star-empty'), { id: 'done-task-' + task.id },),
|
||||
$('.task-description-container', {},
|
||||
$('h3.task-title', {}, task.title),
|
||||
$('.task-description.description', {}, task.description),
|
||||
...(
|
||||
task.button
|
||||
? [$('button.emphasis.getting-started-task-action', { 'x-dispatch': 'runTaskAction:' + task.id },
|
||||
task.button.title + this.getKeybindingLabel(task.button.command)
|
||||
)]
|
||||
: []),
|
||||
...(
|
||||
arr[i + 1]
|
||||
? [
|
||||
$('a.task-next',
|
||||
{ 'x-dispatch': 'selectTask:' + arr[i + 1].id }, localize('next', "Next")),
|
||||
] : []
|
||||
)
|
||||
)));
|
||||
|
||||
const detailContainer = assertIsDefined(document.getElementById('getting-started-detail-container'));
|
||||
categoryElements.forEach(element => detailContainer.appendChild(element));
|
||||
|
||||
const toExpand = this.currentCategory.content.items.find(item => !item.done) ?? this.currentCategory.content.items[0];
|
||||
this.selectTask(toExpand.id);
|
||||
this.registerDispatchListeners(container);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private clearDetialView() {
|
||||
const detailContainer = assertIsDefined(document.getElementById('getting-started-detail-container'));
|
||||
while (detailContainer.firstChild) { detailContainer.removeChild(detailContainer.firstChild); }
|
||||
const detailTitle = assertIsDefined(document.getElementById('getting-started-detail-title'));
|
||||
while (detailTitle.firstChild) { detailTitle.removeChild(detailTitle.firstChild); }
|
||||
}
|
||||
|
||||
private getKeybindingLabel(command: string) {
|
||||
const binding = this.keybindingService.lookupKeybinding(command);
|
||||
if (!binding) { return ''; }
|
||||
else { return ` (${binding.getLabel()})`; }
|
||||
}
|
||||
|
||||
private async scrollPrev(container: HTMLElement) {
|
||||
this.inProgressScroll = this.inProgressScroll.then(async () => {
|
||||
this.currentCategory = undefined;
|
||||
this.selectTask(undefined);
|
||||
const slides = [...container.querySelectorAll('.gettingStartedSlide').values()];
|
||||
const currentSlide = slides.findIndex(element =>
|
||||
!element.classList.contains('prev') && !element.classList.contains('next'));
|
||||
if (currentSlide > 0) {
|
||||
slides[currentSlide].classList.add('next');
|
||||
assertIsDefined(slides[currentSlide - 1]).classList.remove('prev');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class GettingStartedInputFactory implements IEditorInputFactory {
|
||||
|
||||
static readonly ID = gettingStartedInputTypeId;
|
||||
|
||||
public canSerialize(editorInput: EditorInput): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public serialize(editorInput: EditorInput): string {
|
||||
return '{}';
|
||||
}
|
||||
|
||||
public deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): WalkThroughInput {
|
||||
return instantiationService.createInstance(GettingStartedPage).editorInput;
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const backgroundColor = theme.getColor(welcomePageBackground);
|
||||
if (backgroundColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .welcomePageContainer { background-color: ${backgroundColor}; }`);
|
||||
}
|
||||
const foregroundColor = theme.getColor(foreground);
|
||||
if (foregroundColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer { color: ${foregroundColor}; }`);
|
||||
}
|
||||
const descriptionColor = theme.getColor(descriptionForeground);
|
||||
if (descriptionColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .description { color: ${descriptionColor}; }`);
|
||||
}
|
||||
const buttonColor = getExtraColor(theme, welcomeButtonBackground, { dark: 'rgba(0, 0, 0, .2)', extra_dark: 'rgba(200, 235, 255, .042)', light: 'rgba(0,0,0,.04)', hc: 'black' });
|
||||
if (buttonColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button { background: ${buttonColor}; }`);
|
||||
}
|
||||
|
||||
const buttonHoverColor = getExtraColor(theme, welcomeButtonHoverBackground, { dark: 'rgba(200, 235, 255, .072)', extra_dark: 'rgba(200, 235, 255, .072)', light: 'rgba(0,0,0,.10)', hc: null });
|
||||
if (buttonHoverColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover { background: ${buttonHoverColor}; }`);
|
||||
}
|
||||
if (buttonColor && buttonHoverColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.expanded:hover { background: ${buttonColor}; }`);
|
||||
}
|
||||
|
||||
const emphasisButtonForeground = theme.getColor(buttonForeground);
|
||||
if (emphasisButtonForeground) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis { color: ${emphasisButtonForeground}; }`);
|
||||
}
|
||||
|
||||
const emphasisButtonBackground = theme.getColor(buttonBackground);
|
||||
if (emphasisButtonBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis { background: ${emphasisButtonBackground}; }`);
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .getting-started-category .codicon { color: ${emphasisButtonBackground} }`);
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer .gettingStartedSlide.detail .getting-started-task .codicon { color: ${emphasisButtonBackground} } `);
|
||||
}
|
||||
|
||||
const emphasisButtonHoverBackground = theme.getColor(buttonHoverBackground);
|
||||
if (emphasisButtonHoverBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button.emphasis:hover { background: ${emphasisButtonHoverBackground}; }`);
|
||||
}
|
||||
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a { color: ${link}; }`);
|
||||
}
|
||||
const activeLink = theme.getColor(textLinkActiveForeground);
|
||||
if (activeLink) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:hover,
|
||||
.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:active { color: ${activeLink}; }`);
|
||||
}
|
||||
const focusColor = theme.getColor(focusBorder);
|
||||
if (focusColor) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer a:focus { outline-color: ${focusColor}; }`);
|
||||
}
|
||||
const border = theme.getColor(contrastBorder);
|
||||
if (border) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button { border-color: ${border}; border: 1px solid; }`);
|
||||
}
|
||||
const activeBorder = theme.getColor(activeContrastBorder);
|
||||
if (activeBorder) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .walkThroughContent .gettingStartedContainer button:hover { outline-color: ${activeBorder}; }`);
|
||||
}
|
||||
});
|
|
@ -0,0 +1,39 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
export default () => `
|
||||
<div class="gettingStartedContainer">
|
||||
<div class="gettingStarted" role="document">
|
||||
<div class="gettingStartedSlide categories">
|
||||
<div class="header">
|
||||
<div class="product-icon"></div>
|
||||
<div class="title">
|
||||
<h1 class="caption"><span id="product-name">${escape(localize('gettingStarted.vscode', "Visual Studio Code"))}</span></h1>
|
||||
<p class="subtitle description">${escape(localize({ key: 'gettingStarted.editingRedefined', comment: ['Shown as subtitle on the Welcome page.'] }, "Code editing. Redefined"))}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="getting-started-categories-container"></div>
|
||||
</div>
|
||||
<div class="gettingStartedSlide detail next">
|
||||
<a class="prev-button" x-dispatch="scrollPrev"><span
|
||||
class="scroll-button codicon codicon-chevron-left"></span>Back</a>
|
||||
<div id="getting-started-detail-columns">
|
||||
<div id="getting-started-detail-left">
|
||||
<div id="getting-started-detail-title"></div>
|
||||
<div id="getting-started-detail-container"></div>
|
||||
</div>
|
||||
<div id="getting-started-detail-right">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<a class="skip" x-dispatch="skip">Skip</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`.replace(/\|/g, '`');
|
|
@ -13,6 +13,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur
|
|||
import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor';
|
||||
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
|
||||
import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuration';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
|
||||
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
|
||||
.registerConfiguration({
|
||||
|
@ -21,13 +22,21 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
|
|||
'workbench.startupEditor': {
|
||||
'scope': ConfigurationScope.APPLICATION, // Make sure repositories cannot trigger opening a README for tracking.
|
||||
'type': 'string',
|
||||
'enum': ['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench'],
|
||||
'enumDescriptions': [
|
||||
'enum': [
|
||||
...['none', 'welcomePage', 'readme', 'newUntitledFile', 'welcomePageInEmptyWorkbench'],
|
||||
...(product.quality !== 'stable'
|
||||
? ['gettingStarted']
|
||||
: [])
|
||||
],
|
||||
'enumDescriptions': [...[
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page (default)."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.readme' }, "Open the README when opening a folder that contains one, fallback to 'welcomePage' otherwise."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty workspace)."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."),
|
||||
localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePageInEmptyWorkbench' }, "Open the Welcome page when opening an empty workbench."),],
|
||||
...(product.quality !== 'stable'
|
||||
? [localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.gettingStarted' }, "Open the Getting Started page (experimental).")]
|
||||
: [])
|
||||
],
|
||||
'default': 'welcomePage',
|
||||
'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.")
|
||||
|
|
|
@ -29,7 +29,7 @@ import { ILifecycleService, StartupKind } from 'vs/workbench/services/lifecycle/
|
|||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { splitName } from 'vs/base/common/labels';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils';
|
||||
import { IExtensionsViewPaneContainer, IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor';
|
||||
|
@ -46,6 +46,8 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
|||
import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { gettingStartedInputTypeId, GettingStartedPage } from 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted';
|
||||
import { buttonBackground, buttonHoverBackground, welcomePageBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePageColors';
|
||||
|
||||
const configurationKey = 'workbench.startupEditor';
|
||||
const oldConfigurationKey = 'workbench.welcome.enabled';
|
||||
|
@ -69,7 +71,8 @@ export class WelcomePageContribution implements IWorkbenchContribution {
|
|||
backupFileService.hasBackups().then(hasBackups => {
|
||||
// Open the welcome even if we opened a set of default editors
|
||||
if ((!editorService.activeEditor || layoutService.openedDefaultEditors) && !hasBackups) {
|
||||
const openWithReadme = configurationService.getValue(configurationKey) === 'readme';
|
||||
const startupEditorSetting = configurationService.getValue(configurationKey) as string;
|
||||
const openWithReadme = startupEditorSetting === 'readme';
|
||||
if (openWithReadme) {
|
||||
return Promise.all(contextService.getWorkspace().folders.map(folder => {
|
||||
const folderUri = folder.uri;
|
||||
|
@ -101,18 +104,21 @@ export class WelcomePageContribution implements IWorkbenchContribution {
|
|||
return undefined;
|
||||
});
|
||||
} else {
|
||||
const startupEditorTypeID = startupEditorSetting === 'gettingStarted' ? gettingStartedInputTypeId : welcomeInputTypeId;
|
||||
const startupEditorCtor = startupEditorSetting === 'gettingStarted' ? GettingStartedPage : WelcomePage;
|
||||
|
||||
let options: IEditorOptions;
|
||||
let editor = editorService.activeEditor;
|
||||
if (editor) {
|
||||
// Ensure that the welcome editor won't get opened more than once
|
||||
if (editor.getTypeId() === welcomeInputTypeId || editorService.editors.some(e => e.getTypeId() === welcomeInputTypeId)) {
|
||||
if (editor.getTypeId() === startupEditorTypeID || editorService.editors.some(e => e.getTypeId() === startupEditorTypeID)) {
|
||||
return undefined;
|
||||
}
|
||||
options = { pinned: false, index: 0 };
|
||||
} else {
|
||||
options = { pinned: false };
|
||||
}
|
||||
return instantiationService.createInstance(WelcomePage).openEditor(options);
|
||||
return instantiationService.createInstance(startupEditorCtor).openEditor(options);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
@ -129,7 +135,7 @@ function isWelcomePageEnabled(configurationService: IConfigurationService, conte
|
|||
return welcomeEnabled.value;
|
||||
}
|
||||
}
|
||||
return startupEditor.value === 'welcomePage' || startupEditor.value === 'readme' || startupEditor.value === 'welcomePageInEmptyWorkbench' && contextService.getWorkbenchState() === WorkbenchState.EMPTY;
|
||||
return startupEditor.value === 'welcomePage' || startupEditor.value === 'gettingStarted' || startupEditor.value === 'readme' || startupEditor.value === 'welcomePageInEmptyWorkbench' && contextService.getWorkbenchState() === WorkbenchState.EMPTY;
|
||||
}
|
||||
|
||||
export class WelcomePageAction extends Action {
|
||||
|
@ -639,10 +645,6 @@ export class WelcomeInputFactory implements IEditorInputFactory {
|
|||
|
||||
// theming
|
||||
|
||||
export const buttonBackground = registerColor('welcomePage.buttonBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonBackground', 'Background color for the buttons on the Welcome page.'));
|
||||
export const buttonHoverBackground = registerColor('welcomePage.buttonHoverBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonHoverBackground', 'Hover background color for the buttons on the Welcome page.'));
|
||||
export const welcomePageBackground = registerColor('welcomePage.background', { light: null, dark: null, hc: null }, localize('welcomePage.background', 'Background color for the Welcome page.'));
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const backgroundColor = theme.getColor(welcomePageBackground);
|
||||
if (backgroundColor) {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
// Seprate from main module to break dependency cycles between welcomePage and gettingStarted.
|
||||
export const buttonBackground = registerColor('welcomePage.buttonBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonBackground', 'Background color for the buttons on the Welcome page.'));
|
||||
export const buttonHoverBackground = registerColor('welcomePage.buttonHoverBackground', { dark: null, light: null, hc: null }, localize('welcomePage.buttonHoverBackground', 'Hover background color for the buttons on the Welcome page.'));
|
||||
export const welcomePageBackground = registerColor('welcomePage.background', { light: null, dark: null, hc: null }, localize('welcomePage.background', 'Background color for the Welcome page.'));
|
|
@ -0,0 +1,170 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
type GettingStartedItem = {
|
||||
id: string
|
||||
title: string,
|
||||
description: string,
|
||||
button: { title: string, command: string },
|
||||
doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never, }
|
||||
when?: string,
|
||||
media: string,
|
||||
};
|
||||
|
||||
type GettingStartedCategory = {
|
||||
id: string
|
||||
title: string,
|
||||
description: string,
|
||||
codicon: string,
|
||||
when?: string,
|
||||
content:
|
||||
| { type: 'items', items: GettingStartedItem[] }
|
||||
| { type: 'command', command: string }
|
||||
};
|
||||
|
||||
type GettingStartedContent = GettingStartedCategory[];
|
||||
|
||||
export const content: GettingStartedContent = [
|
||||
{
|
||||
id: 'Beginner',
|
||||
title: localize('gettingStarted.beginner.title', "Get Started"),
|
||||
codicon: 'lightbulb',
|
||||
description: localize('gettingStarted.beginner.description', "Get to know your new Editor"),
|
||||
content: {
|
||||
type: 'items',
|
||||
items: [
|
||||
{
|
||||
id: 'pickColorTheme',
|
||||
description: localize('pickColorTask.description', "Modify the colors in the user interface to suit your preferences and work environment."),
|
||||
title: localize('pickColorTask.title', "Color Theme"),
|
||||
button: { title: localize('pickColorTask.button', "Find a Theme"), command: 'workbench.action.selectTheme' },
|
||||
doneOn: { eventFired: 'themeSelected' },
|
||||
media: 'Square.png'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'findKeybindingsExtensions',
|
||||
description: localize('findKeybindingsTask.description', "Find keyboard shortcuts for Vim, Sublime, Atom and others."),
|
||||
title: localize('findKeybindingsTask.title', "Configure Keybindings"),
|
||||
button: {
|
||||
title: localize('findKeybindingsTask.button', "Search for Keymaps"),
|
||||
command: 'workbench.extensions.action.showRecommendedKeymapExtensions'
|
||||
},
|
||||
doneOn: { commandExecuted: 'workbench.extensions.action.showRecommendedKeymapExtensions' },
|
||||
media: 'Tall.png',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'findLanguageExtensions',
|
||||
description: localize('findLanguageExtsTask.description', "Get support for your languages like JavaScript, Python, Java, Azure, Docker, and more."),
|
||||
title: localize('findLanguageExtsTask.title', "Languages & Tools"),
|
||||
button: {
|
||||
title: localize('findLanguageExtsTask.button', "Install Language Support"),
|
||||
command: 'workbench.extensions.action.showLanguageExtensions',
|
||||
},
|
||||
doneOn: { commandExecuted: 'workbench.extensions.action.showLanguageExtensions' },
|
||||
media: 'Short.png',
|
||||
},
|
||||
|
||||
{
|
||||
id: 'pickAFolderTask-Mac',
|
||||
description: localize('gettingStartedOpenFolder.description', "Open a project folder to get started!"),
|
||||
title: localize('gettingStartedOpenFolder.title', "Open Folder"),
|
||||
when: 'isMac',
|
||||
button: {
|
||||
title: localize('gettingStartedOpenFolder.button', "Pick a Folder"),
|
||||
command: 'workbench.action.files.openFileFolder'
|
||||
},
|
||||
doneOn: { commandExecuted: 'workbench.action.files.openFileFolder' },
|
||||
media: 'Square.png'
|
||||
},
|
||||
|
||||
{
|
||||
id: 'pickAFolderTask-Other',
|
||||
description: localize('gettingStartedOpenFolder.description', "Open a project folder to get started!"),
|
||||
title: localize('gettingStartedOpenFolder.title', "Open Folder"),
|
||||
when: '!isMac',
|
||||
button: {
|
||||
title: localize('gettingStartedOpenFolder.button', "Pick a Folder"),
|
||||
command: 'workbench.action.files.openFolder'
|
||||
},
|
||||
doneOn: { commandExecuted: 'workbench.action.files.openFolder' },
|
||||
media: 'Square.png'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'Intermediate',
|
||||
title: localize('gettingStarted.intermediate.title', "Essentials"),
|
||||
codicon: 'heart',
|
||||
description: localize('gettingStarted.intermediate.description', "Must know features you'll love"),
|
||||
content: {
|
||||
type: 'items',
|
||||
items: [
|
||||
{
|
||||
id: 'commandPaletteTask',
|
||||
description: localize('commandPaletteTask.description', "The easiest way to find everything VS Code can do. If you\'re ever looking for a feature, check here first!"),
|
||||
title: localize('commandPaletteTask.title', "Command Palette"),
|
||||
button: {
|
||||
title: localize('commandPaletteTask.button', "View All Commands"),
|
||||
command: 'workbench.action.showCommands'
|
||||
},
|
||||
doneOn: { commandExecuted: 'workbench.action.showCommands' },
|
||||
media: 'https://code.visualstudio.com/assets/updates/1_51/custom-tree-hover.gif',
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'Advanced',
|
||||
title: localize('gettingStarted.advanced.title', "Tips & Tricks"),
|
||||
codicon: 'tools',
|
||||
description: localize('gettingStarted.advanced.description', "Favorites from VS Code experts"),
|
||||
content: {
|
||||
type: 'items',
|
||||
items: []
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'OpenFolder-Mac',
|
||||
title: localize('gettingStarted.openFolder.title', "Open Folder"),
|
||||
codicon: 'folder-opened',
|
||||
when: 'isMac',
|
||||
description: localize('gettingStarted.openFolder.description', "Open a project and start working"),
|
||||
content: {
|
||||
type: 'command',
|
||||
command: 'workbench.action.files.openFileFolder'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'OpenFolder-Other',
|
||||
title: localize('gettingStarted.openFolder.title', "Open Folder"),
|
||||
codicon: 'folder-opened',
|
||||
description: localize('gettingStarted.openFolder.description', "Open a project and start working"),
|
||||
when: '!isMac',
|
||||
content: {
|
||||
type: 'command',
|
||||
command: 'workbench.action.files.openFolder'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
id: 'InteractivePlayground',
|
||||
title: localize('gettingStarted.playground.title', "Interactive Playground"),
|
||||
codicon: 'library',
|
||||
description: localize('gettingStarted.interactivePlayground.description', "Learn essential editor features"),
|
||||
content: {
|
||||
type: 'command',
|
||||
command: 'workbench.action.showInteractivePlayground'
|
||||
}
|
||||
}
|
||||
];
|
|
@ -0,0 +1,146 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { FileAccess } from 'vs/base/common/network';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ContextKeyExpr, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { content } from 'vs/workbench/services/gettingStarted/common/gettingStartedContent';
|
||||
|
||||
export const enum GettingStartedCategory {
|
||||
Beginner = 'Beginner',
|
||||
Intermediate = 'Intermediate',
|
||||
Advanced = 'Advanced'
|
||||
}
|
||||
|
||||
export interface IGettingStartedTask {
|
||||
id: string,
|
||||
title: string,
|
||||
description: string,
|
||||
category: GettingStartedCategory | string,
|
||||
when: ContextKeyExpression,
|
||||
order: number,
|
||||
button: { title: string, command: string },
|
||||
doneOn: { commandExecuted: string, eventFired?: never } | { eventFired: string, commandExecuted?: never, }
|
||||
media: URI
|
||||
}
|
||||
|
||||
export interface IGettingStartedCategoryDescriptor {
|
||||
id: GettingStartedCategory | string
|
||||
title: string
|
||||
description: string
|
||||
codicon: string
|
||||
when: ContextKeyExpression
|
||||
content:
|
||||
| { type: 'items' }
|
||||
| { type: 'command', command: string }
|
||||
}
|
||||
|
||||
export interface IGettingStartedCategory {
|
||||
id: GettingStartedCategory | string
|
||||
title: string
|
||||
description: string
|
||||
codicon: string
|
||||
when: ContextKeyExpression
|
||||
content:
|
||||
| { type: 'items', items: IGettingStartedTask[] }
|
||||
| { type: 'command', command: string }
|
||||
}
|
||||
|
||||
export interface IGettingStartedRegistry {
|
||||
onDidAddCategory: Event<IGettingStartedCategory>
|
||||
onDidAddTask: Event<IGettingStartedTask>
|
||||
|
||||
registerTask(task: IGettingStartedTask): IGettingStartedTask;
|
||||
getTask(id: string): IGettingStartedTask
|
||||
|
||||
registerCategory(categoryDescriptor: IGettingStartedCategoryDescriptor): void
|
||||
getCategory(id: GettingStartedCategory | string): Readonly<IGettingStartedCategory> | undefined
|
||||
|
||||
getCategories(): readonly Readonly<IGettingStartedCategory>[]
|
||||
}
|
||||
|
||||
export class GettingStartedRegistryImpl implements IGettingStartedRegistry {
|
||||
private readonly _onDidAddTask = new Emitter<IGettingStartedTask>();
|
||||
onDidAddTask: Event<IGettingStartedTask> = this._onDidAddTask.event;
|
||||
private readonly _onDidAddCategory = new Emitter<IGettingStartedCategory>();
|
||||
onDidAddCategory: Event<IGettingStartedCategory> = this._onDidAddCategory.event;
|
||||
|
||||
private readonly gettingStartedContributions = new Map<string, IGettingStartedCategory>();
|
||||
private readonly tasks = new Map<string, IGettingStartedTask>();
|
||||
|
||||
public registerTask(task: IGettingStartedTask): IGettingStartedTask {
|
||||
const category = this.gettingStartedContributions.get(task.category);
|
||||
if (!category) { throw Error('Registering getting started task to category that does not exist (' + task.category + ')'); }
|
||||
if (category.content.type !== 'items') { throw Error('Registering getting started task to category that is not of `items` type (' + task.category + ')'); }
|
||||
if (this.tasks.has(task.id)) { throw Error('Attempting to register task with id ' + task.id + ' twice. Second is dropped.'); }
|
||||
this.tasks.set(task.id, task);
|
||||
category.content.items.push(task);
|
||||
this._onDidAddTask.fire(task);
|
||||
return task;
|
||||
}
|
||||
|
||||
public registerCategory(categoryDescriptor: IGettingStartedCategoryDescriptor): void {
|
||||
const oldCategory = this.gettingStartedContributions.get(categoryDescriptor.id);
|
||||
if (oldCategory) {
|
||||
console.error(`Skipping attempt to overwrite getting started category. (${categoryDescriptor})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const category: IGettingStartedCategory = {
|
||||
...categoryDescriptor,
|
||||
content: categoryDescriptor.content.type === 'items'
|
||||
? { type: 'items', items: [] }
|
||||
: categoryDescriptor.content
|
||||
};
|
||||
|
||||
this.gettingStartedContributions.set(categoryDescriptor.id, category);
|
||||
this._onDidAddCategory.fire(category);
|
||||
}
|
||||
|
||||
public getCategory(id: GettingStartedCategory | string): Readonly<IGettingStartedCategory> | undefined {
|
||||
return this.gettingStartedContributions.get(id);
|
||||
}
|
||||
|
||||
public getTask(id: string): IGettingStartedTask {
|
||||
const task = this.tasks.get(id);
|
||||
if (!task) { throw Error('Attempting to access task which does not exist in registry ' + id); }
|
||||
return task;
|
||||
}
|
||||
|
||||
public getCategories(): readonly Readonly<IGettingStartedCategory>[] {
|
||||
return [...this.gettingStartedContributions.values()];
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export const GettingStartedRegistryID = 'GettingStartedRegistry';
|
||||
const registryImpl = new GettingStartedRegistryImpl();
|
||||
|
||||
content.forEach(category => {
|
||||
|
||||
registryImpl.registerCategory({
|
||||
...category,
|
||||
when: ContextKeyExpr.deserialize(category.when) ?? ContextKeyExpr.true()
|
||||
});
|
||||
|
||||
if (category.content.type === 'items') {
|
||||
category.content.items.forEach((item, index) => {
|
||||
registryImpl.registerTask({
|
||||
...item,
|
||||
category: category.id,
|
||||
order: index,
|
||||
when: ContextKeyExpr.deserialize(item.when) ?? ContextKeyExpr.true(),
|
||||
media: item.media.startsWith('https://')
|
||||
? URI.parse(item.media, true)
|
||||
: FileAccess.asFileUri('vs/workbench/services/gettingStarted/common/media/' + item.media, require)
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Registry.add(GettingStartedRegistryID, registryImpl);
|
||||
export const GettingStartedRegistry: IGettingStartedRegistry = Registry.as(GettingStartedRegistryID);
|
|
@ -0,0 +1,196 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IGettingStartedTask, GettingStartedRegistry, IGettingStartedCategory, } from 'vs/workbench/services/gettingStarted/common/gettingStartedRegistry';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { Memento } from 'vs/workbench/common/memento';
|
||||
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
|
||||
export const IGettingStartedService = createDecorator<IGettingStartedService>('gettingStartedService');
|
||||
|
||||
type TaskProgress = { done: boolean; };
|
||||
export interface IGettingStartedTaskWithProgress extends IGettingStartedTask, TaskProgress { }
|
||||
|
||||
export interface IGettingStartedCategoryWithProgress extends Omit<IGettingStartedCategory, 'content'> {
|
||||
content:
|
||||
| {
|
||||
type: 'items',
|
||||
items: IGettingStartedTaskWithProgress[],
|
||||
done: boolean;
|
||||
stepsComplete: number
|
||||
stepsTotal: number
|
||||
}
|
||||
| { type: 'command', command: string }
|
||||
}
|
||||
|
||||
export interface IGettingStartedService {
|
||||
_serviceBrand: undefined,
|
||||
|
||||
readonly onDidAddTask: Event<IGettingStartedTaskWithProgress>
|
||||
readonly onDidAddCategory: Event<IGettingStartedCategoryWithProgress>
|
||||
|
||||
readonly onDidProgressTask: Event<IGettingStartedTaskWithProgress>
|
||||
|
||||
getCategories(): IGettingStartedCategoryWithProgress[]
|
||||
|
||||
progressByEvent(eventName: string): void;
|
||||
}
|
||||
|
||||
export class GettingStartedService implements IGettingStartedService {
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onDidAddTask = new Emitter<IGettingStartedTaskWithProgress>();
|
||||
onDidAddTask: Event<IGettingStartedTaskWithProgress> = this._onDidAddTask.event;
|
||||
private readonly _onDidAddCategory = new Emitter<IGettingStartedCategoryWithProgress>();
|
||||
onDidAddCategory: Event<IGettingStartedCategoryWithProgress> = this._onDidAddCategory.event;
|
||||
|
||||
private readonly _onDidProgressTask = new Emitter<IGettingStartedTaskWithProgress>();
|
||||
onDidProgressTask: Event<IGettingStartedTaskWithProgress> = this._onDidProgressTask.event;
|
||||
|
||||
private registry = GettingStartedRegistry;
|
||||
private memento: Memento;
|
||||
private taskProgress: Record<string, TaskProgress>;
|
||||
|
||||
private commandListeners = new Map<string, string[]>();
|
||||
private eventListeners = new Map<string, string[]>();
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IContextKeyService private readonly contextService: IContextKeyService,
|
||||
) {
|
||||
this.memento = new Memento('gettingStartedService', this.storageService);
|
||||
this.taskProgress = this.memento.getMemento(StorageScope.GLOBAL, StorageTarget.USER);
|
||||
|
||||
this.registry.getCategories().forEach(category => {
|
||||
if (category.content.type === 'items') {
|
||||
category.content.items.forEach(task => this.registerDoneListeners(task));
|
||||
}
|
||||
});
|
||||
|
||||
this.registry.onDidAddCategory(category => this._onDidAddCategory.fire(this.getCategoryProgress(category)));
|
||||
this.registry.onDidAddTask(task => {
|
||||
this.registerDoneListeners(task);
|
||||
this._onDidAddTask.fire(this.getTaskProgress(task));
|
||||
});
|
||||
|
||||
this.commandService.onDidExecuteCommand(command => this.progressByCommand(command.commandId));
|
||||
}
|
||||
|
||||
private registerDoneListeners(task: IGettingStartedTask) {
|
||||
if (task.doneOn.commandExecuted) {
|
||||
const existing = this.commandListeners.get(task.doneOn.commandExecuted);
|
||||
if (existing) { existing.push(task.id); }
|
||||
else {
|
||||
this.commandListeners.set(task.doneOn.commandExecuted, [task.id]);
|
||||
}
|
||||
}
|
||||
if (task.doneOn.eventFired) {
|
||||
const existing = this.eventListeners.get(task.doneOn.eventFired);
|
||||
if (existing) { existing.push(task.id); }
|
||||
else {
|
||||
this.eventListeners.set(task.doneOn.eventFired, [task.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getCategories(): IGettingStartedCategoryWithProgress[] {
|
||||
const registeredCategories = this.registry.getCategories();
|
||||
const categoriesWithCompletion = registeredCategories
|
||||
.filter(category => this.contextService.contextMatchesRules(category.when))
|
||||
.map(category => {
|
||||
if (category.content.type === 'items') {
|
||||
return {
|
||||
...category,
|
||||
content: {
|
||||
type: 'items' as const,
|
||||
items: category.content.items.filter(item => this.contextService.contextMatchesRules(item.when))
|
||||
}
|
||||
};
|
||||
}
|
||||
return category;
|
||||
})
|
||||
.filter(category => category.content.type !== 'items' || category.content.items.length)
|
||||
.map(category => this.getCategoryProgress(category));
|
||||
return categoriesWithCompletion;
|
||||
}
|
||||
|
||||
private getCategoryProgress(category: IGettingStartedCategory): IGettingStartedCategoryWithProgress {
|
||||
if (category.content.type === 'command') {
|
||||
return { ...category, content: category.content };
|
||||
}
|
||||
|
||||
const tasksWithProgress = category.content.items.map(task => this.getTaskProgress(task));
|
||||
const tasksComplete = tasksWithProgress.filter(task => task.done);
|
||||
|
||||
return {
|
||||
...category,
|
||||
content: {
|
||||
type: 'items',
|
||||
items: tasksWithProgress,
|
||||
stepsComplete: tasksComplete.length,
|
||||
stepsTotal: tasksWithProgress.length,
|
||||
done: tasksComplete.length === tasksWithProgress.length,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getTaskProgress(task: IGettingStartedTask): IGettingStartedTaskWithProgress {
|
||||
return {
|
||||
...task,
|
||||
...this.taskProgress[task.id]
|
||||
};
|
||||
}
|
||||
|
||||
private progressTask(id: string) {
|
||||
const oldProgress = this.taskProgress[id];
|
||||
if (!oldProgress || oldProgress.done !== true) {
|
||||
this.taskProgress[id] = { done: true };
|
||||
this.memento.saveMemento();
|
||||
const task = this.registry.getTask(id);
|
||||
this._onDidProgressTask.fire(this.getTaskProgress(task));
|
||||
}
|
||||
}
|
||||
|
||||
private progressByCommand(command: string) {
|
||||
const listening = this.commandListeners.get(command) ?? [];
|
||||
listening.forEach(id => this.progressTask(id));
|
||||
}
|
||||
|
||||
progressByEvent(event: string): void {
|
||||
const listening = this.eventListeners.get(event) ?? [];
|
||||
console.log(event, listening, this.eventListeners);
|
||||
listening.forEach(id => this.progressTask(id));
|
||||
}
|
||||
}
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'resetGettingStartedProgress',
|
||||
category: 'Getting Started',
|
||||
title: 'Reset Progress',
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor) {
|
||||
const memento = new Memento('gettingStartedService', accessor.get(IStorageService));
|
||||
const record = memento.getMemento(StorageScope.GLOBAL, StorageTarget.USER);
|
||||
for (const key in record) {
|
||||
if (Object.prototype.hasOwnProperty.call(record, key)) {
|
||||
delete record[key];
|
||||
}
|
||||
}
|
||||
memento.saveMemento();
|
||||
}
|
||||
});
|
||||
|
||||
registerSingleton(IGettingStartedService, GettingStartedService);
|
BIN
src/vs/workbench/services/gettingStarted/common/media/Short.png
Normal file
BIN
src/vs/workbench/services/gettingStarted/common/media/Short.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 173 KiB |
BIN
src/vs/workbench/services/gettingStarted/common/media/Square.png
Normal file
BIN
src/vs/workbench/services/gettingStarted/common/media/Square.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 179 KiB |
BIN
src/vs/workbench/services/gettingStarted/common/media/Tall.png
Normal file
BIN
src/vs/workbench/services/gettingStarted/common/media/Tall.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 84 KiB |
|
@ -276,6 +276,7 @@ import 'vs/workbench/contrib/surveys/browser/languageSurveys.contribution';
|
|||
// Welcome
|
||||
import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay';
|
||||
import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution';
|
||||
import 'vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.contribution';
|
||||
import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution';
|
||||
|
||||
// Call Hierarchy
|
||||
|
|
Loading…
Reference in a new issue