mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2026-04-19 04:48:59 +01:00
* perf: remove deprecated copilot feature, add ARM timeouts, fix race conditions - Remove entire QueryCopilot feature (~50 files deleted, ~30 files cleaned) - Remove CopilotConfigured and SampleDataLoaded metric phases - Fix DatabaseTreeRendered 76% stuck rate (remove one-shot guard in useMetricPhases) - Add 8s default timeout to ARM requests (AbortController-based) - Fix MSAL token forceRefresh (true -> false, use cache) - Add concurrency limit of 5 to collection loading in Explorer - Remove orphaned SampleDataClient.ts and queryCopilotSampleData.json - Clean up dead sampleDataConnectionInfo field from UserContext * Clean up copilot and optimize initialization * Clean up copilot and optimize initialization
299 lines
9.9 KiB
TypeScript
299 lines
9.9 KiB
TypeScript
import {
|
|
Dropdown,
|
|
ICommandBarItemProps,
|
|
IComponentAsProps,
|
|
IconType,
|
|
IDropdownOption,
|
|
IDropdownStyles,
|
|
TooltipHost,
|
|
} from "@fluentui/react";
|
|
import { KeyboardHandlerMap } from "KeyboardShortcuts";
|
|
import * as React from "react";
|
|
import _ from "underscore";
|
|
import ChevronDownIcon from "../../../../images/Chevron_down.svg";
|
|
|
|
import { StyleConstants } from "../../../Common/StyleConstants";
|
|
import { configContext, Platform } from "../../../ConfigContext";
|
|
import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryConstants";
|
|
import * as TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
|
|
import { CommandButtonComponentProps } from "../../Controls/CommandButton/CommandButtonComponent";
|
|
import Explorer from "../../Explorer";
|
|
import { ConnectionStatus } from "./ConnectionStatusComponent";
|
|
import { MemoryTracker } from "./MemoryTrackerComponent";
|
|
|
|
/**
|
|
* Convert our NavbarButtonConfig to UI Fabric buttons
|
|
* @param btns
|
|
*/
|
|
export const convertButton = (btns: CommandButtonComponentProps[], backgroundColor: string): ICommandBarItemProps[] => {
|
|
const buttonHeightPx =
|
|
configContext.platform == Platform.Fabric
|
|
? StyleConstants.FabricCommandBarButtonHeight
|
|
: StyleConstants.CommandBarButtonHeight;
|
|
|
|
const hoverColor =
|
|
configContext.platform == Platform.Fabric ? StyleConstants.FabricAccentLight : StyleConstants.AccentLight;
|
|
|
|
const getFilter = (isDisabled: boolean): string => {
|
|
if (isDisabled) {
|
|
return StyleConstants.GrayScale;
|
|
}
|
|
return configContext.platform == Platform.Fabric ? StyleConstants.FabricToolbarIconColor : undefined;
|
|
};
|
|
|
|
return btns
|
|
.filter((btn) => btn)
|
|
.map((btn: CommandButtonComponentProps, index: number): ICommandBarItemProps => {
|
|
if (btn.isDivider) {
|
|
return createDivider(btn.commandButtonLabel);
|
|
}
|
|
|
|
const isSplit = !!btn.children && btn.children.length > 0;
|
|
const label = btn.commandButtonLabel || btn.tooltipText;
|
|
const result: ICommandBarItemProps = {
|
|
iconProps: {
|
|
style: {
|
|
width: StyleConstants.CommandBarIconWidth,
|
|
alignSelf: btn.iconName ? "baseline" : undefined,
|
|
filter: getFilter(btn.disabled),
|
|
},
|
|
imageProps: btn.iconSrc ? { src: btn.iconSrc, alt: btn.iconAlt } : undefined,
|
|
iconName: btn.iconName,
|
|
},
|
|
onClick: btn.onCommandClick
|
|
? (ev?: React.MouseEvent<HTMLElement, MouseEvent> | React.KeyboardEvent<HTMLElement>) => {
|
|
btn.onCommandClick(ev);
|
|
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label });
|
|
}
|
|
: undefined,
|
|
key: `${btn.commandButtonLabel}${index}`,
|
|
text: label,
|
|
title: btn.tooltipText,
|
|
name: label,
|
|
disabled: btn.disabled,
|
|
ariaLabel: btn.ariaLabel,
|
|
"data-test": `CommandBar/Button:${label}`,
|
|
buttonStyles: {
|
|
root: {
|
|
backgroundColor: "var(--colorNeutralBackground1)",
|
|
height: buttonHeightPx,
|
|
paddingRight: 0,
|
|
paddingLeft: 0,
|
|
borderRadius: configContext.platform == Platform.Fabric ? StyleConstants.FabricButtonBorderRadius : "0px",
|
|
minWidth: 24,
|
|
marginLeft: isSplit ? 0 : 5,
|
|
marginRight: isSplit ? 0 : 5,
|
|
color: "var(--colorNeutralForeground1)",
|
|
selectors: {
|
|
"&:hover": {
|
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
|
color: "var(--colorNeutralForeground1)",
|
|
},
|
|
"&:active": {
|
|
backgroundColor: "var(--colorNeutralBackground1Pressed)",
|
|
color: "var(--colorNeutralForeground1)",
|
|
},
|
|
},
|
|
},
|
|
rootDisabled: {
|
|
backgroundColor: "var(--colorNeutralBackground1)",
|
|
pointerEvents: "auto",
|
|
color: "var(--colorNeutralForegroundDisabled)",
|
|
},
|
|
splitButtonMenuButton: {
|
|
backgroundColor: "var(--colorNeutralBackground1)",
|
|
selectors: {
|
|
":hover": {
|
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
|
},
|
|
},
|
|
width: 16,
|
|
},
|
|
label: {
|
|
fontSize:
|
|
configContext.platform == Platform.Fabric
|
|
? StyleConstants.DefaultFontSize
|
|
: StyleConstants.mediumFontSize,
|
|
color: "var(--colorNeutralForeground1)",
|
|
},
|
|
rootHovered: {
|
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
|
color: "var(--colorNeutralForeground1)",
|
|
},
|
|
rootPressed: {
|
|
backgroundColor: "var(--colorNeutralBackground1Pressed)",
|
|
color: "var(--colorNeutralForeground1)",
|
|
},
|
|
splitButtonMenuButtonExpanded: {
|
|
backgroundColor: "var(--colorNeutralBackground1Pressed)",
|
|
selectors: {
|
|
":hover": {
|
|
backgroundColor: "var(--colorNeutralBackground1Hover)",
|
|
},
|
|
},
|
|
},
|
|
splitButtonDivider: {
|
|
display: "none",
|
|
},
|
|
icon: {
|
|
paddingLeft: 0,
|
|
paddingRight: 0,
|
|
color: "var(--colorNeutralForeground1)",
|
|
},
|
|
splitButtonContainer: {
|
|
marginLeft: 5,
|
|
marginRight: 5,
|
|
height: buttonHeightPx,
|
|
},
|
|
},
|
|
className: btn.className,
|
|
id: btn.id,
|
|
};
|
|
|
|
if (btn.tooltipContent) {
|
|
result.title = undefined;
|
|
result.commandBarButtonAs = (props: IComponentAsProps<ICommandBarItemProps>) => {
|
|
const { defaultRender: DefaultRender, ...rest } = props;
|
|
return React.createElement(
|
|
TooltipHost,
|
|
{
|
|
content: btn.tooltipContent as JSX.Element,
|
|
calloutProps: { gapSpace: 0 },
|
|
},
|
|
React.createElement(DefaultRender, rest),
|
|
);
|
|
};
|
|
}
|
|
|
|
if (isSplit) {
|
|
// It's a split button
|
|
result.split = true;
|
|
|
|
result.subMenuProps = {
|
|
items: convertButton(btn.children, backgroundColor),
|
|
styles: {
|
|
list: {
|
|
// TODO Figure out how to do it the proper way with subComponentStyles.
|
|
// TODO Remove all this crazy styling once we adopt Ui-Fabric Azure themes
|
|
selectors: {
|
|
".ms-ContextualMenu-itemText": {
|
|
fontSize:
|
|
configContext.platform == Platform.Fabric
|
|
? StyleConstants.DefaultFontSize
|
|
: StyleConstants.mediumFontSize,
|
|
},
|
|
".ms-ContextualMenu-link:hover": { backgroundColor: hoverColor },
|
|
".ms-ContextualMenu-icon": { width: 16, height: 16 },
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
result.menuIconProps = {
|
|
iconType: IconType.image,
|
|
style: {
|
|
width: 12,
|
|
paddingLeft: 1,
|
|
paddingTop: 6,
|
|
filter: getFilter(btn.disabled),
|
|
},
|
|
imageProps: {
|
|
src: ChevronDownIcon,
|
|
alt: btn.iconAlt,
|
|
},
|
|
};
|
|
}
|
|
|
|
if (btn.isDropdown) {
|
|
const selectedChild = _.find(btn.children, (child) => child.dropdownItemKey === btn.dropdownSelectedKey);
|
|
result.name = selectedChild?.commandButtonLabel || btn.dropdownPlaceholder;
|
|
|
|
const dropdownStyles: Partial<IDropdownStyles> = {
|
|
root: { margin: 5 },
|
|
dropdown: { width: btn.dropdownWidth },
|
|
title: { fontSize: 12, height: 30, lineHeight: 28 },
|
|
dropdownItem: { fontSize: 12, lineHeight: 28, minHeight: 30 },
|
|
dropdownItemSelected: { fontSize: 12, lineHeight: 28, minHeight: 30 },
|
|
};
|
|
|
|
const onDropdownChange = (
|
|
event: React.FormEvent<HTMLDivElement>,
|
|
option?: IDropdownOption,
|
|
index?: number,
|
|
): void => {
|
|
btn.children[index].onCommandClick(event);
|
|
TelemetryProcessor.trace(Action.ClickCommandBarButton, ActionModifiers.Mark, { label: option.text });
|
|
};
|
|
|
|
result.commandBarButtonAs = (props: IComponentAsProps<ICommandBarItemProps>) => {
|
|
return (
|
|
<Dropdown
|
|
placeholder={btn.dropdownPlaceholder}
|
|
defaultSelectedKey={btn.dropdownSelectedKey}
|
|
onChange={onDropdownChange}
|
|
options={btn.children.map((child: CommandButtonComponentProps) => ({
|
|
key: child.dropdownItemKey,
|
|
text: child.commandButtonLabel,
|
|
}))}
|
|
styles={dropdownStyles}
|
|
/>
|
|
);
|
|
};
|
|
}
|
|
|
|
return result;
|
|
});
|
|
};
|
|
|
|
export const createDivider = (key: string): ICommandBarItemProps => {
|
|
return {
|
|
onRender: () => (
|
|
<div className="dividerContainer">
|
|
<span />
|
|
</div>
|
|
),
|
|
iconOnly: true,
|
|
disabled: true,
|
|
key: key,
|
|
};
|
|
};
|
|
|
|
export const createMemoryTracker = (key: string): ICommandBarItemProps => {
|
|
return {
|
|
key,
|
|
onRender: () => <MemoryTracker />,
|
|
};
|
|
};
|
|
|
|
export const createConnectionStatus = (container: Explorer, key: string): ICommandBarItemProps => {
|
|
return {
|
|
key,
|
|
onRender: () => <ConnectionStatus container={container} />,
|
|
};
|
|
};
|
|
|
|
export function createKeyboardHandlers(allButtons: CommandButtonComponentProps[]): KeyboardHandlerMap {
|
|
const handlers: KeyboardHandlerMap = {};
|
|
|
|
function createHandlers(buttons: CommandButtonComponentProps[]) {
|
|
buttons.forEach((button) => {
|
|
if (!button.disabled && button.keyboardAction) {
|
|
handlers[button.keyboardAction] = (e) => {
|
|
button.onCommandClick(e);
|
|
|
|
// If the handler is bound, it means the button is visible and enabled, so we should prevent the default action
|
|
return true;
|
|
};
|
|
}
|
|
|
|
if (button.children && button.children.length > 0) {
|
|
createHandlers(button.children);
|
|
}
|
|
});
|
|
}
|
|
|
|
createHandlers(allButtons);
|
|
|
|
return handlers;
|
|
}
|