mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -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 azdata from 'azdata';
|
||||||
import * as nls from 'vscode-nls';
|
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 { BdcDeploymentType } from '../../../interfaces';
|
||||||
import { BigDataClusterDeploymentProfile } from '../../../services/bigDataClusterDeploymentProfile';
|
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();
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
||||||
|
|
||||||
private _cards: azdata.CardComponent[] = [];
|
private _profiles: BigDataClusterDeploymentProfile[] = [];
|
||||||
private _cardContainer: azdata.FlexContainer | undefined;
|
private _cardContainer: azdata.RadioCardGroupComponent | undefined;
|
||||||
private _loadingComponent: azdata.LoadingComponent | undefined;
|
private _loadingComponent: azdata.LoadingComponent | undefined;
|
||||||
private _view: azdata.ModelView | undefined;
|
|
||||||
|
|
||||||
constructor(wizard: DeployClusterWizard) {
|
constructor(wizard: DeployClusterWizard) {
|
||||||
super(localize('deployCluster.summaryPageTitle', "Deployment configuration template"),
|
super(localize('deployCluster.summaryPageTitle', "Deployment configuration profile"),
|
||||||
localize('deployCluster.summaryPageDescription', "Select the target configuration template"), wizard);
|
localize('deployCluster.summaryPageDescription', "Select the target configuration profile"), wizard);
|
||||||
}
|
}
|
||||||
|
|
||||||
public initialize(): void {
|
public initialize(): void {
|
||||||
this.pageObject.registerContent((view: azdata.ModelView) => {
|
this.pageObject.registerContent(async (view: azdata.ModelView): Promise<void> => {
|
||||||
this._view = view;
|
this._cardContainer = view.modelBuilder.radioCardGroup().withProperties<azdata.RadioCardGroupComponentProperties>({
|
||||||
this._cardContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row', flexWrap: 'wrap' }).component();
|
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>({
|
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.")
|
value: localize('deployCluster.ProfileHintText', "Note: The settings of the deployment profile can be customized in later steps.")
|
||||||
}).component();
|
}).component();
|
||||||
const container = createFlexContainer(view, [this._cardContainer, hintText], false);
|
const container = createFlexContainer(view, [this._cardContainer, hintText], false);
|
||||||
this._loadingComponent = view.modelBuilder.loadingComponent().withItem(container).withProperties<azdata.LoadingComponentProperties>({
|
this._loadingComponent = view.modelBuilder.loadingComponent().withItem(container).withProperties<azdata.LoadingComponentProperties>({
|
||||||
loading: true,
|
loading: true,
|
||||||
loadingText: localize('deployCluster.loadingProfiles', "Loading deployment profiles"),
|
loadingText: localize('deployCluster.loadingProfiles', "Loading profiles"),
|
||||||
loadingCompletedText: localize('deployCluster.loadingProfilesCompleted', "Loading deployment profiles completed"),
|
loadingCompletedText: localize('deployCluster.loadingProfilesCompleted', "Loading profiles completed"),
|
||||||
showText: true
|
showText: true
|
||||||
}).component();
|
}).component();
|
||||||
let formBuilder = view.modelBuilder.formContainer().withFormItems(
|
let formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||||
@@ -51,99 +62,74 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
}
|
}
|
||||||
).withLayout({ width: '100%', height: '100%' });
|
).withLayout({ width: '100%', height: '100%' });
|
||||||
const form = formBuilder.withLayout({ width: '100%' }).component();
|
const form = formBuilder.withLayout({ width: '100%' }).component();
|
||||||
this.loadCards().then(() => {
|
await view.initializeModel(form);
|
||||||
this._loadingComponent!.loading = false;
|
await this.loadCards();
|
||||||
}, (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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private createProfileCard(profile: BigDataClusterDeploymentProfile, view: azdata.ModelView): azdata.CardComponent {
|
private createProfileCard(profile: BigDataClusterDeploymentProfile): azdata.RadioCard {
|
||||||
const descriptions: azdata.CardDescriptionItem[] = [{
|
const scaleDescription: azdata.RadioCardDescription = {
|
||||||
label: localize('deployCluster.serviceLabel', "Service"),
|
ariaLabel: localize('deployCluster.scaleDescription', "Scale description"),
|
||||||
value: localize('deployCluster.instancesLabel', "Instances"),
|
labelHeader: localize('deployCluster.serviceLabel', "Service"),
|
||||||
fontWeight: 'bold'
|
valueHeader: localize('deployCluster.instancesLabel', "Instances"),
|
||||||
}, {
|
contents: [
|
||||||
label: localize('deployCluster.masterPoolLabel', "SQL Server Master"),
|
{
|
||||||
value: profile.sqlServerReplicas.toString()
|
label: localize('deployCluster.masterPoolLabel', "SQL Server Master"),
|
||||||
}, {
|
value: profile.sqlServerReplicas.toString()
|
||||||
label: localize('deployCluster.computePoolLable', "Compute"),
|
},
|
||||||
value: profile.computeReplicas.toString()
|
{
|
||||||
}, {
|
label: localize('deployCluster.computePoolLable', "Compute"),
|
||||||
label: localize('deployCluster.dataPoolLabel', "Data"),
|
value: profile.computeReplicas.toString()
|
||||||
value: profile.dataReplicas.toString()
|
},
|
||||||
}, {
|
{
|
||||||
label: localize('deployCluster.hdfsLabel', "HDFS + Spark"),
|
label: localize('deployCluster.dataPoolLabel', "Data"),
|
||||||
value: profile.hdfsReplicas.toString()
|
value: profile.dataReplicas.toString()
|
||||||
}, {
|
}, {
|
||||||
label: '' // line separator
|
label: localize('deployCluster.hdfsLabel', "HDFS + Spark"),
|
||||||
}, {
|
value: profile.hdfsReplicas.toString()
|
||||||
label: localize('deployCluster.storageSize', "Storage size"),
|
}]
|
||||||
value: localize('deployCluster.gbPerInstance', "GB per Instance"),
|
};
|
||||||
fontWeight: 'bold'
|
const storageDescription: azdata.RadioCardDescription = {
|
||||||
}, {
|
ariaLabel: localize('deployCluster.storageDescription', "Storage description"),
|
||||||
label: localize('deployCluster.defaultDataStorage', "Data storage"),
|
labelHeader: localize('deployCluster.storageSize', "Storage size"),
|
||||||
value: profile.controllerDataStorageSize.toString()
|
valueHeader: localize('deployCluster.gbPerInstance', "GB per Instance"),
|
||||||
}, {
|
contents: [
|
||||||
label: localize('deployCluster.defaultLogStorage', "Log storage"),
|
{
|
||||||
value: profile.controllerLogsStorageSize.toString()
|
label: localize('deployCluster.defaultDataStorage', "Data storage"),
|
||||||
}, {
|
value: profile.controllerDataStorageSize.toString()
|
||||||
label: '' // line separator
|
}, {
|
||||||
}, {
|
label: localize('deployCluster.defaultLogStorage', "Log storage"),
|
||||||
label: localize('deployCluster.features', "Features"),
|
value: profile.controllerLogsStorageSize.toString()
|
||||||
value: '',
|
}
|
||||||
fontWeight: 'bold'
|
]
|
||||||
}, {
|
};
|
||||||
label: localize('deployCluster.basicAuthentication', "Basic authentication"),
|
|
||||||
value: ''
|
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) {
|
if (profile.activeDirectorySupported) {
|
||||||
descriptions.push({
|
featureDescription.contents.push({
|
||||||
label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication"),
|
label: localize('deployCluster.activeDirectoryAuthentication', "Active Directory authentication")
|
||||||
value: ''
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (profile.sqlServerReplicas > 1) {
|
if (profile.sqlServerReplicas > 1) {
|
||||||
descriptions.push({
|
featureDescription.contents.push({
|
||||||
label: localize('deployCluster.hadr', "High Availability"),
|
label: localize('deployCluster.hadr', "High Availability")
|
||||||
value: ''
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const card = view.modelBuilder.card().withProperties<azdata.CardProperties>({
|
return {
|
||||||
cardType: azdata.CardType.VerticalButton,
|
id: profile.profileName,
|
||||||
label: profile.profileName,
|
label: profile.profileName,
|
||||||
descriptions: descriptions,
|
descriptions: [scaleDescription, storageDescription, featureDescription]
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setModelValuesByProfile(selectedProfile: BigDataClusterDeploymentProfile): void {
|
private setModelValuesByProfile(selectedProfile: BigDataClusterDeploymentProfile): void {
|
||||||
@@ -174,20 +160,20 @@ export class DeploymentProfilePage extends WizardPageBase<DeployClusterWizard> {
|
|||||||
this.wizard.model.selectedProfile = selectedProfile;
|
this.wizard.model.selectedProfile = selectedProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
private loadCards(): Promise<void> {
|
private async loadCards(): Promise<void> {
|
||||||
return this.wizard.azdataService.getDeploymentProfiles(this.wizard.deploymentType).then((profiles: BigDataClusterDeploymentProfile[]) => {
|
try {
|
||||||
|
this._profiles = await this.wizard.azdataService.getDeploymentProfiles(this.wizard.deploymentType);
|
||||||
const defaultProfile: string = this.getDefaultProfile();
|
const defaultProfile: string = this.getDefaultProfile();
|
||||||
|
this._cardContainer!.cards = this._profiles.map(profile => this.createProfileCard(profile));
|
||||||
profiles.forEach(profile => {
|
this._loadingComponent!.loading = false;
|
||||||
const card = this.createProfileCard(profile, this._view!);
|
this._cardContainer!.selectedCardId = defaultProfile;
|
||||||
if (profile.profileName === defaultProfile) {
|
} catch (error) {
|
||||||
card.selected = true;
|
this.wizard.wizardObject.message = {
|
||||||
card.focus();
|
level: azdata.window.MessageLevel.Error,
|
||||||
this.setModelValuesByProfile(profile);
|
text: localize('deployCluster.loadProfileFailed', "Failed to load the deployment profiles: {0}", error.message)
|
||||||
}
|
};
|
||||||
this._cardContainer!.addItem(card, { flex: '0 0 auto' });
|
this._loadingComponent!.loading = false;
|
||||||
});
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public onEnter() {
|
public onEnter() {
|
||||||
|
|||||||
@@ -17,19 +17,17 @@ const localize = nls.loadMessageBundle();
|
|||||||
export class ResourceTypePickerDialog extends DialogBase {
|
export class ResourceTypePickerDialog extends DialogBase {
|
||||||
private toolRefreshTimestamp: number = 0;
|
private toolRefreshTimestamp: number = 0;
|
||||||
private _selectedResourceType: ResourceType;
|
private _selectedResourceType: ResourceType;
|
||||||
private _resourceTypeCards: azdata.CardComponent[] = [];
|
|
||||||
private _view!: azdata.ModelView;
|
private _view!: azdata.ModelView;
|
||||||
private _resourceDescriptionLabel!: azdata.TextComponent;
|
private _resourceDescriptionLabel!: azdata.TextComponent;
|
||||||
private _optionsContainer!: azdata.FlexContainer;
|
private _optionsContainer!: azdata.FlexContainer;
|
||||||
private _toolsTable!: azdata.TableComponent;
|
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 _optionDropDownMap: Map<string, azdata.DropDownComponent> = new Map();
|
||||||
private _toolsLoadingComponent!: azdata.LoadingComponent;
|
private _toolsLoadingComponent!: azdata.LoadingComponent;
|
||||||
private _agreementContainer!: azdata.DivContainer;
|
private _agreementContainer!: azdata.DivContainer;
|
||||||
private _agreementCheckboxChecked: boolean = false;
|
private _agreementCheckboxChecked: boolean = false;
|
||||||
private _installToolButton: azdata.window.Button;
|
private _installToolButton: azdata.window.Button;
|
||||||
private _tools: ITool[] = [];
|
private _tools: ITool[] = [];
|
||||||
private _cardsContainer!: azdata.FlexContainer;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private toolsService: IToolsService,
|
private toolsService: IToolsService,
|
||||||
@@ -61,10 +59,30 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
tab.registerContent((view: azdata.ModelView) => {
|
tab.registerContent((view: azdata.ModelView) => {
|
||||||
const tableWidth = 1126;
|
const tableWidth = 1126;
|
||||||
this._view = view;
|
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);
|
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._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._optionsContainer = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column' }).component();
|
||||||
this._agreementContainer = view.modelBuilder.divContainer().component();
|
this._agreementContainer = view.modelBuilder.divContainer().component();
|
||||||
@@ -106,7 +124,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
const formBuilder = view.modelBuilder.formContainer().withFormItems(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
component: this._cardsContainer,
|
component: this._cardGroup,
|
||||||
title: ''
|
title: ''
|
||||||
}, {
|
}, {
|
||||||
component: this._resourceDescriptionLabel,
|
component: this._resourceDescriptionLabel,
|
||||||
@@ -132,50 +150,15 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
|
|
||||||
return view.initializeModel(form).then(() => {
|
return view.initializeModel(form).then(() => {
|
||||||
if (this._selectedResourceType) {
|
if (this._selectedResourceType) {
|
||||||
this.selectResourceType(this._selectedResourceType);
|
this._cardGroup.selectedCardId = this._selectedResourceType.name;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
this._dialogObject.content = [tab];
|
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 {
|
private selectResourceType(resourceType: ResourceType): void {
|
||||||
this._selectedResourceType = resourceType;
|
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._resourceDescriptionLabel.value = resourceType.description;
|
||||||
this._agreementCheckboxChecked = false;
|
this._agreementCheckboxChecked = false;
|
||||||
this._agreementContainer.clearItems();
|
this._agreementContainer.clearItems();
|
||||||
@@ -346,7 +329,7 @@ export class ResourceTypePickerDialog extends DialogBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enableUiControlsWhenNotInstalling(enabled: boolean): void {
|
private enableUiControlsWhenNotInstalling(enabled: boolean): void {
|
||||||
this._cardsContainer.enabled = enabled;
|
this._cardGroup.enabled = enabled;
|
||||||
this._agreementContainer.enabled = enabled;
|
this._agreementContainer.enabled = enabled;
|
||||||
this._optionsContainer.enabled = enabled;
|
this._optionsContainer.enabled = enabled;
|
||||||
this._dialogObject.cancelButton.enabled = enabled;
|
this._dialogObject.cancelButton.enabled = enabled;
|
||||||
|
|||||||
36
src/sql/azdata.proposed.d.ts
vendored
36
src/sql/azdata.proposed.d.ts
vendored
@@ -124,6 +124,42 @@ declare module 'azdata' {
|
|||||||
OssRdbms = 2
|
OssRdbms = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ModelBuilder {
|
||||||
|
radioCardGroup(): ComponentBuilder<RadioCardGroupComponent>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RadioCard {
|
||||||
|
id: string;
|
||||||
|
label: string;
|
||||||
|
descriptions?: RadioCardDescription[];
|
||||||
|
icon?: string | vscode.Uri | { light: string | vscode.Uri; dark: string | vscode.Uri };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RadioCardDescription {
|
||||||
|
ariaLabel: string;
|
||||||
|
labelHeader: string;
|
||||||
|
contents: RadioCardLabelValuePair[];
|
||||||
|
valueHeader?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RadioCardLabelValuePair {
|
||||||
|
label: string;
|
||||||
|
value?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RadioCardGroupComponentProperties extends ComponentProperties, TitledComponentProperties {
|
||||||
|
cards: RadioCard[];
|
||||||
|
cardWidth: string;
|
||||||
|
cardHeight: string;
|
||||||
|
iconWidth?: string;
|
||||||
|
iconHeight?: string;
|
||||||
|
selectedCardId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RadioCardGroupComponent extends Component, RadioCardGroupComponentProperties {
|
||||||
|
onSelectionChanged: vscode.Event<any>;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DeclarativeTableProperties extends ComponentProperties {
|
export interface DeclarativeTableProperties extends ComponentProperties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,6 +226,13 @@ class ModelBuilderImpl implements azdata.ModelBuilder {
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
radioCardGroup(): azdata.ComponentBuilder<azdata.RadioCardGroupComponent> {
|
||||||
|
let id = this.getNextComponentId();
|
||||||
|
let builder: ComponentBuilderImpl<azdata.RadioCardGroupComponent> = this.getComponentBuilder(new RadioCardGroupComponentWrapper(this._proxy, this._handle, id), id);
|
||||||
|
this._componentBuilders.set(id, builder);
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
|
|
||||||
getComponentBuilder<T extends azdata.Component>(component: ComponentWrapper, id: string): ComponentBuilderImpl<T> {
|
getComponentBuilder<T extends azdata.Component>(component: ComponentWrapper, id: string): ComponentBuilderImpl<T> {
|
||||||
let componentBuilder: ComponentBuilderImpl<T> = new ComponentBuilderImpl<T>(component);
|
let componentBuilder: ComponentBuilderImpl<T> = new ComponentBuilderImpl<T>(component);
|
||||||
this._componentBuilders.set(id, componentBuilder);
|
this._componentBuilders.set(id, componentBuilder);
|
||||||
@@ -1591,6 +1598,66 @@ class HyperlinkComponentWrapper extends ComponentWrapper implements azdata.Hyper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RadioCardGroupComponentWrapper extends ComponentWrapper implements azdata.RadioCardGroupComponent {
|
||||||
|
constructor(proxy: MainThreadModelViewShape, handle: number, id: string) {
|
||||||
|
super(proxy, handle, ModelComponentTypes.RadioCardGroup, id);
|
||||||
|
this.properties = {};
|
||||||
|
this._emitterMap.set(ComponentEventType.onDidChange, new Emitter<any>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public get iconWidth(): string | undefined {
|
||||||
|
return this.properties['iconWidth'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set iconWidth(v: string | undefined) {
|
||||||
|
this.setProperty('iconWidth', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get iconHeight(): string | undefined {
|
||||||
|
return this.properties['iconHeight'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set iconHeight(v: string | undefined) {
|
||||||
|
this.setProperty('iconHeight', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cardWidth(): string | undefined {
|
||||||
|
return this.properties['cardWidth'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set cardWidth(v: string | undefined) {
|
||||||
|
this.setProperty('cardWidth', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cardHeight(): string | undefined {
|
||||||
|
return this.properties['cardHeight'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set cardHeight(v: string | undefined) {
|
||||||
|
this.setProperty('cardHeight', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cards(): azdata.RadioCard[] {
|
||||||
|
return this.properties['cards'];
|
||||||
|
}
|
||||||
|
public set cards(v: azdata.RadioCard[]) {
|
||||||
|
this.setProperty('cards', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get selectedCardId(): string | undefined {
|
||||||
|
return this.properties['selectedCardId'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public set selectedCardId(v: string | undefined) {
|
||||||
|
this.setProperty('selectedCardId', v);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onSelectionChanged(): vscode.Event<any> {
|
||||||
|
let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
|
||||||
|
return emitter && emitter.event;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class GroupContainerComponentWrapper extends ComponentWrapper implements azdata.GroupContainer {
|
class GroupContainerComponentWrapper extends ComponentWrapper implements azdata.GroupContainer {
|
||||||
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
constructor(proxy: MainThreadModelViewShape, handle: number, type: ModelComponentTypes, id: string) {
|
||||||
super(proxy, handle, type, id);
|
super(proxy, handle, type, id);
|
||||||
|
|||||||
@@ -167,7 +167,8 @@ export enum ModelComponentTypes {
|
|||||||
DiffEditor,
|
DiffEditor,
|
||||||
Dom,
|
Dom,
|
||||||
Hyperlink,
|
Hyperlink,
|
||||||
Image
|
Image,
|
||||||
|
RadioCardGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ColumnSizingMode {
|
export enum ColumnSizingMode {
|
||||||
|
|||||||
@@ -4,23 +4,17 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { ChangeDetectorRef, ElementRef } from '@angular/core';
|
import { ChangeDetectorRef, ElementRef } from '@angular/core';
|
||||||
|
|
||||||
import { IComponentDescriptor } from 'sql/workbench/browser/modelComponents/interfaces';
|
|
||||||
import * as azdata from 'azdata';
|
import * as azdata from 'azdata';
|
||||||
import { URI } from 'vs/base/common/uri';
|
|
||||||
import { IdGenerator } from 'vs/base/common/idGenerator';
|
|
||||||
import { createCSSRule, removeCSSRulesContainingSelector, asCSSUrl } from 'vs/base/browser/dom';
|
|
||||||
import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase';
|
import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase';
|
||||||
|
import { createIconCssClass, IUserFriendlyIcon } from 'sql/workbench/browser/modelComponents/iconUtils';
|
||||||
|
import { IComponentDescriptor } from 'sql/workbench/browser/modelComponents/interfaces';
|
||||||
export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI };
|
import { removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
export class ItemDescriptor<T> {
|
export class ItemDescriptor<T> {
|
||||||
constructor(public descriptor: IComponentDescriptor, public config: T) { }
|
constructor(public descriptor: IComponentDescriptor, public config: T) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids = new IdGenerator('model-view-component-icon-');
|
|
||||||
|
|
||||||
export abstract class ComponentWithIconBase extends ComponentBase {
|
export abstract class ComponentWithIconBase extends ComponentBase {
|
||||||
|
|
||||||
protected _iconClass: string;
|
protected _iconClass: string;
|
||||||
@@ -40,42 +34,11 @@ export abstract class ComponentWithIconBase extends ComponentBase {
|
|||||||
protected updateIcon() {
|
protected updateIcon() {
|
||||||
if (this.iconPath && this.iconPath !== this._iconPath) {
|
if (this.iconPath && this.iconPath !== this._iconPath) {
|
||||||
this._iconPath = this.iconPath;
|
this._iconPath = this.iconPath;
|
||||||
if (!this._iconClass) {
|
this._iconClass = createIconCssClass(this.iconPath, this._iconClass);
|
||||||
this._iconClass = ids.nextId();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeCSSRulesContainingSelector(this._iconClass);
|
|
||||||
const icon = this.getLightIconUri(this.iconPath);
|
|
||||||
const iconDark = this.getDarkIconUri(this.iconPath) || icon;
|
|
||||||
createCSSRule(`.icon.${this._iconClass}`, `background-image: ${asCSSUrl(icon)}`);
|
|
||||||
createCSSRule(`.vs-dark .icon.${this._iconClass}, .hc-black .icon.${this._iconClass}`, `background-image: ${asCSSUrl(iconDark)}`);
|
|
||||||
this._changeRef.detectChanges();
|
this._changeRef.detectChanges();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLightIconUri(iconPath: IUserFriendlyIcon): URI {
|
|
||||||
if (iconPath && iconPath['light']) {
|
|
||||||
return this.getIconUri(iconPath['light']);
|
|
||||||
} else {
|
|
||||||
return this.getIconUri(<string | URI>iconPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getDarkIconUri(iconPath: IUserFriendlyIcon): URI {
|
|
||||||
if (iconPath && iconPath['dark']) {
|
|
||||||
return this.getIconUri(iconPath['dark']);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getIconUri(iconPath: string | URI): URI {
|
|
||||||
if (typeof iconPath === 'string') {
|
|
||||||
return URI.file(iconPath);
|
|
||||||
} else {
|
|
||||||
return URI.revive(iconPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getIconWidth(): string {
|
public getIconWidth(): string {
|
||||||
return this.convertSize(this.iconWidth, '40px');
|
return this.convertSize(this.iconWidth, '40px');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import { registerComponentType } from 'sql/platform/dashboard/browser/modelCompo
|
|||||||
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||||
import HyperlinkComponent from 'sql/workbench/browser/modelComponents/hyperlink.component';
|
import HyperlinkComponent from 'sql/workbench/browser/modelComponents/hyperlink.component';
|
||||||
import SplitViewContainer from 'sql/workbench/browser/modelComponents/splitviewContainer.component';
|
import SplitViewContainer from 'sql/workbench/browser/modelComponents/splitviewContainer.component';
|
||||||
|
import RadioCardGroup from 'sql/workbench/browser/modelComponents/radioCardGroup.component';
|
||||||
|
|
||||||
export const DIV_CONTAINER = 'div-container';
|
export const DIV_CONTAINER = 'div-container';
|
||||||
registerComponentType(DIV_CONTAINER, ModelComponentTypes.DivContainer, DivContainer);
|
registerComponentType(DIV_CONTAINER, ModelComponentTypes.DivContainer, DivContainer);
|
||||||
@@ -105,3 +106,7 @@ registerComponentType(DOM_COMPONENT, ModelComponentTypes.Dom, DomComponent);
|
|||||||
|
|
||||||
export const HYPERLINK_COMPONENT = 'hyperlink-component';
|
export const HYPERLINK_COMPONENT = 'hyperlink-component';
|
||||||
registerComponentType(HYPERLINK_COMPONENT, ModelComponentTypes.Hyperlink, HyperlinkComponent);
|
registerComponentType(HYPERLINK_COMPONENT, ModelComponentTypes.Hyperlink, HyperlinkComponent);
|
||||||
|
|
||||||
|
export const RADIOCARDGROUP_COMPONENT = 'radiocardgroup-component';
|
||||||
|
registerComponentType(RADIOCARDGROUP_COMPONENT, ModelComponentTypes.RadioCardGroup, RadioCardGroup);
|
||||||
|
|
||||||
|
|||||||
54
src/sql/workbench/browser/modelComponents/iconUtils.ts
Normal file
54
src/sql/workbench/browser/modelComponents/iconUtils.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { asCSSUrl, createCSSRule, removeCSSRulesContainingSelector } from 'vs/base/browser/dom';
|
||||||
|
import { IdGenerator } from 'vs/base/common/idGenerator';
|
||||||
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
|
||||||
|
const ids = new IdGenerator('model-view-component-icon-');
|
||||||
|
|
||||||
|
export type IUserFriendlyIcon = string | URI | { light: string | URI; dark: string | URI };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a CSS class for the specified icon, if a class with the name already exists, it will be deleted first.
|
||||||
|
* @param iconPath icon specification
|
||||||
|
* @param className optional, the class name you want to reuse.
|
||||||
|
* @returns the CSS class name
|
||||||
|
*/
|
||||||
|
export function createIconCssClass(iconPath: IUserFriendlyIcon, className?: string): string {
|
||||||
|
let iconClass = className;
|
||||||
|
if (!iconClass) {
|
||||||
|
iconClass = ids.nextId();
|
||||||
|
}
|
||||||
|
removeCSSRulesContainingSelector(iconClass);
|
||||||
|
const icon = getLightIconUri(iconPath);
|
||||||
|
const iconDark = getDarkIconUri(iconPath) || icon;
|
||||||
|
createCSSRule(`.icon.${iconClass}`, `background-image: ${asCSSUrl(icon)}`);
|
||||||
|
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: ${asCSSUrl(iconDark)}`);
|
||||||
|
return iconClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLightIconUri(iconPath: IUserFriendlyIcon): URI {
|
||||||
|
if (iconPath && iconPath['light']) {
|
||||||
|
return getIconUri(iconPath['light']);
|
||||||
|
} else {
|
||||||
|
return getIconUri(<string | URI>iconPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDarkIconUri(iconPath: IUserFriendlyIcon): URI {
|
||||||
|
if (iconPath && iconPath['dark']) {
|
||||||
|
return getIconUri(iconPath['dark']);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIconUri(iconPath: string | URI): URI {
|
||||||
|
if (typeof iconPath === 'string') {
|
||||||
|
return URI.file(iconPath);
|
||||||
|
} else {
|
||||||
|
return URI.revive(iconPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,16 +13,7 @@
|
|||||||
border-style: solid;
|
border-style: solid;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
|
||||||
|
|
||||||
.model-card-list-item.selected, .model-card.selected {
|
|
||||||
border-color: rgb(0, 120, 215);
|
|
||||||
box-shadow: rgba(0, 120, 215, 0.75) 0px 0px 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.model-card-list-item.unselected, .model-card.unselected {
|
|
||||||
border-color: rgb(214, 214, 214);
|
border-color: rgb(214, 214, 214);
|
||||||
box-shadow: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.model-card .card-content {
|
.model-card .card-content {
|
||||||
@@ -122,7 +113,7 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-color: rgb(0, 120, 215);
|
border-color: rgb(214, 214, 214);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,3 +198,22 @@
|
|||||||
.model-card-list-item-description-value {
|
.model-card-list-item-description-value {
|
||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card-group {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-description-table {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-description-label-column {
|
||||||
|
text-align: left;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.model-card-description-value-column {
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<div role="radiogroup" *ngIf="cards" class="card-group" style="flex-wrap:wrap" [style.height]="height"
|
||||||
|
[style.width]="width" [attr.aria-label]="ariaLabel" (keydown)="onKeyDown($event)">
|
||||||
|
<div #cardDiv role="radio" *ngFor="let card of cards" class="model-card" (click)="selectCard(card)"
|
||||||
|
[attr.aria-checked]="isCardSelected(card)" [tabIndex]="getTabIndex(card)" [style.width]="cardWidth"
|
||||||
|
[style.height]="cardHeight" (focus)="onCardFocus(card)" (blur)="onCardBlur(card)" style="flex:0 0 auto;">
|
||||||
|
<span class="selection-indicator-container">
|
||||||
|
<div *ngIf="isCardSelected(card)" class="selection-indicator"></div>
|
||||||
|
</span>
|
||||||
|
<div class="card-vertical-button">
|
||||||
|
<div *ngIf="card.icon" class="iconContainer">
|
||||||
|
<div [class]="getIconClass(card)" [style.width]="iconWidth" [style.height]="iconHeight"></div>
|
||||||
|
</div>
|
||||||
|
<h4 class="card-label">{{card.label}}</h4>
|
||||||
|
<div *ngIf="card.descriptions && card.descriptions.length > 0" class="model-card-description-container">
|
||||||
|
<ng-container *ngFor="let desc of card.descriptions">
|
||||||
|
<table class="model-card-description-table" [attr.aria-label]="desc.ariaLabel">
|
||||||
|
<tr>
|
||||||
|
<th class="model-card-description-label-column">{{desc.labelHeader}}</th>
|
||||||
|
<th class="model-card-description-value-column" *ngIf="desc.valueHeader">
|
||||||
|
{{desc.valueHeader}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let content of desc.contents">
|
||||||
|
<td class="model-card-description-label-column">{{content.label}}</td>
|
||||||
|
<td class="model-card-description-value-column" *ngIf="content.value">{{content.value}}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { ChangeDetectorRef, Component, ElementRef, forwardRef, Inject, Input, OnDestroy, QueryList, ViewChildren } from '@angular/core';
|
||||||
|
import * as azdata from 'azdata';
|
||||||
|
import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase';
|
||||||
|
import { createIconCssClass } from 'sql/workbench/browser/modelComponents/iconUtils';
|
||||||
|
import { ComponentEventType, IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/browser/modelComponents/interfaces';
|
||||||
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||||
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||||
|
import 'vs/css!./media/card';
|
||||||
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
templateUrl: decodeURI(require.toUrl('./radioCardGroup.component.html'))
|
||||||
|
|
||||||
|
})
|
||||||
|
export default class RadioCardGroup extends ComponentBase implements IComponent, OnDestroy {
|
||||||
|
@Input() descriptor: IComponentDescriptor;
|
||||||
|
@Input() modelStore: IModelStore;
|
||||||
|
@ViewChildren('cardDiv') cardElements: QueryList<ElementRef>;
|
||||||
|
|
||||||
|
private selectedCard: azdata.RadioCard;
|
||||||
|
private focusedCard: azdata.RadioCard;
|
||||||
|
private iconClasses: { [key: string]: string } = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
|
||||||
|
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||||
|
@Inject(ILogService) private _logService: ILogService
|
||||||
|
) {
|
||||||
|
super(changeRef, el);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.baseInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
setLayout(layout: any): void {
|
||||||
|
this.layout();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
Object.keys(this.iconClasses).forEach((key) => {
|
||||||
|
DOM.removeCSSRulesContainingSelector(this.iconClasses[key]);
|
||||||
|
});
|
||||||
|
this.baseDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event: KeyboardEvent): void {
|
||||||
|
if (!this.enabled || this.cards.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let e = new StandardKeyboardEvent(event);
|
||||||
|
if (e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space) {
|
||||||
|
if (this.focusedCard && !this.selectedCard) {
|
||||||
|
this.selectCard(this.focusedCard);
|
||||||
|
}
|
||||||
|
DOM.EventHelper.stop(e, true);
|
||||||
|
}
|
||||||
|
else if (e.keyCode === KeyCode.LeftArrow || e.keyCode === KeyCode.UpArrow) {
|
||||||
|
if (this.focusedCard) {
|
||||||
|
this.selectCard(this.findPreviousCard(this.focusedCard));
|
||||||
|
}
|
||||||
|
DOM.EventHelper.stop(e, true);
|
||||||
|
} else if (e.keyCode === KeyCode.RightArrow || e.keyCode === KeyCode.DownArrow) {
|
||||||
|
if (this.focusedCard) {
|
||||||
|
this.selectCard(this.findNextCard(this.focusedCard));
|
||||||
|
}
|
||||||
|
DOM.EventHelper.stop(e, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private findPreviousCard(currentCard: azdata.RadioCard): azdata.RadioCard {
|
||||||
|
const currentIndex = this.cards.indexOf(currentCard);
|
||||||
|
const previousCardIndex = currentIndex === 0 ? this.cards.length - 1 : currentIndex - 1;
|
||||||
|
return this.cards[previousCardIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
private findNextCard(currentCard: azdata.RadioCard): azdata.RadioCard {
|
||||||
|
const currentIndex = this.cards.indexOf(currentCard);
|
||||||
|
const nextCardIndex = currentIndex === this.cards.length - 1 ? 0 : currentIndex + 1;
|
||||||
|
return this.cards[nextCardIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cards(): azdata.RadioCard[] {
|
||||||
|
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, azdata.RadioCard[]>((props) => props.cards, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cardWidth(): string | undefined {
|
||||||
|
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.cardWidth, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get cardHeight(): string | undefined {
|
||||||
|
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.cardHeight, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get iconWidth(): string | undefined {
|
||||||
|
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.iconWidth, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get iconHeight(): string | undefined {
|
||||||
|
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.iconHeight, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get selectedCardId(): string | undefined {
|
||||||
|
return this.getPropertyOrDefault<azdata.RadioCardGroupComponentProperties, string | undefined>((props) => props.selectedCardId, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getIconClass(card: azdata.RadioCard): string {
|
||||||
|
if (!this.iconClasses[card.id]) {
|
||||||
|
this.iconClasses[card.id] = `cardIcon icon ${createIconCssClass(card.icon)}`;
|
||||||
|
}
|
||||||
|
return this.iconClasses[card.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
public setProperties(properties: { [key: string]: any }) {
|
||||||
|
super.setProperties(properties);
|
||||||
|
// This is the entry point for the extension to set the selectedCardId
|
||||||
|
if (this.selectedCardId) {
|
||||||
|
const filteredCards = this.cards.filter(c => { return c.id === this.selectedCardId; });
|
||||||
|
if (filteredCards.length === 1) {
|
||||||
|
this.selectCard(filteredCards[0]);
|
||||||
|
} else {
|
||||||
|
this._logService.error(`There should be one and only one matching card for the giving selectedCardId, actual number: ${filteredCards.length}, selectedCardId: ${this.selectedCardId} $`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public selectCard(card: azdata.RadioCard): void {
|
||||||
|
if (!this.enabled || this.selectedCard === card || this.cards.indexOf(card) === -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.selectedCard = card;
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
const cardElement = this.getCardElement(this.selectedCard);
|
||||||
|
cardElement.nativeElement.focus();
|
||||||
|
this.setPropertyFromUI<azdata.RadioCardGroupComponentProperties, string | undefined>((props, value) => props.selectedCardId = value, card.id);
|
||||||
|
this.fireEvent({
|
||||||
|
eventType: ComponentEventType.onDidChange,
|
||||||
|
args: this.selectedCard.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCardElement(card: azdata.RadioCard): ElementRef {
|
||||||
|
return this.cardElements.toArray()[this.cards.indexOf(card)];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTabIndex(card: azdata.RadioCard): number {
|
||||||
|
if (!this.enabled) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (!this.selectedCard) {
|
||||||
|
return this.cards.indexOf(card) === 0 ? 0 : -1;
|
||||||
|
} else {
|
||||||
|
return card === this.selectedCard ? 0 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isCardSelected(card: azdata.RadioCard): boolean {
|
||||||
|
return card === this.selectedCard;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCardFocus(card: azdata.RadioCard): void {
|
||||||
|
this.focusedCard = card;
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
public onCardBlur(card: azdata.RadioCard): void {
|
||||||
|
this.focusedCard = undefined;
|
||||||
|
this._changeRef.detectChanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user