mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-03 17:23:42 -05:00
Merge from vscode a348d103d1256a06a2c9b3f9b406298a9fef6898 (#15681)
* Merge from vscode a348d103d1256a06a2c9b3f9b406298a9fef6898 * Fixes and cleanup * Distro * Fix hygiene yarn * delete no yarn lock changes file * Fix hygiene * Fix layer check * Fix CI * Skip lib checks * Remove tests deleted in vs code * Fix tests * Distro * Fix tests and add removed extension point * Skip failing notebook tests for now * Disable broken tests and cleanup build folder * Update yarn.lock and fix smoke tests * Bump sqlite * fix contributed actions and file spacing * Fix user data path * Update yarn.locks Co-authored-by: ADS Merger <karlb@microsoft.com>
This commit is contained in:
@@ -4,26 +4,47 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Load issue reporter into window
|
||||
bootstrapWindow.load(['vs/code/electron-sandbox/issue/issueReporterMain'], function (issueReporter, configuration) {
|
||||
issueReporter.startup(configuration);
|
||||
}, { forceEnableDeveloperKeybindings: true, disallowReloadKeybinding: true });
|
||||
|
||||
|
||||
//#region Globals
|
||||
return issueReporter.startup(configuration);
|
||||
},
|
||||
{
|
||||
configureDeveloperSettings: function () {
|
||||
return {
|
||||
forceEnableDeveloperKeybindings: true,
|
||||
disallowReloadKeybinding: true
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }}
|
||||
* @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration
|
||||
*
|
||||
* @returns {{
|
||||
* load: (
|
||||
* modules: string[],
|
||||
* resultCallback: (result, configuration: ISandboxConfiguration) => unknown,
|
||||
* options?: {
|
||||
* configureDeveloperSettings?: (config: ISandboxConfiguration) => {
|
||||
* forceEnableDeveloperKeybindings?: boolean,
|
||||
* disallowReloadKeybinding?: boolean,
|
||||
* removeDeveloperKeybindingsAfterLoad?: boolean
|
||||
* },
|
||||
* canModifyDOM?: (config: ISandboxConfiguration) => void,
|
||||
* beforeLoaderConfig?: (loaderConfig: object) => void,
|
||||
* beforeRequire?: () => void
|
||||
* }
|
||||
* ) => Promise<unknown>
|
||||
* }}
|
||||
*/
|
||||
function bootstrapWindowLib() {
|
||||
// @ts-ignore (defined in bootstrap-window.js)
|
||||
return window.MonacoBootstrapWindow;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}());
|
||||
|
||||
@@ -11,10 +11,10 @@ import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window';
|
||||
import { $, reset, safeInnerHtml, windowOpenNoOpener } from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import * as collections from 'vs/base/common/collections';
|
||||
import { groupBy } from 'vs/base/common/collections';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { isWindows, isLinux, isLinuxSnap, isMacintosh } from 'vs/base/common/platform';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { normalizeGitHubUrl } from 'vs/platform/issue/common/issueReporterUtil';
|
||||
import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-sandbox/issue/issueReporterModel';
|
||||
@@ -22,11 +22,11 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
|
||||
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
|
||||
import { IssueReporterWindowConfiguration, IssueReporterData, IssueReporterExtensionData, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue';
|
||||
import { Codicon } from 'vs/base/common/codicons';
|
||||
import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
|
||||
const MAX_URL_LENGTH = 2045;
|
||||
|
||||
@@ -36,27 +36,14 @@ interface SearchResult {
|
||||
state?: string;
|
||||
}
|
||||
|
||||
export interface IssueReporterConfiguration extends IWindowConfiguration {
|
||||
windowId: number;
|
||||
disableExtensions: boolean;
|
||||
data: IssueReporterData;
|
||||
features: IssueReporterFeatures;
|
||||
os: {
|
||||
type: string;
|
||||
arch: string;
|
||||
release: string;
|
||||
},
|
||||
product: {
|
||||
nameShort: string;
|
||||
version: string;
|
||||
commit: string | undefined;
|
||||
date: string | undefined;
|
||||
reportIssueUrl: string | undefined;
|
||||
}
|
||||
enum IssueSource {
|
||||
VSCode = 'vscode',
|
||||
Extension = 'extension',
|
||||
Marketplace = 'marketplace'
|
||||
}
|
||||
|
||||
export function startup(configuration: IssueReporterConfiguration) {
|
||||
const platformClass = platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac';
|
||||
export function startup(configuration: IssueReporterWindowConfiguration) {
|
||||
const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac';
|
||||
document.body.classList.add(platformClass); // used by our fonts
|
||||
|
||||
safeInnerHtml(document.body, BaseHtml());
|
||||
@@ -78,7 +65,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private readonly previewButton!: Button;
|
||||
|
||||
constructor(private readonly configuration: IssueReporterConfiguration) {
|
||||
constructor(private readonly configuration: IssueReporterWindowConfiguration) {
|
||||
super();
|
||||
|
||||
this.initServices(configuration);
|
||||
@@ -87,8 +74,8 @@ export class IssueReporter extends Disposable {
|
||||
this.issueReporterModel = new IssueReporterModel({
|
||||
issueType: configuration.data.issueType || IssueType.Bug,
|
||||
versionInfo: {
|
||||
vscodeVersion: `${configuration.product.nameShort} ${configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`,
|
||||
os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${platform.isLinuxSnap ? ' snap' : ''}`
|
||||
vscodeVersion: `${configuration.product.nameShort} ${!!configuration.product.darwinUniversalAssetId ? `${configuration.product.version} (Universal)` : configuration.product.version} (${configuration.product.commit || 'Commit unknown'}, ${configuration.product.date || 'Date unknown'})`,
|
||||
os: `${this.configuration.os.type} ${this.configuration.os.arch} ${this.configuration.os.release}${isLinuxSnap ? ' snap' : ''}`
|
||||
},
|
||||
extensionsDisabled: !!configuration.disableExtensions,
|
||||
fileOnExtension: configuration.data.extensionId ? !targetExtension?.isBuiltin : undefined,
|
||||
@@ -249,7 +236,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private handleExtensionData(extensions: IssueReporterExtensionData[]) {
|
||||
const installedExtensions = extensions.filter(x => !x.isBuiltin);
|
||||
const { nonThemes, themes } = collections.groupBy(installedExtensions, ext => {
|
||||
const { nonThemes, themes } = groupBy(installedExtensions, ext => {
|
||||
return ext.isTheme ? 'themes' : 'nonThemes';
|
||||
});
|
||||
|
||||
@@ -264,7 +251,7 @@ export class IssueReporter extends Disposable {
|
||||
this.updateExtensionSelector(installedExtensions);
|
||||
}
|
||||
|
||||
private initServices(configuration: IssueReporterConfiguration): void {
|
||||
private initServices(configuration: IssueReporterWindowConfiguration): void {
|
||||
const serviceCollection = new ServiceCollection();
|
||||
const mainProcessService = new ElectronIPCMainProcessService(configuration.windowId);
|
||||
serviceCollection.set(IMainProcessService, mainProcessService);
|
||||
@@ -325,17 +312,18 @@ export class IssueReporter extends Disposable {
|
||||
hide(problemSourceHelpText);
|
||||
}
|
||||
|
||||
const fileOnExtension = JSON.parse(value);
|
||||
this.issueReporterModel.update({ fileOnExtension: fileOnExtension });
|
||||
let fileOnExtension, fileOnMarketplace = false;
|
||||
if (value === IssueSource.Extension) {
|
||||
fileOnExtension = true;
|
||||
} else if (value === IssueSource.Marketplace) {
|
||||
fileOnMarketplace = true;
|
||||
}
|
||||
|
||||
this.issueReporterModel.update({ fileOnExtension, fileOnMarketplace });
|
||||
this.render();
|
||||
|
||||
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
if (fileOnExtension) {
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
this.searchIssues(title, fileOnExtension, fileOnMarketplace);
|
||||
});
|
||||
|
||||
this.addEventListener('description', 'input', (e: Event) => {
|
||||
@@ -352,23 +340,19 @@ export class IssueReporter extends Disposable {
|
||||
this.addEventListener('issue-title', 'input', (e: Event) => {
|
||||
const title = (<HTMLInputElement>e.target).value;
|
||||
const lengthValidationMessage = this.getElementById('issue-title-length-validation-error');
|
||||
if (title && this.getIssueUrlWithTitle(title).length > MAX_URL_LENGTH) {
|
||||
const issueUrl = this.getIssueUrl();
|
||||
if (title && this.getIssueUrlWithTitle(title, issueUrl).length > MAX_URL_LENGTH) {
|
||||
show(lengthValidationMessage);
|
||||
} else {
|
||||
hide(lengthValidationMessage);
|
||||
}
|
||||
|
||||
const fileOnExtension = this.issueReporterModel.fileOnExtension();
|
||||
if (fileOnExtension === undefined) {
|
||||
const issueSource = this.getElementById<HTMLSelectElement>('issue-source');
|
||||
if (!issueSource || issueSource.value === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileOnExtension) {
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
const { fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData();
|
||||
this.searchIssues(title, fileOnExtension, fileOnMarketplace);
|
||||
});
|
||||
|
||||
this.previewButton.onDidClick(() => this.createIssue());
|
||||
@@ -395,7 +379,7 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
|
||||
document.onkeydown = async (e: KeyboardEvent) => {
|
||||
const cmdOrCtrlKey = platform.isMacintosh ? e.metaKey : e.ctrlKey;
|
||||
const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey;
|
||||
// Cmd/Ctrl+Enter previews issue and closes window
|
||||
if (cmdOrCtrlKey && e.keyCode === 13) {
|
||||
if (await this.createIssue()) {
|
||||
@@ -429,7 +413,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
// With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac
|
||||
// Manually perform the selection
|
||||
if (platform.isMacintosh) {
|
||||
if (isMacintosh) {
|
||||
if (cmdOrCtrlKey && e.keyCode === 65 && e.target) {
|
||||
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) {
|
||||
(<HTMLInputElement>e.target).select();
|
||||
@@ -488,6 +472,19 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private searchIssues(title: string, fileOnExtension: boolean | undefined, fileOnMarketplace: boolean | undefined): void {
|
||||
if (fileOnExtension) {
|
||||
return this.searchExtensionIssues(title);
|
||||
}
|
||||
|
||||
if (fileOnMarketplace) {
|
||||
return this.searchMarketplaceIssues(title);
|
||||
}
|
||||
|
||||
const description = this.issueReporterModel.getData().issueDescription;
|
||||
this.searchVSCodeIssues(title, description);
|
||||
}
|
||||
|
||||
private searchExtensionIssues(title: string): void {
|
||||
const url = this.getExtensionGitHubUrl();
|
||||
if (title) {
|
||||
@@ -508,6 +505,15 @@ export class IssueReporter extends Disposable {
|
||||
this.clearSearchResults();
|
||||
}
|
||||
|
||||
private searchMarketplaceIssues(title: string): void {
|
||||
if (title) {
|
||||
const gitHubInfo = this.parseGitHubUrl(this.configuration.product.reportMarketplaceIssueUrl!);
|
||||
if (gitHubInfo) {
|
||||
return this.searchGitHub(`${gitHubInfo.owner}/${gitHubInfo.repositoryName}`, title);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private clearSearchResults(): void {
|
||||
const similarIssues = this.getElementById('similar-issues')!;
|
||||
similarIssues.innerText = '';
|
||||
@@ -635,7 +641,7 @@ export class IssueReporter extends Disposable {
|
||||
reset(typeSelect,
|
||||
makeOption(IssueType.Bug, localize('bugReporter', "Bug Report")),
|
||||
makeOption(IssueType.FeatureRequest, localize('featureRequest', "Feature Request")),
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue"))
|
||||
makeOption(IssueType.PerformanceIssue, localize('performanceIssue', "Performance Issue")),
|
||||
);
|
||||
|
||||
typeSelect.value = issueType.toString();
|
||||
@@ -665,19 +671,15 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
sourceSelect.innerText = '';
|
||||
if (issueType === IssueType.FeatureRequest) {
|
||||
sourceSelect.append(...[
|
||||
this.makeOption('', localize('selectSource', "Select source"), true),
|
||||
this.makeOption('false', localize('azuredatastudio', "Azure Data Studio"), false),
|
||||
this.makeOption('true', localize('extension', "An extension"), false)
|
||||
]);
|
||||
} else {
|
||||
sourceSelect.append(...[
|
||||
this.makeOption('', localize('selectSource', "Select source"), true),
|
||||
this.makeOption('false', localize('azuredatastudio', "Azure Data Studio"), false),
|
||||
this.makeOption('true', localize('extension', "An extension"), false),
|
||||
this.makeOption('', localize('unknown', "Don't Know"), false)
|
||||
]);
|
||||
sourceSelect.append(this.makeOption('', localize('selectSource', "Select source"), true));
|
||||
sourceSelect.append(this.makeOption('azuredatastudio', localize('azuredatastudio', "Azure Data Studio"), false)); // {{SQL CARBON EDIT}} Update name
|
||||
sourceSelect.append(this.makeOption('extension', localize('extension', "An extension"), false));
|
||||
if (this.configuration.product.reportMarketplaceIssueUrl) {
|
||||
sourceSelect.append(this.makeOption('marketplace', localize('marketplace', "Extensions marketplace"), false));
|
||||
}
|
||||
|
||||
if (issueType !== IssueType.FeatureRequest) {
|
||||
sourceSelect.append(this.makeOption('', localize('unknown', "Don't know"), false));
|
||||
}
|
||||
|
||||
if (selected !== -1 && selected < sourceSelect.options.length) {
|
||||
@@ -690,7 +692,7 @@ export class IssueReporter extends Disposable {
|
||||
|
||||
private renderBlocks(): void {
|
||||
// Depending on Issue Type, we render different blocks and text
|
||||
const { issueType, fileOnExtension } = this.issueReporterModel.getData();
|
||||
const { issueType, fileOnExtension, fileOnMarketplace } = this.issueReporterModel.getData();
|
||||
const blockContainer = this.getElementById('block-container');
|
||||
const systemBlock = document.querySelector('.block-system');
|
||||
const processBlock = document.querySelector('.block-process');
|
||||
@@ -714,29 +716,35 @@ export class IssueReporter extends Disposable {
|
||||
hide(extensionSelector);
|
||||
|
||||
if (issueType === IssueType.Bug) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(problemSource);
|
||||
show(experimentsBlock);
|
||||
|
||||
if (!fileOnMarketplace) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(experimentsBlock);
|
||||
}
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
} else if (!fileOnMarketplace) {
|
||||
show(extensionsBlock);
|
||||
}
|
||||
reset(descriptionTitle, localize('stepsToReproduce', "Steps to Reproduce"), $('span.required-input', undefined, '*'));
|
||||
reset(descriptionSubtitle, localize('bugDescription', "Share the steps needed to reliably reproduce the problem. Please include actual and expected results. We support GitHub-flavored Markdown. You will be able to edit your issue and add screenshots when we preview it on GitHub."));
|
||||
} else if (issueType === IssueType.PerformanceIssue) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(problemSource);
|
||||
show(experimentsBlock);
|
||||
|
||||
if (!fileOnMarketplace) {
|
||||
show(blockContainer);
|
||||
show(systemBlock);
|
||||
show(processBlock);
|
||||
show(workspaceBlock);
|
||||
show(experimentsBlock);
|
||||
}
|
||||
|
||||
if (fileOnExtension) {
|
||||
show(extensionSelector);
|
||||
} else {
|
||||
} else if (!fileOnMarketplace) {
|
||||
show(extensionsBlock);
|
||||
}
|
||||
|
||||
@@ -844,13 +852,13 @@ export class IssueReporter extends Disposable {
|
||||
const issueTitle = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
const issueBody = this.issueReporterModel.serialize();
|
||||
|
||||
const issueUrl = this.issueReporterModel.fileOnExtension() ? this.getExtensionGitHubUrl() : this.configuration.product.reportIssueUrl!;
|
||||
const issueUrl = this.getIssueUrl();
|
||||
const gitHubDetails = this.parseGitHubUrl(issueUrl);
|
||||
if (this.configuration.data.githubAccessToken && gitHubDetails) {
|
||||
return this.submitToGitHub(issueTitle, issueBody, gitHubDetails);
|
||||
}
|
||||
|
||||
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value);
|
||||
const baseUrl = this.getIssueUrlWithTitle((<HTMLInputElement>this.getElementById('issue-title')).value, issueUrl);
|
||||
let url = baseUrl + `&body=${encodeURIComponent(issueBody)}`;
|
||||
|
||||
if (url.length > MAX_URL_LENGTH) {
|
||||
@@ -880,6 +888,14 @@ export class IssueReporter extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private getIssueUrl(): string {
|
||||
return this.issueReporterModel.fileOnExtension()
|
||||
? this.getExtensionGitHubUrl()
|
||||
: this.issueReporterModel.getData().fileOnMarketplace
|
||||
? this.configuration.product.reportMarketplaceIssueUrl!
|
||||
: this.configuration.product.reportIssueUrl!;
|
||||
}
|
||||
|
||||
private parseGitHubUrl(url: string): undefined | { repositoryName: string, owner: string } {
|
||||
// Assumes a GitHub url to a particular repo, https://github.com/repositoryName/owner.
|
||||
// Repository name and owner cannot contain '/'
|
||||
@@ -908,16 +924,12 @@ export class IssueReporter extends Disposable {
|
||||
return repositoryUrl;
|
||||
}
|
||||
|
||||
private getIssueUrlWithTitle(issueTitle: string): string {
|
||||
let repositoryUrl = this.configuration.product.reportIssueUrl;
|
||||
private getIssueUrlWithTitle(issueTitle: string, repositoryUrl: string): string {
|
||||
if (this.issueReporterModel.fileOnExtension()) {
|
||||
const extensionGitHubUrl = this.getExtensionGitHubUrl();
|
||||
if (extensionGitHubUrl) {
|
||||
repositoryUrl = extensionGitHubUrl + '/issues/new';
|
||||
}
|
||||
repositoryUrl = repositoryUrl + '/issues/new';
|
||||
}
|
||||
|
||||
const queryStringPrefix = repositoryUrl && repositoryUrl.indexOf('?') === -1 ? '?' : '&'; // {{SQL CARBON EDIT}}
|
||||
const queryStringPrefix = repositoryUrl.indexOf('?') === -1 ? '?' : '&';
|
||||
return `${repositoryUrl}${queryStringPrefix}title=${encodeURIComponent(issueTitle)}`;
|
||||
}
|
||||
|
||||
@@ -1155,7 +1167,7 @@ export class IssueReporter extends Disposable {
|
||||
),
|
||||
...extensions.map(extension => $('tr', undefined,
|
||||
$('td', undefined, extension.name),
|
||||
$('td', undefined, extension.publisher.substr(0, 3)),
|
||||
$('td', undefined, extension.publisher?.substr(0, 3) ?? 'N/A'),
|
||||
$('td', undefined, extension.version),
|
||||
))
|
||||
);
|
||||
|
||||
@@ -26,6 +26,7 @@ export interface IssueReporterData {
|
||||
enabledNonThemeExtesions?: IssueReporterExtensionData[];
|
||||
extensionsDisabled?: boolean;
|
||||
fileOnExtension?: boolean;
|
||||
fileOnMarketplace?: boolean;
|
||||
selectedExtension?: IssueReporterExtensionData;
|
||||
actualSearchResults?: ISettingSearchResult[];
|
||||
query?: string;
|
||||
@@ -111,30 +112,30 @@ ${this.getInfos()}
|
||||
let info = '';
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeSystemInfo && this._data.systemInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeSystemInfo && this._data.systemInfo) {
|
||||
info += this.generateSystemInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.PerformanceIssue) {
|
||||
|
||||
if (this._data.includeProcessInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeProcessInfo) {
|
||||
info += this.generateProcessInfoMd();
|
||||
}
|
||||
|
||||
if (this._data.includeWorkspaceInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeWorkspaceInfo) {
|
||||
info += this.generateWorkspaceInfoMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (!this._data.fileOnExtension && this._data.includeExtensions) {
|
||||
if (!this._data.fileOnMarketplace && !this._data.fileOnExtension && this._data.includeExtensions) {
|
||||
info += this.generateExtensionsMd();
|
||||
}
|
||||
}
|
||||
|
||||
if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) {
|
||||
if (this._data.includeExperiments && this._data.experimentInfo) {
|
||||
if (!this._data.fileOnMarketplace && this._data.includeExperiments && this._data.experimentInfo) {
|
||||
info += this.generateExperimentsInfoMd();
|
||||
}
|
||||
}
|
||||
@@ -239,7 +240,7 @@ ${this._data.experimentInfo}
|
||||
const tableHeader = `Extension|Author (truncated)|Version
|
||||
---|---|---`;
|
||||
const table = this._data.enabledNonThemeExtesions.map(e => {
|
||||
return `${e.name}|${e.publisher.substr(0, 3)}|${e.version}`;
|
||||
return `${e.name}|${e.publisher?.substr(0, 3) ?? 'N/A'}|${e.version}`;
|
||||
}).join('\n');
|
||||
|
||||
return `<details><summary>Extensions (${this._data.enabledNonThemeExtesions.length})</summary>
|
||||
|
||||
@@ -12,7 +12,7 @@ suite('IssueReporter', () => {
|
||||
|
||||
test('sets defaults to include all data', () => {
|
||||
const issueReporterModel = new IssueReporterModel();
|
||||
assert.deepEqual(issueReporterModel.getData(), {
|
||||
assert.deepStrictEqual(issueReporterModel.getData(), {
|
||||
allExtensions: [],
|
||||
includeSystemInfo: true,
|
||||
includeWorkspaceInfo: true,
|
||||
@@ -25,7 +25,7 @@ suite('IssueReporter', () => {
|
||||
|
||||
test('serializes model skeleton when no data is provided', () => { // {{SQL CARBON EDIT}} modify test for ads
|
||||
const issueReporterModel = new IssueReporterModel({});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
assert.strictEqual(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
@@ -55,7 +55,7 @@ Extensions: none
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
assert.strictEqual(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
@@ -77,6 +77,58 @@ OS version: undefined
|
||||
|Screen Reader|no|
|
||||
|VM|0%|
|
||||
</details>Extensions: none
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
test.skip('serializes experiment info when data is provided', () => { // {{SQL CARBON EDIT}} skip test
|
||||
const issueReporterModel = new IssueReporterModel({
|
||||
issueType: 0,
|
||||
systemInfo: {
|
||||
os: 'Darwin',
|
||||
cpus: 'Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)',
|
||||
memory: '16.00GB',
|
||||
vmHint: '0%',
|
||||
processArgs: '',
|
||||
screenReader: 'no',
|
||||
remoteData: [],
|
||||
gpuStatus: {
|
||||
'2d_canvas': 'enabled',
|
||||
'checker_imaging': 'disabled_off'
|
||||
}
|
||||
},
|
||||
experimentInfo: 'vsliv695:30137379\nvsins829:30139715'
|
||||
});
|
||||
assert.strictEqual(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
undefined
|
||||
|
||||
VS Code version: undefined
|
||||
OS version: undefined
|
||||
|
||||
<details>
|
||||
<summary>System Info</summary>
|
||||
|
||||
|Item|Value|
|
||||
|---|---|
|
||||
|CPUs|Intel(R) Core(TM) i7-7700HQ CPU @ 2.80GHz (8 x 2800)|
|
||||
|GPU Status|2d_canvas: enabled<br>checker_imaging: disabled_off|
|
||||
|Load (avg)|undefined|
|
||||
|Memory (System)|16.00GB|
|
||||
|Process Argv||
|
||||
|Screen Reader|no|
|
||||
|VM|0%|
|
||||
</details>Extensions: none<details>
|
||||
<summary>A/B Experiments</summary>
|
||||
|
||||
\`\`\`
|
||||
vsliv695:30137379
|
||||
vsins829:30139715
|
||||
\`\`\`
|
||||
|
||||
</details>
|
||||
|
||||
<!-- generated by issue reporter -->`);
|
||||
});
|
||||
|
||||
@@ -100,7 +152,7 @@ OS version: undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
assert.strictEqual(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
@@ -156,7 +208,7 @@ OS version: undefined
|
||||
]
|
||||
}
|
||||
});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
assert.strictEqual(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
@@ -204,7 +256,7 @@ Remote OS version: Linux x64 4.18.0
|
||||
gpuStatus: {}
|
||||
}
|
||||
});
|
||||
assert.equal(issueReporterModel.serialize(),
|
||||
assert.strictEqual(issueReporterModel.serialize(),
|
||||
`
|
||||
Issue Type: <b>Bug</b>
|
||||
|
||||
@@ -239,7 +291,7 @@ OS version: undefined
|
||||
'https://github.com/repo/issues/new',
|
||||
'https://github.com/repo/issues/new/'
|
||||
].forEach(url => {
|
||||
assert.equal('https://github.com/repo', normalizeGitHubUrl(url));
|
||||
assert.strictEqual('https://github.com/repo', normalizeGitHubUrl(url));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -254,7 +306,7 @@ OS version: undefined
|
||||
fileOnExtension: true
|
||||
});
|
||||
|
||||
assert.equal(issueReporterModel.fileOnExtension(), true);
|
||||
assert.strictEqual(issueReporterModel.fileOnExtension(), true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,26 +4,44 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Load process explorer into window
|
||||
bootstrapWindow.load(['vs/code/electron-sandbox/processExplorer/processExplorerMain'], function (processExplorer, configuration) {
|
||||
processExplorer.startup(configuration.windowId, configuration.data);
|
||||
}, { forceEnableDeveloperKeybindings: true });
|
||||
|
||||
|
||||
//#region Globals
|
||||
return processExplorer.startup(configuration);
|
||||
}, {
|
||||
configureDeveloperSettings: function () {
|
||||
return {
|
||||
forceEnableDeveloperKeybindings: true
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* @returns {{ load: (modules: string[], resultCallback: (result, configuration: object) => any, options?: object) => unknown }}
|
||||
* @typedef {import('../../../base/parts/sandbox/common/sandboxTypes').ISandboxConfiguration} ISandboxConfiguration
|
||||
*
|
||||
* @returns {{
|
||||
* load: (
|
||||
* modules: string[],
|
||||
* resultCallback: (result, configuration: ISandboxConfiguration) => unknown,
|
||||
* options?: {
|
||||
* configureDeveloperSettings?: (config: ISandboxConfiguration) => {
|
||||
* forceEnableDeveloperKeybindings?: boolean,
|
||||
* disallowReloadKeybinding?: boolean,
|
||||
* removeDeveloperKeybindingsAfterLoad?: boolean
|
||||
* },
|
||||
* canModifyDOM?: (config: ISandboxConfiguration) => void,
|
||||
* beforeLoaderConfig?: (loaderConfig: object) => void,
|
||||
* beforeRequire?: () => void
|
||||
* }
|
||||
* ) => Promise<unknown>
|
||||
* }}
|
||||
*/
|
||||
function bootstrapWindowLib() {
|
||||
// @ts-ignore (defined in bootstrap-window.js)
|
||||
return window.MonacoBootstrapWindow;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}());
|
||||
|
||||
@@ -9,13 +9,12 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
|
||||
import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService';
|
||||
import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals';
|
||||
import { localize } from 'vs/nls';
|
||||
import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue';
|
||||
import { ProcessExplorerStyles, ProcessExplorerData, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue';
|
||||
import { applyZoom, zoomIn, zoomOut } from 'vs/platform/windows/electron-sandbox/window';
|
||||
import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu';
|
||||
import { popup } from 'vs/base/parts/contextmenu/electron-sandbox/contextmenu';
|
||||
import { ProcessItem } from 'vs/base/common/processes';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { append, $ } from 'vs/base/browser/dom';
|
||||
import { isRemoteDiagnosticError, IRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService';
|
||||
import { ByteSize } from 'vs/platform/files/common/files';
|
||||
@@ -105,11 +104,11 @@ class ProcessHeaderTreeRenderer implements ITreeRenderer<ProcessInformation, voi
|
||||
templateId: string = 'header';
|
||||
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
|
||||
const data = Object.create(null);
|
||||
const row = dom.append(container, dom.$('.row'));
|
||||
data.name = dom.append(row, dom.$('.nameLabel'));
|
||||
data.CPU = dom.append(row, dom.$('.cpu'));
|
||||
data.memory = dom.append(row, dom.$('.memory'));
|
||||
data.PID = dom.append(row, dom.$('.pid'));
|
||||
const row = append(container, $('.row'));
|
||||
data.name = append(row, $('.nameLabel'));
|
||||
data.CPU = append(row, $('.cpu'));
|
||||
data.memory = append(row, $('.memory'));
|
||||
data.PID = append(row, $('.pid'));
|
||||
return data;
|
||||
}
|
||||
renderElement(node: ITreeNode<ProcessInformation, void>, index: number, templateData: IProcessItemTemplateData, height: number | undefined): void {
|
||||
@@ -128,8 +127,8 @@ class MachineRenderer implements ITreeRenderer<MachineProcessInformation, void,
|
||||
templateId: string = 'machine';
|
||||
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
|
||||
const data = Object.create(null);
|
||||
const row = dom.append(container, dom.$('.row'));
|
||||
data.name = dom.append(row, dom.$('.nameLabel'));
|
||||
const row = append(container, $('.row'));
|
||||
data.name = append(row, $('.nameLabel'));
|
||||
return data;
|
||||
}
|
||||
renderElement(node: ITreeNode<MachineProcessInformation, void>, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void {
|
||||
@@ -144,8 +143,8 @@ class ErrorRenderer implements ITreeRenderer<IRemoteDiagnosticError, void, IProc
|
||||
templateId: string = 'error';
|
||||
renderTemplate(container: HTMLElement): IProcessRowTemplateData {
|
||||
const data = Object.create(null);
|
||||
const row = dom.append(container, dom.$('.row'));
|
||||
data.name = dom.append(row, dom.$('.nameLabel'));
|
||||
const row = append(container, $('.row'));
|
||||
data.name = append(row, $('.nameLabel'));
|
||||
return data;
|
||||
}
|
||||
renderElement(node: ITreeNode<IRemoteDiagnosticError, void>, index: number, templateData: IProcessRowTemplateData, height: number | undefined): void {
|
||||
@@ -163,12 +162,12 @@ class ProcessRenderer implements ITreeRenderer<ProcessItem, void, IProcessItemTe
|
||||
templateId: string = 'process';
|
||||
renderTemplate(container: HTMLElement): IProcessItemTemplateData {
|
||||
const data = <IProcessItemTemplateData>Object.create(null);
|
||||
const row = dom.append(container, dom.$('.row'));
|
||||
const row = append(container, $('.row'));
|
||||
|
||||
data.name = dom.append(row, dom.$('.nameLabel'));
|
||||
data.CPU = dom.append(row, dom.$('.cpu'));
|
||||
data.memory = dom.append(row, dom.$('.memory'));
|
||||
data.PID = dom.append(row, dom.$('.pid'));
|
||||
data.name = append(row, $('.nameLabel'));
|
||||
data.CPU = append(row, $('.cpu'));
|
||||
data.memory = append(row, $('.memory'));
|
||||
data.PID = append(row, $('.pid'));
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -226,8 +225,6 @@ class ProcessExplorer {
|
||||
|
||||
private mapPidToWindowTitle = new Map<number, string>();
|
||||
|
||||
private listeners = new DisposableStore();
|
||||
|
||||
private nativeHostService: INativeHostService;
|
||||
|
||||
private tree: DataTree<any, ProcessTree | MachineProcessInformation | ProcessItem | ProcessInformation | IRemoteDiagnosticError, any> | undefined;
|
||||
@@ -237,6 +234,7 @@ class ProcessExplorer {
|
||||
this.nativeHostService = new NativeHostService(windowId, mainProcessService) as INativeHostService;
|
||||
|
||||
this.applyStyles(data.styles);
|
||||
this.setEventHandlers(data);
|
||||
|
||||
// Map window process pids to titles, annotate process names with this when rendering to distinguish between them
|
||||
ipcRenderer.on('vscode:windowsInfoResponse', (event: unknown, windows: any[]) => {
|
||||
@@ -263,6 +261,32 @@ class ProcessExplorer {
|
||||
this.lastRequestTime = Date.now();
|
||||
ipcRenderer.send('vscode:windowsInfoRequest');
|
||||
ipcRenderer.send('vscode:listProcesses');
|
||||
|
||||
|
||||
}
|
||||
|
||||
private setEventHandlers(data: ProcessExplorerData): void {
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey;
|
||||
|
||||
// Cmd/Ctrl + w closes issue window
|
||||
if (cmdOrCtrlKey && e.keyCode === 87) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
ipcRenderer.send('vscode:closeProcessExplorer');
|
||||
}
|
||||
|
||||
// Cmd/Ctrl + zooms in
|
||||
if (cmdOrCtrlKey && e.keyCode === 187) {
|
||||
zoomIn();
|
||||
}
|
||||
|
||||
// Cmd/Ctrl - zooms out
|
||||
if (cmdOrCtrlKey && e.keyCode === 189) {
|
||||
zoomOut();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async createProcessTree(processRoots: MachineProcessInformation[]): Promise<void> {
|
||||
@@ -449,39 +473,12 @@ class ProcessExplorer {
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
public dispose() {
|
||||
this.listeners.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export function startup(windowId: number, data: ProcessExplorerData): void {
|
||||
const platformClass = data.platform === 'win32' ? 'windows' : data.platform === 'linux' ? 'linux' : 'mac';
|
||||
export function startup(configuration: ProcessExplorerWindowConfiguration): void {
|
||||
const platformClass = configuration.data.platform === 'win32' ? 'windows' : configuration.data.platform === 'linux' ? 'linux' : 'mac';
|
||||
document.body.classList.add(platformClass); // used by our fonts
|
||||
applyZoom(data.zoomLevel);
|
||||
applyZoom(configuration.data.zoomLevel);
|
||||
|
||||
const processExplorer = new ProcessExplorer(windowId, data);
|
||||
|
||||
document.onkeydown = (e: KeyboardEvent) => {
|
||||
const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey;
|
||||
|
||||
// Cmd/Ctrl + w closes issue window
|
||||
if (cmdOrCtrlKey && e.keyCode === 87) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
processExplorer.dispose();
|
||||
ipcRenderer.send('vscode:closeProcessExplorer');
|
||||
}
|
||||
|
||||
// Cmd/Ctrl + zooms in
|
||||
if (cmdOrCtrlKey && e.keyCode === 187) {
|
||||
zoomIn();
|
||||
}
|
||||
|
||||
// Cmd/Ctrl - zooms out
|
||||
if (cmdOrCtrlKey && e.keyCode === 189) {
|
||||
zoomOut();
|
||||
}
|
||||
};
|
||||
new ProcessExplorer(configuration.windowId, configuration.data);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview: https://*.vscode-webview-test.com; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src 'self' https: data: blob: vscode-remote-resource:; media-src 'none'; frame-src 'self' vscode-webview:; object-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: ws:; font-src 'self' https: vscode-remote-resource:;">
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types default TrustedFunctionWorkaround ExtensionScripts amdLoader cellRendererEditorText defaultWorkerFactory diffEditorWidget domLineBreaksComputer editorViewLayer diffReview extensionHostWorker insane notebookOutputRenderer safeInnerHtml standaloneColorizer tokenizeToString webNestedWorkerExtensionHost webWorkerExtensionHost;">
|
||||
</head>
|
||||
<body aria-label="">
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
/// <reference path="../../../../typings/require.d.ts" />
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const bootstrapWindow = bootstrapWindowLib();
|
||||
|
||||
// Add a perf entry right from the top
|
||||
@@ -32,15 +32,38 @@
|
||||
return require('vs/workbench/electron-sandbox/desktop.main').main(configuration);
|
||||
},
|
||||
{
|
||||
removeDeveloperKeybindingsAfterLoad: true,
|
||||
configureDeveloperSettings: function (windowConfig) {
|
||||
return {
|
||||
// disable automated devtools opening on error when running extension tests
|
||||
// as this can lead to undeterministic test exectuion (devtools steals focus)
|
||||
forceDisableShowDevtoolsOnError: typeof windowConfig.extensionTestsPath === 'string',
|
||||
// enable devtools keybindings in extension development window
|
||||
forceEnableDeveloperKeybindings: Array.isArray(windowConfig.extensionDevelopmentPath) && windowConfig.extensionDevelopmentPath.length > 0,
|
||||
removeDeveloperKeybindingsAfterLoad: true
|
||||
};
|
||||
},
|
||||
canModifyDOM: function (windowConfig) {
|
||||
// TODO@sandbox part-splash is non-sandboxed only
|
||||
},
|
||||
beforeLoaderConfig: function (windowConfig, loaderConfig) {
|
||||
beforeLoaderConfig: function (loaderConfig) {
|
||||
loaderConfig.recordStats = true;
|
||||
},
|
||||
beforeRequire: function () {
|
||||
performance.mark('code/willLoadWorkbenchMain');
|
||||
|
||||
// It looks like browsers only lazily enable
|
||||
// the <canvas> element when needed. Since we
|
||||
// leverage canvas elements in our code in many
|
||||
// locations, we try to help the browser to
|
||||
// initialize canvas when it is idle, right
|
||||
// before we wait for the scripts to be loaded.
|
||||
// @ts-ignore
|
||||
window.requestIdleCallback(() => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
canvas.remove();
|
||||
}, { timeout: 50 });
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -62,12 +85,26 @@
|
||||
}
|
||||
});
|
||||
|
||||
//region Helpers
|
||||
//#region Helpers
|
||||
|
||||
/**
|
||||
* @typedef {import('../../../platform/windows/common/windows').INativeWindowConfiguration} INativeWindowConfiguration
|
||||
*
|
||||
* @returns {{
|
||||
* load: (modules: string[], resultCallback: (result, configuration: import('../../../platform/windows/common/windows').INativeWindowConfiguration) => any, options: object) => unknown,
|
||||
* globals: () => typeof import('../../../base/parts/sandbox/electron-sandbox/globals')
|
||||
* load: (
|
||||
* modules: string[],
|
||||
* resultCallback: (result, configuration: INativeWindowConfiguration) => unknown,
|
||||
* options?: {
|
||||
* configureDeveloperSettings?: (config: INativeWindowConfiguration & object) => {
|
||||
* forceEnableDeveloperKeybindings?: boolean,
|
||||
* disallowReloadKeybinding?: boolean,
|
||||
* removeDeveloperKeybindingsAfterLoad?: boolean
|
||||
* },
|
||||
* canModifyDOM?: (config: INativeWindowConfiguration & object) => void,
|
||||
* beforeLoaderConfig?: (loaderConfig: object) => void,
|
||||
* beforeRequire?: () => void
|
||||
* }
|
||||
* ) => Promise<unknown>
|
||||
* }}
|
||||
*/
|
||||
function bootstrapWindowLib() {
|
||||
@@ -76,5 +113,4 @@
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
}());
|
||||
|
||||
Reference in New Issue
Block a user