mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
accessible radio card (#8514)
* accessible radio card group * set radio card group width * address comments * address comments 2 * fix the profile card not being focused issue
This commit is contained in:
@@ -5,38 +5,49 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import { createFlexContainer } from '../../modelViewUtils';
|
||||
import { BdcDeploymentType } from '../../../interfaces';
|
||||
import { BigDataClusterDeploymentProfile } from '../../../services/bigDataClusterDeploymentProfile';
|
||||
import { createFlexContainer } from '../../modelViewUtils';
|
||||
import { WizardPageBase } from '../../wizardPageBase';
|
||||
import * as VariableNames from '../constants';
|
||||
import { DeployClusterWizard } from '../deployClusterWizard';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
||||
|
||||
private _cards: azdata.CardComponent[] = [];
|
||||
private _cardContainer: azdata.FlexContainer | undefined;
|
||||
private _profiles: BigDataClusterDeploymentProfile[] = [];
|
||||
private _cardContainer: azdata.RadioCardGroupComponent | undefined;
|
||||
private _loadingComponent: azdata.LoadingComponent | undefined;
|
||||
private _view: azdata.ModelView | undefined;
|
||||
|
||||
constructor(wizard: DeployClusterWizard) {
|
||||
super(localize('deployCluster.summaryPageTitle', "Deployment configuration template"),
|
||||
localize('deployCluster.summaryPageDescription', "Select the target configuration template"), wizard);
|
||||
super(localize('deployCluster.summaryPageTitle', "Deployment configuration profile"),
|
||||
localize('deployCluster.summaryPageDescription', "Select the target configuration profile"), wizard);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
||||
this._view = view;
|
||||
this._cardContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', flexWrap: 'wrap' }).component();
|
||||
this.pageObject.registerContent(async (view: azdata.ModelView): Promise<void> => {
|
||||
this._cardContainer = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
||||
cards: [],
|
||||
cardWidth: '240px',
|
||||
cardHeight: '340px',
|
||||
ariaLabel: localize('deploymentDialog.deploymentOptions', "Deployment options"),
|
||||
width: '1000px'
|
||||
}).component();
|
||||
this.wizard.registerDisposable(this._cardContainer.onSelectionChanged((profileName) => {
|
||||
const selectedProfile = this._profiles.find(p => profileName === p.profileName);
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
if (selectedProfile) {
|
||||
this.setModelValuesByProfile(selectedProfile);
|
||||
}
|
||||
}));
|
||||
const hintText = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({
|
||||
value: localize('deployCluster.ProfileHintText', "Note: The settings of the deployment profile can be customized in later steps.")
|
||||
}).component();
|
||||
const container = createFlexContainer(view, [this._cardContainer, hintText], false);
|
||||
this._loadingComponent = view.modelBuilder.loadingComponent().withItem(container).withProperties<azdata.LoadingComponentProperties>({
|
||||
loading: true,
|
||||
loadingText: localize('deployCluster.loadingProfiles', "Loading deployment profiles"),
|
||||
loadingCompletedText: localize('deployCluster.loadingProfilesCompleted', "Loading deployment profiles completed"),
|
||||
loadingText: localize('deployCluster.loadingProfiles', "Loading profiles"),
|
||||
loadingCompletedText: localize('deployCluster.loadingProfilesCompleted', "Loading profiles completed"),
|
||||
showText: true
|
||||
}).component();
|
||||
let formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
@@ -51,99 +62,74 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
||||
}
|
||||
).withLayout({ width: '100%', height: '100%' });
|
||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||
this.loadCards().then(() => {
|
||||
this._loadingComponent!.loading = false;
|
||||
}, (error) => {
|
||||
this.wizard.wizardObject.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: localize('deployCluster.loadProfileFailed', "Failed to load the deployment profiles: {0}", error.message)
|
||||
};
|
||||
this._loadingComponent!.loading = false;
|
||||
});
|
||||
return view.initializeModel(form);
|
||||
await view.initializeModel(form);
|
||||
await this.loadCards();
|
||||
});
|
||||
}
|
||||
|
||||
private createProfileCard(profile: BigDataClusterDeploymentProfile, view: azdata.ModelView): azdata.CardComponent {
|
||||
const descriptions: azdata.CardDescriptionItem[] = [{
|
||||
label: localize('deployCluster.serviceLabel', "Service"),
|
||||
value: localize('deployCluster.instancesLabel', "Instances"),
|
||||
fontWeight: 'bold'
|
||||
}, {
|
||||
label: localize('deployCluster.masterPoolLabel', "SQL Server Master"),
|
||||
value: profile.sqlServerReplicas.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.computePoolLable', "Compute"),
|
||||
value: profile.computeReplicas.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.dataPoolLabel', "Data"),
|
||||
value: profile.dataReplicas.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.hdfsLabel', "HDFS + Spark"),
|
||||
value: profile.hdfsReplicas.toString()
|
||||
}, {
|
||||
label: '' // line separator
|
||||
}, {
|
||||
label: localize('deployCluster.storageSize', "Storage size"),
|
||||
value: localize('deployCluster.gbPerInstance', "GB per Instance"),
|
||||
fontWeight: 'bold'
|
||||
}, {
|
||||
label: localize('deployCluster.defaultDataStorage', "Data storage"),
|
||||
value: profile.controllerDataStorageSize.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.defaultLogStorage', "Log storage"),
|
||||
value: profile.controllerLogsStorageSize.toString()
|
||||
}, {
|
||||
label: '' // line separator
|
||||
}, {
|
||||
label: localize('deployCluster.features', "Features"),
|
||||
value: '',
|
||||
fontWeight: 'bold'
|
||||
}, {
|
||||
label: localize('deployCluster.basicAuthentication', "Basic authentication"),
|
||||
value: ''
|
||||
}];
|
||||
private createProfileCard(profile: BigDataClusterDeploymentProfile): azdata.RadioCard {
|
||||
const scaleDescription: azdata.RadioCardDescription = {
|
||||
ariaLabel: localize('deployCluster.scaleDescription', "Scale description"),
|
||||
labelHeader: localize('deployCluster.serviceLabel', "Service"),
|
||||
valueHeader: localize('deployCluster.instancesLabel', "Instances"),
|
||||
contents: [
|
||||
{
|
||||
label: localize('deployCluster.masterPoolLabel', "SQL Server Master"),
|
||||
value: profile.sqlServerReplicas.toString()
|
||||
},
|
||||
{
|
||||
label: localize('deployCluster.computePoolLable', "Compute"),
|
||||
value: profile.computeReplicas.toString()
|
||||
},
|
||||
{
|
||||
label: localize('deployCluster.dataPoolLabel', "Data"),
|
||||
value: profile.dataReplicas.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.hdfsLabel', "HDFS + Spark"),
|
||||
value: profile.hdfsReplicas.toString()
|
||||
}]
|
||||
};
|
||||
const storageDescription: azdata.RadioCardDescription = {
|
||||
ariaLabel: localize('deployCluster.storageDescription', "Storage description"),
|
||||
labelHeader: localize('deployCluster.storageSize', "Storage size"),
|
||||
valueHeader: localize('deployCluster.gbPerInstance', "GB per Instance"),
|
||||
contents: [
|
||||
{
|
||||
label: localize('deployCluster.defaultDataStorage', "Data storage"),
|
||||
value: profile.controllerDataStorageSize.toString()
|
||||
}, {
|
||||
label: localize('deployCluster.defaultLogStorage', "Log storage"),
|
||||
value: profile.controllerLogsStorageSize.toString()
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const featureDescription: azdata.RadioCardDescription = {
|
||||
ariaLabel: localize('deployCluster.featureDescription', "Feature description"),
|
||||
labelHeader: localize('deployCluster.features', "Features"),
|
||||
contents: [
|
||||
{
|
||||
label: localize('deployCluster.basicAuthentication', "Basic authentication")
|
||||
}
|
||||
]
|
||||
};
|
||||
if (profile.activeDirectorySupported) {
|
||||
descriptions.push({
|
||||
label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication"),
|
||||
value: ''
|
||||
featureDescription.contents.push({
|
||||
label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication")
|
||||
});
|
||||
}
|
||||
|
||||
if (profile.sqlServerReplicas > 1) {
|
||||
descriptions.push({
|
||||
label: localize('deployCluster.hadr', "High Availability"),
|
||||
value: ''
|
||||
featureDescription.contents.push({
|
||||
label: localize('deployCluster.hadr', "High Availability")
|
||||
});
|
||||
}
|
||||
|
||||
const card = view.modelBuilder.card().withProperties<azdata.CardProperties>({
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
return {
|
||||
id: profile.profileName,
|
||||
label: profile.profileName,
|
||||
descriptions: descriptions,
|
||||
width: '240px',
|
||||
height: '320px',
|
||||
}).component();
|
||||
this._cards.push(card);
|
||||
this.wizard.registerDisposable(card.onCardSelectedChanged(() => {
|
||||
if (card.selected) {
|
||||
this.wizard.wizardObject.message = { text: '' };
|
||||
this.setModelValuesByProfile(profile);
|
||||
// clear the selected state of the previously selected card
|
||||
this._cards.forEach(c => {
|
||||
if (c !== card) {
|
||||
c.selected = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// keep the selected state if no other card is selected
|
||||
if (this._cards.filter(c => { return c !== card && c.selected; }).length === 0) {
|
||||
card.selected = true;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
return card;
|
||||
descriptions: [scaleDescription, storageDescription, featureDescription]
|
||||
};
|
||||
}
|
||||
|
||||
private setModelValuesByProfile(selectedProfile: BigDataClusterDeploymentProfile): void {
|
||||
@@ -174,20 +160,20 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
||||
this.wizard.model.selectedProfile = selectedProfile;
|
||||
}
|
||||
|
||||
private loadCards(): Promise<void> {
|
||||
return this.wizard.azdataService.getDeploymentProfiles(this.wizard.deploymentType).then((profiles: BigDataClusterDeploymentProfile[]) => {
|
||||
private async loadCards(): Promise<void> {
|
||||
try {
|
||||
this._profiles = await this.wizard.azdataService.getDeploymentProfiles(this.wizard.deploymentType);
|
||||
const defaultProfile: string = this.getDefaultProfile();
|
||||
|
||||
profiles.forEach(profile => {
|
||||
const card = this.createProfileCard(profile, this._view!);
|
||||
if (profile.profileName === defaultProfile) {
|
||||
card.selected = true;
|
||||
card.focus();
|
||||
this.setModelValuesByProfile(profile);
|
||||
}
|
||||
this._cardContainer!.addItem(card, { flex: '0 0 auto' });
|
||||
});
|
||||
});
|
||||
this._cardContainer!.cards = this._profiles.map(profile => this.createProfileCard(profile));
|
||||
this._loadingComponent!.loading = false;
|
||||
this._cardContainer!.selectedCardId = defaultProfile;
|
||||
} catch (error) {
|
||||
this.wizard.wizardObject.message = {
|
||||
level: azdata.window.MessageLevel.Error,
|
||||
text: localize('deployCluster.loadProfileFailed', "Failed to load the deployment profiles: {0}", error.message)
|
||||
};
|
||||
this._loadingComponent!.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
public onEnter() {
|
||||
|
||||
@@ -17,19 +17,17 @@ const localize = nls.loadMessageBundle();
|
||||
export class ResourceTypePickerDialog extends DialogBase {
|
||||
private toolRefreshTimestamp: number = 0;
|
||||
private _selectedResourceType: ResourceType;
|
||||
private _resourceTypeCards: azdata.CardComponent[] = [];
|
||||
private _view!: azdata.ModelView;
|
||||
private _resourceDescriptionLabel!: azdata.TextComponent;
|
||||
private _optionsContainer!: azdata.FlexContainer;
|
||||
private _toolsTable!: azdata.TableComponent;
|
||||
private _cardResourceTypeMap: Map<string, azdata.CardComponent> = new Map();
|
||||
private _cardGroup!: azdata.RadioCardGroupComponent;
|
||||
private _optionDropDownMap: Map<string, azdata.DropDownComponent> = new Map();
|
||||
private _toolsLoadingComponent!: azdata.LoadingComponent;
|
||||
private _agreementContainer!: azdata.DivContainer;
|
||||
private _agreementCheckboxChecked: boolean = false;
|
||||
private _installToolButton: azdata.window.Button;
|
||||
private _tools: ITool[] = [];
|
||||
private _cardsContainer!: azdata.FlexContainer;
|
||||
|
||||
constructor(
|
||||
private toolsService: IToolsService,
|
||||
@@ -61,10 +59,30 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
tab.registerContent((view: azdata.ModelView) => {
|
||||
const tableWidth = 1126;
|
||||
this._view = view;
|
||||
this.resourceTypeService.getResourceTypes().sort((a: ResourceType, b: ResourceType) => {
|
||||
const resourceTypes = this.resourceTypeService.getResourceTypes().sort((a: ResourceType, b: ResourceType) => {
|
||||
return (a.displayIndex || Number.MAX_VALUE) - (b.displayIndex || Number.MAX_VALUE);
|
||||
}).forEach(resourceType => this.addCard(resourceType));
|
||||
this._cardsContainer = view.modelBuilder.flexContainer().withItems(this._resourceTypeCards, { flex: '0 0 auto', CSSStyles: { 'margin-bottom': '10px' } }).withLayout({ flexFlow: 'row' }).component();
|
||||
});
|
||||
this._cardGroup = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
||||
cards: resourceTypes.map((resourceType) => {
|
||||
return <azdata.RadioCard>{
|
||||
id: resourceType.name,
|
||||
label: resourceType.displayName,
|
||||
icon: resourceType.icon
|
||||
};
|
||||
}),
|
||||
iconHeight: '50px',
|
||||
iconWidth: '50px',
|
||||
cardWidth: '220px',
|
||||
cardHeight: '180px',
|
||||
ariaLabel: localize('deploymentDialog.deploymentOptions', "Deployment options"),
|
||||
width: '1100px'
|
||||
}).component();
|
||||
this._toDispose.push(this._cardGroup.onSelectionChanged((cardId: string) => {
|
||||
const resourceType = resourceTypes.find(rt => { return rt.name === cardId; });
|
||||
if (resourceType) {
|
||||
this.selectResourceType(resourceType);
|
||||
}
|
||||
}));
|
||||
this._resourceDescriptionLabel = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: this._selectedResourceType ? this._selectedResourceType.description : undefined }).component();
|
||||
this._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||
this._agreementContainer = view.modelBuilder.divContainer().component();
|
||||
@@ -106,7 +124,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this._cardsContainer,
|
||||
component: this._cardGroup,
|
||||
title: ''
|
||||
}, {
|
||||
component: this._resourceDescriptionLabel,
|
||||
@@ -132,50 +150,15 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
|
||||
return view.initializeModel(form).then(() => {
|
||||
if (this._selectedResourceType) {
|
||||
this.selectResourceType(this._selectedResourceType);
|
||||
this._cardGroup.selectedCardId = this._selectedResourceType.name;
|
||||
}
|
||||
});
|
||||
});
|
||||
this._dialogObject.content = [tab];
|
||||
}
|
||||
|
||||
private addCard(resourceType: ResourceType): void {
|
||||
const card = this._view.modelBuilder.card().withProperties<azdata.CardProperties>({
|
||||
cardType: azdata.CardType.VerticalButton,
|
||||
iconPath: {
|
||||
dark: resourceType.icon.dark,
|
||||
light: resourceType.icon.light
|
||||
},
|
||||
label: resourceType.displayName,
|
||||
selected: (this._selectedResourceType && this._selectedResourceType.name === resourceType.name),
|
||||
width: '220px',
|
||||
height: '180px',
|
||||
iconWidth: '50px',
|
||||
iconHeight: '50px'
|
||||
}).component();
|
||||
this._resourceTypeCards.push(card);
|
||||
this._cardResourceTypeMap.set(resourceType.name, card);
|
||||
this._toDispose.push(card.onCardSelectedChanged(() => this.selectResourceType(resourceType)));
|
||||
}
|
||||
|
||||
private selectResourceType(resourceType: ResourceType): void {
|
||||
this._selectedResourceType = resourceType;
|
||||
const card = this._cardResourceTypeMap.get(this._selectedResourceType.name)!;
|
||||
if (card.selected) {
|
||||
card.focus();
|
||||
// clear the selected state of the previously selected card
|
||||
this._resourceTypeCards.forEach(c => {
|
||||
if (c !== card) {
|
||||
c.selected = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// keep the selected state if no other card is selected
|
||||
if (this._resourceTypeCards.filter(c => { return c !== card && c.selected; }).length === 0) {
|
||||
card.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
this._resourceDescriptionLabel.value = resourceType.description;
|
||||
this._agreementCheckboxChecked = false;
|
||||
this._agreementContainer.clearItems();
|
||||
@@ -346,7 +329,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
||||
}
|
||||
|
||||
private enableUiControlsWhenNotInstalling(enabled: boolean): void {
|
||||
this._cardsContainer.enabled = enabled;
|
||||
this._cardGroup.enabled = enabled;
|
||||
this._agreementContainer.enabled = enabled;
|
||||
this._optionsContainer.enabled = enabled;
|
||||
this._dialogObject.cancelButton.enabled = enabled;
|
||||
|
||||
Reference in New Issue
Block a user