Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rendering svg and md in welcome message #183580

Merged
merged 1 commit into from May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -11,7 +11,7 @@
border-radius: 6px;
}

.dialog-message-detail-title{
.dialog-message-detail-title {
height: 22px;
padding-bottom: 4px;
font-size: large;
Expand Down
Expand Up @@ -16,6 +16,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ICommandService } from 'vs/platform/commands/common/commands';
import { WelcomeWidget } from 'vs/workbench/contrib/welcomeDialog/browser/welcomeWidget';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';
import { IFileService } from 'vs/platform/files/common/files';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { LanguageService } from 'vs/editor/common/services/languageService';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { GettingStartedDetailsRenderer } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer';

const configurationKey = 'welcome.experimental.dialog';

Expand All @@ -31,7 +39,13 @@ class WelcomeDialogContribution extends Disposable implements IWorkbenchContribu
@ICodeEditorService readonly codeEditorService: ICodeEditorService,
@IInstantiationService readonly instantiationService: IInstantiationService,
@ICommandService readonly commandService: ICommandService,
@ITelemetryService readonly telemetryService: ITelemetryService
@ITelemetryService readonly telemetryService: ITelemetryService,
@IOpenerService readonly openerService: IOpenerService,
@IWebviewService readonly webviewService: IWebviewService,
@IFileService readonly fileService: IFileService,
@INotificationService readonly notificationService: INotificationService,
@IExtensionService readonly extensionService: IExtensionService,
@ILanguageService readonly languageService: LanguageService
) {
super();

Expand All @@ -56,12 +70,24 @@ class WelcomeDialogContribution extends Disposable implements IWorkbenchContribu
Array.from(this.contextKeysToWatch).every(value => this.contextService.contextMatchesRules(ContextKeyExpr.deserialize(value)))) {
const codeEditor = this.codeEditorService.getActiveCodeEditor();
if (codeEditor?.hasModel()) {
const welcomeWidget = new WelcomeWidget(codeEditor, instantiationService, commandService, telemetryService);

const detailsRenderer = new GettingStartedDetailsRenderer(fileService, notificationService, extensionService, languageService);

const welcomeWidget = new WelcomeWidget(
codeEditor,
instantiationService,
commandService,
telemetryService,
openerService,
webviewService,
detailsRenderer);

welcomeWidget.render(welcomeDialog.title,
welcomeDialog.message,
welcomeDialog.buttonText,
welcomeDialog.buttonCommand,
welcomeDialog.media);

this.contextKeysToWatch.delete(welcomeDialog.when);
}
}
Expand Down
73 changes: 64 additions & 9 deletions src/vs/workbench/contrib/welcomeDialog/browser/welcomeWidget.ts
Expand Up @@ -6,7 +6,7 @@
import 'vs/css!./media/welcomeWidget';
import { Disposable } from 'vs/base/common/lifecycle';
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
import { $, hide } from 'vs/base/browser/dom'; import { RunOnceScheduler } from 'vs/base/common/async';
import { $, append, hide } from 'vs/base/browser/dom'; import { RunOnceScheduler } from 'vs/base/common/async';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { MarkdownRenderer } from 'vs/editor/contrib/markdownRenderer/browser/markdownRenderer';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
Expand All @@ -20,9 +20,21 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { localize } from 'vs/nls';
import { ThemeIcon } from 'vs/base/common/themables';
import { Codicon } from 'vs/base/common/codicons';
import { LinkedText, parseLinkedText } from 'vs/base/common/linkedText';
import { Link } from 'vs/platform/opener/browser/link';
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
import { renderFormattedText } from 'vs/base/browser/formattedTextRenderer';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { generateUuid } from 'vs/base/common/uuid';
import { GettingStartedDetailsRenderer } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedDetailsRenderer';
import { FileAccess } from 'vs/base/common/network';
import { IWebviewService } from 'vs/workbench/contrib/webview/browser/webview';

