Refactor Components

This commit is contained in:
Steve Faulkner
2021-01-02 20:47:54 -06:00
parent 09ac1d1552
commit 2147b10361
8 changed files with 1099 additions and 15 deletions

View File

@@ -0,0 +1,148 @@
import { StyleConstants } from "../../../Common/Constants";
import * as React from "react";
import { DefaultButton, IButtonStyles } from "office-ui-fabric-react/lib/Button";
import { IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
import { Dropdown, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
import { useSubscriptions } from "../../../hooks/useSubscriptions";
import { useDatabaseAccounts } from "../../../hooks/useDatabaseAccounts";
import { DatabaseAccount } from "../../../Contracts/DataModels";
const buttonStyles: IButtonStyles = {
root: {
fontSize: StyleConstants.DefaultFontSize,
height: 40,
padding: 0,
paddingLeft: 10,
marginRight: 5,
backgroundColor: StyleConstants.BaseDark,
color: StyleConstants.BaseLight
},
rootHovered: {
backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight
},
rootFocused: {
backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight
},
rootPressed: {
backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight
},
rootExpanded: {
backgroundColor: StyleConstants.BaseHigh,
color: StyleConstants.BaseLight
},
textContainer: {
flexGrow: "initial"
}
};
const cachedSubscriptionId = localStorage.getItem("cachedSubscriptionId");
const cachedDatabaseAccountName = localStorage.getItem("cachedDatabaseAccountName");
interface Props {
armToken: string;
setDatabaseAccount: (account: DatabaseAccount) => void;
}
export const AccountSwitcher: React.FunctionComponent<Props> = ({ armToken, setDatabaseAccount }: Props) => {
const subscriptions = useSubscriptions(armToken);
const [selectedSubscriptionId, setSelectedSubscriptionId] = React.useState<string>(cachedSubscriptionId);
const accounts = useDatabaseAccounts(selectedSubscriptionId, armToken);
const [selectedAccountName, setSelectedAccoutName] = React.useState<string>(cachedDatabaseAccountName);
React.useEffect(() => {
if (accounts && selectedAccountName) {
const account = accounts.find(account => account.name === selectedAccountName);
// Only set a new account if one is found
if (account) {
setDatabaseAccount(account);
}
}
}, [accounts, selectedAccountName]);
const menuProps: IContextualMenuProps = {
directionalHintFixed: true,
className: "accountSwitchContextualMenu",
items: [
{
key: "switchSubscription",
onRender: () => {
// const placeHolderText = isLoadingSubscriptions
// ? "Loading subscriptions"
// : !options || !options.length
// ? "No subscriptions found in current directory"
// : "Select subscription from list";
const dropdownProps: IDropdownProps = {
label: "Subscription",
className: "accountSwitchSubscriptionDropdown",
options: subscriptions.map(sub => {
return {
key: sub.subscriptionId,
text: sub.displayName,
data: sub
};
}),
onChange: (event, option) => {
const subscriptionId = String(option.key);
setSelectedSubscriptionId(subscriptionId);
localStorage.setItem("cachedSubscriptionId", subscriptionId);
},
defaultSelectedKey: selectedSubscriptionId,
placeholder: "Select subscription from list",
styles: {
callout: "accountSwitchSubscriptionDropdownMenu"
}
};
return <Dropdown {...dropdownProps} />;
}
},
{
key: "switchAccount",
onRender: (item, dismissMenu) => {
// const placeHolderText = isLoadingAccounts
// ? "Loading Cosmos DB accounts"
// : !options || !options.length
// ? "No Cosmos DB accounts found"
// : "Select Cosmos DB account from list";
const dropdownProps: IDropdownProps = {
label: "Cosmos DB Account Name",
className: "accountSwitchAccountDropdown",
options: accounts.map(account => ({
key: account.name,
text: account.name,
data: account
})),
onChange: (event, option) => {
const accountName = String(option.key);
setSelectedAccoutName(String(option.key));
localStorage.setItem("cachedDatabaseAccountName", accountName);
dismissMenu();
},
defaultSelectedKey: selectedAccountName,
placeholder: "No Cosmos DB accounts found",
styles: {
callout: "accountSwitchAccountDropdownMenu"
}
};
return <Dropdown {...dropdownProps} />;
}
}
]
};
return (
<DefaultButton
text={selectedAccountName || "Select Database Account"}
menuProps={menuProps}
styles={buttonStyles}
className="accountSwitchButton"
id="accountSwitchButton"
/>
);
};

View File

@@ -0,0 +1,78 @@
import * as React from "react";
import { useBoolean } from "@uifabric/react-hooks";
import { HttpHeaders } from "../../../Common/Constants";
import { GenerateTokenResponse } from "../../../Contracts/DataModels";
import { configContext } from "../../../ConfigContext";
interface Props {
login: () => void;
setEncryptedToken: (token: string) => void;
}
export const ConnectExplorer: React.FunctionComponent<Props> = ({ setEncryptedToken, login }: Props) => {
const [connectionString, setConnectionString] = React.useState<string>("");
const [isConnectionStringVisible, { setTrue: showConnectionString }] = useBoolean(false);
return (
<div id="connectExplorer" className="connectExplorerContainer" style={{ display: "flex" }}>
<div className="connectExplorerFormContainer">
<div className="connectExplorer">
<p className="connectExplorerContent">
<img src="images/HdeConnectCosmosDB.svg" alt="Azure Cosmos DB" />
</p>
<p className="welcomeText">Welcome to Azure Cosmos DB</p>
{isConnectionStringVisible ? (
<form
id="connectWithConnectionString"
onSubmit={async event => {
event.preventDefault();
const headers = new Headers();
headers.append(HttpHeaders.connectionString, connectionString);
const url = configContext.BACKEND_ENDPOINT + "/api/guest/tokens/generateToken";
const response = await fetch(url, { headers, method: "POST" });
if (!response.ok) {
throw response;
}
// This API has a quirk where it must be parsed twice
const result: GenerateTokenResponse = JSON.parse(await response.json());
console.log(result.readWrite || result.read);
setEncryptedToken(decodeURIComponent(result.readWrite || result.read));
}}
>
<p className="connectExplorerContent connectStringText">Connect to your account with connection string</p>
<p className="connectExplorerContent">
<input
className="inputToken"
type="text"
required
placeholder="Please enter a connection string"
value={connectionString}
onChange={event => {
setConnectionString(event.target.value);
}}
/>
<span className="errorDetailsInfoTooltip" style={{ display: "none" }}>
<img className="errorImg" src="images/error.svg" alt="Error notification" />
<span className="errorDetails"></span>
</span>
</p>
<p className="connectExplorerContent">
<input className="filterbtnstyle" type="submit" value="Connect" />
</p>
<p className="switchConnectTypeText" onClick={login}>
Sign In with Azure Account
</p>
</form>
) : (
<div id="connectWithAad">
<input className="filterbtnstyle" type="button" value="Sign In" onClick={login} />
<p className="switchConnectTypeText" onClick={showConnectionString}>
Connect to your account with connection string
</p>
</div>
)}
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,50 @@
import { Panel, PanelType, ChoiceGroup } from "office-ui-fabric-react";
import * as React from "react";
import { useDirectories } from "../../../hooks/useDirectories";
interface Props {
isOpen: boolean;
dismissPanel: () => void;
tenantId: string;
armToken: string;
}
export const DirectoryPickerPanel: React.FunctionComponent<Props> = ({
isOpen,
dismissPanel,
armToken,
tenantId
}: Props) => {
const directories = useDirectories(armToken);
return (
<Panel
type={PanelType.medium}
headerText="Select Directory"
isOpen={isOpen}
onDismiss={dismissPanel}
closeButtonAriaLabel="Close"
>
<ChoiceGroup
options={directories.map(dir => ({ key: dir.tenantId, text: `${dir.displayName} (${dir.tenantId})` }))}
selectedKey={tenantId}
onChange={async () => {
dismissPanel();
// TODO!!! Switching directories does not work. Still not sure why. Tried lots of stuff.
// const response = await msal.loginPopup({
// authority: `https://login.microsoftonline.com/${option.key}`
// });
// // msal = new Msal.UserAgentApplication({
// // auth: {
// // authority: `https://login.microsoftonline.com/${option.key}`,
// // clientId: "203f1145-856a-4232-83d4-a43568fba23d",
// // redirectUri: "https://dataexplorer-dev.azurewebsites.net" // TODO! This should only be set in development
// // }
// // });
// setTenantId(option.key);
// setAccount(response.account);
// console.log(account);
}}
/>
</Panel>
);
};

View File

@@ -0,0 +1,22 @@
import * as React from "react";
import { CommandButtonComponent } from "../../../Explorer/Controls/CommandButton/CommandButtonComponent";
import FeedbackIcon from "../images/Feedback.svg";
export const FeedbackCommandButton: React.FunctionComponent = () => {
return (
<div className="feedbackConnectSettingIcons">
<CommandButtonComponent
id="commandbutton-feedback"
iconSrc={FeedbackIcon}
iconAlt="feeback button"
onCommandClick={() =>
window.open("https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Hosted%20Data%20Explorer%20Feedback")
}
ariaLabel="feeback button"
tooltipText="Send feedback"
hasPopup={true}
disabled={false}
/>
</div>
);
};

View File

@@ -0,0 +1,68 @@
import {
FocusZone,
DefaultButton,
DirectionalHint,
Persona,
PersonaInitialsColor,
PersonaSize
} from "office-ui-fabric-react";
import * as React from "react";
import { Account } from "msal";
import { useGraphPhoto } from "../../../hooks/useGraphPhoto";
interface Props {
graphToken: string;
account: Account;
openPanel: () => void;
logout: () => void;
}
export const MeControl: React.FunctionComponent<Props> = ({ openPanel, logout, account, graphToken }: Props) => {
const photo = useGraphPhoto(graphToken);
return (
<FocusZone>
<DefaultButton
id="mecontrolHeader"
className="mecontrolHeaderButton"
menuProps={{
className: "mecontrolContextualMenu",
isBeakVisible: false,
directionalHintFixed: true,
directionalHint: DirectionalHint.bottomRightEdge,
calloutProps: {
minPagePadding: 0
},
items: [
{
key: "SwitchDirectory",
text: "Switch Directory",
onClick: openPanel
},
{
key: "SignOut",
text: "Sign Out",
onClick: logout
}
]
}}
styles={{
rootHovered: { backgroundColor: "#393939" },
rootFocused: { backgroundColor: "#393939" },
rootPressed: { backgroundColor: "#393939" },
rootExpanded: { backgroundColor: "#393939" }
}}
>
<Persona
imageUrl={photo}
text={account?.name}
secondaryText={account?.userName}
showSecondaryText={true}
showInitialsUntilImageLoads={true}
initialsColor={PersonaInitialsColor.teal}
size={PersonaSize.size28}
className="mecontrolHeaderPersona"
/>
</DefaultButton>
</FocusZone>
);
};

View File

@@ -0,0 +1,18 @@
import { DefaultButton } from "office-ui-fabric-react";
import * as React from "react";
interface Props {
login: () => void;
}
export const SignInButton: React.FunctionComponent<Props> = ({ login }: Props) => {
return <DefaultButton
className="mecontrolSigninButton"
text="Sign In"
onClick={login}
styles={{
rootHovered: { backgroundColor: "#393939", color: "#fff" },
rootFocused: { backgroundColor: "#393939", color: "#fff" },
rootPressed: { backgroundColor: "#393939", color: "#fff" }
}} />;
};