/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as azdata from 'azdata'; import * as loc from '../common/localizedConstants'; import { RemoteBookController, IAsset } from '../book/remoteBookController'; import * as utils from '../common/utils'; import * as vscode from 'vscode'; const defaultRepos = ['repos/microsoft/tigertoolbox', 'repos/azure-samples/synapse']; const urlGithubRE = /^(?:https:\/\/(?:github\.com|api\.github\.com\/repos)|(?:\/)?(?:\/)?repos)([\w-.?!=&%*+:@\/]*)/g; function apiGitHub(url: string): string { return `https://api.github.com/${url}/releases`; } function getRemoteLocationCategory(name: string): azdata.CategoryValue { if (name === loc.onGitHub) { return { name: name, displayName: loc.onGitHub }; } return { name: name, displayName: loc.onSharedFile }; } export class RemoteBookDialog { private dialog: azdata.window.Dialog; public view: azdata.ModelView; private formModel: azdata.FormContainer; private githubRepoDropdown: azdata.DropDownComponent; private remoteLocationDropdown: azdata.DropDownComponent; public releaseDropdown: azdata.DropDownComponent; private searchButton: azdata.ButtonComponent; public bookDropdown: azdata.DropDownComponent; public versionDropdown: azdata.DropDownComponent; public languageDropdown: azdata.DropDownComponent; private _remoteTypes: azdata.CategoryValue[]; constructor(public controller: RemoteBookController) { } public async createDialog(): Promise { this.dialog = azdata.window.createModelViewDialog(loc.addRemoteBook); this.dialog.registerContent(async view => { this.view = view; this.remoteLocationDropdown = this.view.modelBuilder.dropDown().withProps({ values: this.remoteLocationCategories, value: '', editable: false, }).component(); this.remoteLocationDropdown.onValueChanged(e => this.onRemoteLocationChanged()); this.githubRepoDropdown = this.view.modelBuilder.dropDown().withProps({ values: defaultRepos, value: '', editable: true, fireOnTextChange: true, }).component(); this.searchButton = this.view.modelBuilder.button().withProps({ label: loc.search, title: loc.search, width: '200px', secondary: true }).component(); this.searchButton.onDidClick(async () => await this.validate()); this.releaseDropdown = this.view.modelBuilder.dropDown() .withProps({ values: [], value: '', enabled: false }).component(); this.releaseDropdown.onValueChanged(async () => await this.getAssets()); this.bookDropdown = this.view.modelBuilder.dropDown().withProps({ values: [], value: '', editable: false, }).component(); this.bookDropdown.onValueChanged(async () => await this.fillVersionDropdown()); this.versionDropdown = this.view.modelBuilder.dropDown().withProps({ values: [], value: '', editable: false, }).component(); this.versionDropdown.onValueChanged(async () => await this.fillLanguageDropdown()); this.languageDropdown = this.view.modelBuilder.dropDown().withProps({ values: [], value: '', editable: false, }).component(); this.languageDropdown.onValueChanged(async () => this.checkValues()); await this.setFieldsToEmpty(); this.formModel = this.view.modelBuilder.formContainer() .withFormItems([{ components: [ { component: this.remoteLocationDropdown, title: loc.location, required: true }, { component: this.githubRepoDropdown, title: loc.repoUrl, required: true }, { component: this.searchButton, title: '' }, { component: this.releaseDropdown, title: loc.releases, }, { component: this.bookDropdown, title: loc.book, required: true }, { component: this.versionDropdown, title: loc.version, required: true }, { component: this.languageDropdown, title: loc.language, required: true }, ], title: '' }]).withLayout({ width: '100%' }).component(); await this.view.initializeModel(this.formModel); }); this.dialog.okButton.enabled = false; this.dialog.okButton.label = loc.add; this.dialog.cancelButton.label = loc.close; this.dialog.registerCloseValidator(async () => await this.download()); azdata.window.openDialog(this.dialog); } private async setFieldsToEmpty(): Promise { await this.bookDropdown.updateProperties({ values: [loc.invalidTextPlaceholder], value: loc.invalidTextPlaceholder }); await this.versionDropdown.updateProperties({ values: [loc.invalidTextPlaceholder], value: loc.invalidTextPlaceholder }); await this.languageDropdown.updateProperties({ values: [loc.invalidTextPlaceholder], value: loc.invalidTextPlaceholder }); this.dialog.okButton.enabled = false; } private get remoteLocationValue(): string { return (this.remoteLocationDropdown.value).name; } public onRemoteLocationChanged(): void { if (this.controller.getReleases() !== undefined && this.remoteLocationValue === loc.onGitHub) { this.releaseDropdown.enabled = true; } else { this.releaseDropdown.enabled = false; } } public async validate(): Promise { try { let url = utils.getDropdownValue(this.githubRepoDropdown); url = url.trim().toLowerCase(); if (this.remoteLocationValue === loc.onGitHub && url.length > 0) { //get the first group to extract /owner/repo/releases format let groupsRe = url.match(urlGithubRE); if (groupsRe?.length > 0) { url = apiGitHub(groupsRe[0]); let releases = await this.controller.getReleases(vscode.Uri.parse(url)); if (releases) { this.releaseDropdown.enabled = true; await this.fillReleasesDropdown(); await this.setFieldsToEmpty(); } } else { throw new Error(loc.urlGithubError); } } } catch (error) { await this.fillReleasesDropdown(); await this.setFieldsToEmpty(); this.showErrorMessage(error.message); } } public async getAssets(): Promise { try { if (this.remoteLocationValue === loc.onGitHub) { let releases = await this.controller.getReleases(); let selected_release = releases.filter(release => release.name === this.releaseDropdown.value); let assets = await this.controller.getAssets(selected_release[0]); if (assets?.length > 0) { this.bookDropdown.values = ['-'].concat([...new Set(assets.map(asset => asset.book))]); } this.checkValues(); } } catch (error) { await this.setFieldsToEmpty(); this.showErrorMessage(error.message); } } public async download(): Promise { try { if (this.remoteLocationValue === loc.onGitHub) { let selected_asset = await this.getSelectedAsset(); if (!selected_asset) { throw new Error(loc.msgUndefinedAssetError); } await this.controller.setRemoteBook(selected_asset.url, this.remoteLocationValue, selected_asset); } else { let url = utils.getDropdownValue(this.githubRepoDropdown); let newUrl = vscode.Uri.parse(url); await this.controller.setRemoteBook(newUrl, this.remoteLocationValue); } return true; } catch (error) { this.showErrorMessage(error.message); return false; } } public async fillReleasesDropdown(): Promise { this.releaseDropdown.values = ['-'].concat((await this.controller.getReleases()).map(release => release.name)); } public async fillVersionDropdown(): Promise { const assets = await this.controller.getAssets(); let filtered_assets = assets.filter(asset => asset.book === this.bookDropdown.value); this.versionDropdown.values = ['-'].concat(filtered_assets.map(asset => asset.version)); this.checkValues(); } public async fillLanguageDropdown(): Promise { const assets = await this.controller.getAssets(); let filtered_assets = assets.filter(asset => asset.book === this.bookDropdown.value && asset.version === this.versionDropdown.value); this.languageDropdown.values = ['-'].concat(filtered_assets.map(asset => asset.language)); this.checkValues(); } public async getSelectedAsset(): Promise { let lang = this.languageDropdown.value; let book = this.bookDropdown.value; let version = this.versionDropdown.value; return (await this.controller.getAssets()).filter(asset => asset.book === book && asset.version === version && asset.language === lang)[0]; } public checkValues(): void { if (this.languageDropdown.value !== loc.invalidTextPlaceholder && this.versionDropdown.value !== loc.invalidTextPlaceholder && this.bookDropdown.value !== loc.invalidTextPlaceholder) { this.dialog.okButton.enabled = true; } else { this.dialog.okButton.enabled = false; } } public get remoteLocationCategories(): azdata.CategoryValue[] { if (!this._remoteTypes) { this._remoteTypes = [getRemoteLocationCategory(loc.onGitHub)]; } return this._remoteTypes; } public showErrorMessage(message: string): void { this.dialog.message = { text: message, level: azdata.window.MessageLevel.Error }; } }