export class WelcomeWidget extends Disposable implements IOverlayWidget {

private static readonly WIDGET_TIMEOUT: number = 15000;
private static readonly WELCOME_MEDIA_PATH = 'vs/workbench/contrib/welcomeGettingStarted/common/media/';

private readonly _rootDomNode: HTMLElement;
private readonly element: HTMLElement;
private readonly messageContainer: HTMLElement;
Expand All @@ -33,6 +45,9 @@ export class WelcomeWidget extends Disposable implements IOverlayWidget {
private readonly instantiationService: IInstantiationService,
private readonly commandService: ICommandService,
private readonly telemetryService: ITelemetryService,
private readonly openerService: IOpenerService,
private readonly webviewService: IWebviewService,
private readonly detailsRenderer: GettingStartedDetailsRenderer
) {
super();
this._rootDomNode = document.createElement('div');
Expand Down Expand Up @@ -82,10 +97,9 @@ export class WelcomeWidget extends Disposable implements IOverlayWidget {
}));
actionBar.push(action, { icon: true, label: false });


const messageTitleElement = this.messageContainer.appendChild($('.dialog-message-title'));
messageTitleElement.style.display = 'contents';
messageTitleElement.style.alignContent = 'start';
if (media) {
this.buildSVGMediaComponent(media.path);
}

const renderBody = (message: string): MarkdownString => {
const mds = new MarkdownString(undefined, { supportHtml: true });
Expand All @@ -97,9 +111,7 @@ export class WelcomeWidget extends Disposable implements IOverlayWidget {
const titleElementMdt = this.markdownRenderer.render(renderBody(title));
titleElement.appendChild(titleElementMdt.element);

const messageElement = this.messageContainer.appendChild($('#monaco-dialog-message-detail.dialog-message-detail-message'));
const messageElementMd = this.markdownRenderer.render(renderBody(message));
messageElement.appendChild(messageElementMd.element);
this.buildStepMarkdownDescription(this.messageContainer, message.split('\n').filter(x => x).map(text => parseLinkedText(text)));

const buttonsRowElement = this.messageContainer.appendChild($('.dialog-buttons-row'));
const buttonContainer = buttonsRowElement.appendChild($('.dialog-buttons'));
Expand All @@ -116,6 +128,49 @@ export class WelcomeWidget extends Disposable implements IOverlayWidget {
this.applyStyles();
}

private buildStepMarkdownDescription(container: HTMLElement, text: LinkedText[]) {
for (const linkedText of text) {
const p = append(container, $('p'));
for (const node of linkedText.nodes) {
if (typeof node === 'string') {
const labelWithIcon = renderLabelWithIcons(node);
for (const element of labelWithIcon) {
if (typeof element === 'string') {
p.appendChild(renderFormattedText(element, { inline: true, renderCodeSegments: true }));
} else {
p.appendChild(element);
}
}
} else {
const link = this.instantiationService.createInstance(Link, p, node, {
opener: (href: string) => {
this.telemetryService.publicLog2<WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification>('workbenchActionExecuted', {
id: 'welcomeWidetLinkAction',
from: 'welcomeWidget'
});
this.openerService.open(href, { allowCommands: true });
}
});
this._register(link);
}
}
}
return container;
}

private buildSVGMediaComponent(path: string) {

const mediaContainer = this.messageContainer.appendChild($('.dialog-image-container'));
mediaContainer.id = generateUuid();

const webview = this._register(this.webviewService.createWebviewElement({ title: undefined, options: {}, contentOptions: {}, extension: undefined }));
webview.mountTo(mediaContainer);

this.detailsRenderer.renderSVG(FileAccess.asFileUri(`${WelcomeWidget.WELCOME_MEDIA_PATH}${path}`)).then(body => {
webview.setHtml(body);
});
}

getId(): string {
return 'editor.contrib.welcomeWidget';
}
Expand All @@ -130,7 +185,7 @@ export class WelcomeWidget extends Disposable implements IOverlayWidget {
};
}

private _hideSoon = this._register(new RunOnceScheduler(() => this._hide(false), 30000));
private _hideSoon = this._register(new RunOnceScheduler(() => this._hide(false), WelcomeWidget.WIDGET_TIMEOUT));
private _isVisible: boolean = false;

private _revealTemporarily(): void {
Expand Down