mirror of
https://github.com/Azure/cosmos-explorer.git
synced 2025-12-21 01:41:31 +00:00
Initial Move from Azure DevOps to GitHub
This commit is contained in:
31
src/Explorer/Menus/NavBar/ControlBarComponent.tsx
Normal file
31
src/Explorer/Menus/NavBar/ControlBarComponent.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* React component for control bar
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
import { CommandButtonComponent } from "../../Controls/CommandButton/CommandButtonComponent";
|
||||
|
||||
export interface ControlBarComponentProps {
|
||||
buttons: ViewModels.NavbarButtonConfig[];
|
||||
}
|
||||
|
||||
export class ControlBarComponent extends React.Component<ControlBarComponentProps> {
|
||||
private static renderButtons(commandButtonOptions: ViewModels.NavbarButtonConfig[]): JSX.Element[] {
|
||||
return commandButtonOptions.map(
|
||||
(btn: ViewModels.NavbarButtonConfig, index: number): JSX.Element => {
|
||||
// Remove label
|
||||
btn.commandButtonLabel = null;
|
||||
return CommandButtonComponent.renderButton(btn, `${index}`);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
if (!this.props.buttons || this.props.buttons.length < 1) {
|
||||
return <React.Fragment />;
|
||||
}
|
||||
|
||||
return <React.Fragment>{ControlBarComponent.renderButtons(this.props.buttons)}</React.Fragment>;
|
||||
}
|
||||
}
|
||||
28
src/Explorer/Menus/NavBar/ControlBarComponentAdapter.tsx
Normal file
28
src/Explorer/Menus/NavBar/ControlBarComponentAdapter.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* This adapter is responsible to render the React component
|
||||
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
|
||||
* and update any knockout observables passed from the parent.
|
||||
*/
|
||||
|
||||
import * as ko from "knockout";
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { ControlBarComponent } from "./ControlBarComponent";
|
||||
import * as ViewModels from "../../../Contracts/ViewModels";
|
||||
|
||||
export class ControlBarComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<number>;
|
||||
|
||||
constructor(private buttons: ko.ObservableArray<ViewModels.NavbarButtonConfig>) {
|
||||
this.buttons.subscribe(() => this.forceRender());
|
||||
this.parameters = ko.observable<number>(Date.now());
|
||||
}
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <ControlBarComponent buttons={this.buttons()} />;
|
||||
}
|
||||
|
||||
public forceRender(): void {
|
||||
window.requestAnimationFrame(() => this.parameters(Date.now()));
|
||||
}
|
||||
}
|
||||
93
src/Explorer/Menus/NavBar/MeControlComponent.less
Normal file
93
src/Explorer/Menus/NavBar/MeControlComponent.less
Normal file
@@ -0,0 +1,93 @@
|
||||
.meControl {
|
||||
.mecontrolHeaderButton {
|
||||
height : auto;
|
||||
border : 0;
|
||||
margin : 0;
|
||||
padding : 0 10px;
|
||||
background-color: black;
|
||||
|
||||
:hover,
|
||||
:active {
|
||||
background-color: #393939
|
||||
}
|
||||
|
||||
.mecontrolHeaderPersona {
|
||||
height: 40px;
|
||||
margin: 0;
|
||||
|
||||
.ms-Persona-details {
|
||||
order : -1;
|
||||
text-align : right;
|
||||
padding-left : 0;
|
||||
padding-right: 10;
|
||||
|
||||
.ms-Persona-primaryText {
|
||||
line-height: 18px;
|
||||
color : white;
|
||||
}
|
||||
|
||||
.ms-Persona-secondaryText {
|
||||
color : white;
|
||||
font-size : 10px;
|
||||
font-family: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||
}
|
||||
}
|
||||
|
||||
:hover .ms-Persona-details .ms-Persona-primaryText {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mecontrolSigninButton {
|
||||
height : 40px;
|
||||
border : 0;
|
||||
background-color: black;
|
||||
color : white;
|
||||
}
|
||||
}
|
||||
|
||||
.mecontrolContextualMenu {
|
||||
.mecontrolContextualMenuPersona {
|
||||
margin : 16px;
|
||||
margin-bottom: 0;
|
||||
|
||||
.ms-Persona-details {
|
||||
padding: 0;
|
||||
margin : -32px 0 0 16;
|
||||
|
||||
.ms-Persona-primaryText {
|
||||
color : black;
|
||||
font-size : 18px;
|
||||
height : 20px;
|
||||
font-family: "Segoe UI Semibold", "Segoe UI", "Segoe WP", Tahoma, Arial, sans-serif;
|
||||
}
|
||||
|
||||
.ms-Persona-secondaryText {
|
||||
color : black;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.switchDirectoryLink,
|
||||
.signOutLink {
|
||||
color : #0078d6;
|
||||
margin-left: 104px;
|
||||
font-size : 14px;
|
||||
cursor : pointer;
|
||||
|
||||
:hover,
|
||||
:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.switchDirectoryLink {
|
||||
margin-top: -32px;
|
||||
}
|
||||
|
||||
.signOutLink {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
108
src/Explorer/Menus/NavBar/MeControlComponent.test.tsx
Normal file
108
src/Explorer/Menus/NavBar/MeControlComponent.test.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
import React from "react";
|
||||
import { shallow, mount } from "enzyme";
|
||||
import { MeControlComponent, MeControlComponentProps } from "./MeControlComponent";
|
||||
|
||||
const createNotSignedInProps = (): MeControlComponentProps => {
|
||||
return {
|
||||
isUserSignedIn: false,
|
||||
user: null,
|
||||
onSignInClick: jest.fn(),
|
||||
onSignOutClick: jest.fn(),
|
||||
onSwitchDirectoryClick: jest.fn()
|
||||
};
|
||||
};
|
||||
|
||||
const createSignedInProps = (): MeControlComponentProps => {
|
||||
return {
|
||||
isUserSignedIn: true,
|
||||
user: {
|
||||
name: "Test User",
|
||||
email: "testuser@contoso.com",
|
||||
tenantName: "Contoso",
|
||||
imageUrl: "../../../../images/dotnet.png"
|
||||
},
|
||||
onSignInClick: jest.fn(),
|
||||
onSignOutClick: jest.fn(),
|
||||
onSwitchDirectoryClick: jest.fn()
|
||||
};
|
||||
};
|
||||
|
||||
describe("test render", () => {
|
||||
it("renders not signed in", () => {
|
||||
const props = createNotSignedInProps();
|
||||
|
||||
const wrapper = shallow(<MeControlComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders signed in with full info", () => {
|
||||
const props = createSignedInProps();
|
||||
|
||||
const wrapper = shallow(<MeControlComponent {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("change not signed in to signed in", () => {
|
||||
const notSignInProps = createNotSignedInProps();
|
||||
|
||||
const wrapper = mount(<MeControlComponent {...notSignInProps} />);
|
||||
expect(wrapper.exists(".mecontrolSigninButton")).toBe(true);
|
||||
expect(wrapper.exists(".mecontrolHeaderButton")).toBe(false);
|
||||
|
||||
const signInProps = createSignedInProps();
|
||||
|
||||
wrapper.setProps(signInProps);
|
||||
expect(wrapper.exists(".mecontrolSigninButton")).toBe(false);
|
||||
expect(wrapper.exists(".mecontrolHeaderButton")).toBe(true);
|
||||
|
||||
wrapper.unmount;
|
||||
});
|
||||
|
||||
it("render contextual menu", () => {
|
||||
const signInProps = createSignedInProps();
|
||||
const wrapper = mount(<MeControlComponent {...signInProps} />);
|
||||
|
||||
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(true);
|
||||
|
||||
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(false);
|
||||
|
||||
wrapper.unmount;
|
||||
});
|
||||
});
|
||||
|
||||
describe("test function got called", () => {
|
||||
it("sign in click", () => {
|
||||
const notSignInProps = createNotSignedInProps();
|
||||
const wrapper = mount(<MeControlComponent {...notSignInProps} />);
|
||||
|
||||
wrapper.find("button.mecontrolSigninButton").simulate("click");
|
||||
expect(notSignInProps.onSignInClick).toBeCalled();
|
||||
expect(notSignInProps.onSignInClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sign out click", () => {
|
||||
const signInProps = createSignedInProps();
|
||||
const wrapper = mount(<MeControlComponent {...signInProps} />);
|
||||
|
||||
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(true);
|
||||
|
||||
wrapper.find("div.signOutLink").simulate("click");
|
||||
expect(signInProps.onSignOutClick).toBeCalled();
|
||||
expect(signInProps.onSignOutClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("switch directory", () => {
|
||||
const signInProps = createSignedInProps();
|
||||
const wrapper = mount(<MeControlComponent {...signInProps} />);
|
||||
|
||||
wrapper.find("button.mecontrolHeaderButton").simulate("click");
|
||||
expect(wrapper.exists(".mecontrolContextualMenu")).toBe(true);
|
||||
|
||||
wrapper.find("div.switchDirectoryLink").simulate("click");
|
||||
expect(signInProps.onSwitchDirectoryClick).toBeCalled();
|
||||
expect(signInProps.onSwitchDirectoryClick).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
167
src/Explorer/Menus/NavBar/MeControlComponent.tsx
Normal file
167
src/Explorer/Menus/NavBar/MeControlComponent.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
import * as React from "react";
|
||||
import { DefaultButton, BaseButton, IButtonProps } from "office-ui-fabric-react/lib/Button";
|
||||
import { DirectionalHint, IContextualMenuProps } from "office-ui-fabric-react/lib/ContextualMenu";
|
||||
import { FocusZone } from "office-ui-fabric-react/lib/FocusZone";
|
||||
import { IPersonaSharedProps, Persona, PersonaInitialsColor, PersonaSize } from "office-ui-fabric-react/lib/Persona";
|
||||
|
||||
export interface MeControlComponentProps {
|
||||
/**
|
||||
* Wheather user is signed in or not
|
||||
*/
|
||||
isUserSignedIn: boolean;
|
||||
/**
|
||||
* User info
|
||||
*/
|
||||
user: MeControlUser;
|
||||
/**
|
||||
* Click handler for sign in click
|
||||
*/
|
||||
onSignInClick: (e: React.MouseEvent<BaseButton>) => void;
|
||||
/**
|
||||
* Click handler for sign out click
|
||||
*/
|
||||
onSignOutClick: (e: React.SyntheticEvent) => void;
|
||||
/**
|
||||
* Click handler for switch directory click
|
||||
*/
|
||||
onSwitchDirectoryClick: (e: React.SyntheticEvent) => void;
|
||||
}
|
||||
|
||||
export interface MeControlUser {
|
||||
/**
|
||||
* Display name for user
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* Display email for user
|
||||
*/
|
||||
email: string;
|
||||
/**
|
||||
* Display tenant for user
|
||||
*/
|
||||
tenantName: string;
|
||||
/**
|
||||
* image source for the profic photo
|
||||
*/
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
export class MeControlComponent extends React.Component<MeControlComponentProps> {
|
||||
public render(): JSX.Element {
|
||||
return this.props.isUserSignedIn ? this._renderProfileComponent() : this._renderSignInComponent();
|
||||
}
|
||||
|
||||
private _renderProfileComponent(): JSX.Element {
|
||||
const { user } = this.props;
|
||||
|
||||
const menuProps: IContextualMenuProps = {
|
||||
className: "mecontrolContextualMenu",
|
||||
isBeakVisible: false,
|
||||
directionalHintFixed: true,
|
||||
directionalHint: DirectionalHint.bottomRightEdge,
|
||||
calloutProps: {
|
||||
minPagePadding: 0
|
||||
},
|
||||
items: [
|
||||
{
|
||||
key: "Persona",
|
||||
onRender: this._renderPersonaComponent
|
||||
},
|
||||
{
|
||||
key: "SwitchDirectory",
|
||||
onRender: this._renderSwitchDirectory
|
||||
},
|
||||
{
|
||||
key: "SignOut",
|
||||
onRender: this._renderSignOut
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const personaProps: IPersonaSharedProps = {
|
||||
imageUrl: user.imageUrl,
|
||||
text: user.email,
|
||||
secondaryText: user.tenantName,
|
||||
showSecondaryText: true,
|
||||
showInitialsUntilImageLoads: true,
|
||||
initialsColor: PersonaInitialsColor.teal,
|
||||
size: PersonaSize.size28,
|
||||
className: "mecontrolHeaderPersona"
|
||||
};
|
||||
|
||||
const buttonProps: IButtonProps = {
|
||||
id: "mecontrolHeader",
|
||||
className: "mecontrolHeaderButton",
|
||||
menuProps: menuProps,
|
||||
onRenderMenuIcon: () => <span />,
|
||||
styles: {
|
||||
rootHovered: { backgroundColor: "#393939" },
|
||||
rootFocused: { backgroundColor: "#393939" },
|
||||
rootPressed: { backgroundColor: "#393939" },
|
||||
rootExpanded: { backgroundColor: "#393939" }
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FocusZone>
|
||||
<DefaultButton {...buttonProps}>
|
||||
<Persona {...personaProps} />
|
||||
</DefaultButton>
|
||||
</FocusZone>
|
||||
);
|
||||
}
|
||||
|
||||
private _renderPersonaComponent = (): JSX.Element => {
|
||||
const { user } = this.props;
|
||||
const personaProps: IPersonaSharedProps = {
|
||||
imageUrl: user.imageUrl,
|
||||
text: user.name,
|
||||
secondaryText: user.email,
|
||||
showSecondaryText: true,
|
||||
showInitialsUntilImageLoads: true,
|
||||
initialsColor: PersonaInitialsColor.teal,
|
||||
size: PersonaSize.size72,
|
||||
className: "mecontrolContextualMenuPersona"
|
||||
};
|
||||
|
||||
return <Persona {...personaProps} />;
|
||||
};
|
||||
|
||||
private _renderSwitchDirectory = (): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className="switchDirectoryLink"
|
||||
onClick={(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) =>
|
||||
this.props.onSwitchDirectoryClick(e)
|
||||
}
|
||||
>
|
||||
Switch Directory
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private _renderSignOut = (): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className="signOutLink"
|
||||
onClick={(e: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => this.props.onSignOutClick(e)}
|
||||
>
|
||||
Sign out
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
private _renderSignInComponent = (): JSX.Element => {
|
||||
const buttonProps: IButtonProps = {
|
||||
className: "mecontrolSigninButton",
|
||||
text: "Sign In",
|
||||
onClick: (e: React.MouseEvent<BaseButton>) => this.props.onSignInClick(e),
|
||||
styles: {
|
||||
rootHovered: { backgroundColor: "#393939", color: "#fff" },
|
||||
rootFocused: { backgroundColor: "#393939", color: "#fff" },
|
||||
rootPressed: { backgroundColor: "#393939", color: "#fff" }
|
||||
}
|
||||
};
|
||||
return <DefaultButton {...buttonProps} />;
|
||||
};
|
||||
}
|
||||
16
src/Explorer/Menus/NavBar/MeControlComponentAdapter.tsx
Normal file
16
src/Explorer/Menus/NavBar/MeControlComponentAdapter.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* This adapter is responsible to render the React component
|
||||
* If the component signals a change through the callback passed in the properties, it must render the React component when appropriate
|
||||
* and update any knockout observables passed from the parent.
|
||||
*/
|
||||
import * as React from "react";
|
||||
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
|
||||
import { MeControlComponent, MeControlComponentProps } from "./MeControlComponent";
|
||||
|
||||
export class MeControlComponentAdapter implements ReactAdapter {
|
||||
public parameters: ko.Observable<MeControlComponentProps>;
|
||||
|
||||
public renderComponent(): JSX.Element {
|
||||
return <MeControlComponent {...this.parameters()} />;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`test render renders not signed in 1`] = `
|
||||
<CustomizedDefaultButton
|
||||
className="mecontrolSigninButton"
|
||||
onClick={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"rootFocused": Object {
|
||||
"backgroundColor": "#393939",
|
||||
"color": "#fff",
|
||||
},
|
||||
"rootHovered": Object {
|
||||
"backgroundColor": "#393939",
|
||||
"color": "#fff",
|
||||
},
|
||||
"rootPressed": Object {
|
||||
"backgroundColor": "#393939",
|
||||
"color": "#fff",
|
||||
},
|
||||
}
|
||||
}
|
||||
text="Sign In"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`test render renders signed in with full info 1`] = `
|
||||
<FocusZone
|
||||
direction={2}
|
||||
isCircularNavigation={false}
|
||||
>
|
||||
<CustomizedDefaultButton
|
||||
className="mecontrolHeaderButton"
|
||||
id="mecontrolHeader"
|
||||
menuProps={
|
||||
Object {
|
||||
"calloutProps": Object {
|
||||
"minPagePadding": 0,
|
||||
},
|
||||
"className": "mecontrolContextualMenu",
|
||||
"directionalHint": 6,
|
||||
"directionalHintFixed": true,
|
||||
"isBeakVisible": false,
|
||||
"items": Array [
|
||||
Object {
|
||||
"key": "Persona",
|
||||
"onRender": [Function],
|
||||
},
|
||||
Object {
|
||||
"key": "SwitchDirectory",
|
||||
"onRender": [Function],
|
||||
},
|
||||
Object {
|
||||
"key": "SignOut",
|
||||
"onRender": [Function],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
onRenderMenuIcon={[Function]}
|
||||
styles={
|
||||
Object {
|
||||
"rootExpanded": Object {
|
||||
"backgroundColor": "#393939",
|
||||
},
|
||||
"rootFocused": Object {
|
||||
"backgroundColor": "#393939",
|
||||
},
|
||||
"rootHovered": Object {
|
||||
"backgroundColor": "#393939",
|
||||
},
|
||||
"rootPressed": Object {
|
||||
"backgroundColor": "#393939",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<StyledPersonaBase
|
||||
className="mecontrolHeaderPersona"
|
||||
imageUrl="../../../../images/dotnet.png"
|
||||
initialsColor={3}
|
||||
secondaryText="Contoso"
|
||||
showInitialsUntilImageLoads={true}
|
||||
showSecondaryText={true}
|
||||
size={7}
|
||||
text="testuser@contoso.com"
|
||||
/>
|
||||
</CustomizedDefaultButton>
|
||||
</FocusZone>
|
||||
`;
|
||||
Reference in New Issue
Block a user