Add positionig for services tooltip

adding tooltip to store and passing props down to component

Add tooltip to services list

Add queck actions tooltip to services
This commit is contained in:
JUDIT GRESKOVITS 2017-03-09 15:17:47 +00:00 committed by Sérgio Ramos
parent 2bede6e669
commit b229c7b63e
15 changed files with 354 additions and 86 deletions

View File

@ -8,7 +8,7 @@ import { remcalc } from '@ui/shared/functions';
import Logo from '../../resources/logo.svg'; import Logo from '../../resources/logo.svg';
import PropTypes from '@root/prop-types'; import PropTypes from '@root/prop-types';
import Row from '@ui/components/row'; import Row from '@ui/components/row';
import Tooltip from '@ui/components/tooltip'; import Tooltip, { TooltipButton } from '@ui/components/tooltip';
import { pseudoEl, typography } from '@ui/shared/composers'; import { pseudoEl, typography } from '@ui/shared/composers';
import { colors } from '@ui/shared/constants'; import { colors } from '@ui/shared/constants';
@ -46,12 +46,6 @@ const StyledAvatarWrapper = styled.div`
} }
`; `;
const StyledTooltipWrapper = styled.div`
right: ${remcalc(-18)};
bottom: ${remcalc(-140)};
position: absolute;
`;
const StyledName = styled.span` const StyledName = styled.span`
color: ${colors.base.secondaryDark}; color: ${colors.base.secondaryDark};
font-size: ${remcalc(16)}; font-size: ${remcalc(16)};
@ -76,7 +70,7 @@ const StyledAvatar = styled(Avatar)`
const arrowPosition = { const arrowPosition = {
bottom: '100%', bottom: '100%',
right: '10%' right: 18
}; };
const Header = ({ const Header = ({
@ -96,19 +90,21 @@ const Header = ({
}; };
const tooltipComponent = !tooltip ? null : ( const tooltipComponent = !tooltip ? null : (
<StyledTooltipWrapper> <Tooltip
<Tooltip arrowPosition={arrowPosition}> arrowPosition={arrowPosition}
<li> right={0}
<Link to='/'>My Account</Link> top={39}
</li> >
<li> <li>
<Link to='/'>Settings</Link> <TooltipButton to='/'>My Account</TooltipButton>
</li> </li>
<li> <li>
<Link to='/'>About</Link> <TooltipButton to='/'>Settings</TooltipButton>
</li> </li>
</Tooltip> <li>
</StyledTooltipWrapper> <TooltipButton to='/'>About</TooltipButton>
</li>
</Tooltip>
); );
return ( return (

View File

@ -30,6 +30,7 @@ const TitleInnerContainer = styled.div`
`; `;
const ServiceItem = ({ const ServiceItem = ({
onQuickActions=() => {},
org = '', org = '',
project = '', project = '',
service = {} service = {}
@ -72,6 +73,10 @@ const ServiceItem = ({
<ListItemDescription>Flags</ListItemDescription> <ListItemDescription>Flags</ListItemDescription>
); );
const onOptionsClick = (evt) => {
onQuickActions(evt, service.uuid);
};
const header = isChild ? null : ( const header = isChild ? null : (
<ListItemHeader> <ListItemHeader>
<ListItemMeta> <ListItemMeta>
@ -79,7 +84,9 @@ const ServiceItem = ({
{subtitle} {subtitle}
{description} {description}
</ListItemMeta> </ListItemMeta>
<ListItemOptions></ListItemOptions> <ListItemOptions onClick={onOptionsClick}>
</ListItemOptions>
</ListItemHeader> </ListItemHeader>
); );
@ -113,6 +120,7 @@ const ServiceItem = ({
}; };
ServiceItem.propTypes = { ServiceItem.propTypes = {
onQuickActions: React.PropTypes.func,
org: React.PropTypes.string, org: React.PropTypes.string,
project: React.PropTypes.string, project: React.PropTypes.string,
service: PropTypes.service service: PropTypes.service

View File

@ -0,0 +1,41 @@
import React from 'react';
import Tooltip, { TooltipButton, TooltipDivider } from '@ui/components/tooltip';
const ServicesTooltip = ({
show,
position
}) => {
return show ? (
<Tooltip {...position}>
<li>
<TooltipButton>Scale</TooltipButton>
</li>
<li>
<TooltipButton>Rollback</TooltipButton>
</li>
<li>
<TooltipButton>Reprovision</TooltipButton>
</li>
<li>
<TooltipButton>Transfer</TooltipButton>
</li>
<li>
<TooltipButton>Setup metrics</TooltipButton>
</li>
<TooltipDivider />
<li>
<TooltipButton>Stop</TooltipButton>
</li>
<li>
<TooltipButton>Delete</TooltipButton>
</li>
</Tooltip>
) : null;
};
ServicesTooltip.propTypes = {
position: React.PropTypes.object,
show: React.PropTypes.bool
};
export default ServicesTooltip;

View File

@ -1,44 +1,93 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from '@root/prop-types'; import PropTypes from '@root/prop-types';
import ServiceItem from '@components/service/item'; import ServiceItem from '@components/service/item';
import UnmanagedInstances from '@components/services/unmanaged-instances'; import UnmanagedInstances from '@components/services/unmanaged-instances';
import { toggleTooltip } from '@state/actions';
import ServicesTooltip from '@components/services/tooltip';
import { import {
orgByIdSelector, orgByIdSelector,
projectByIdSelector, projectByIdSelector,
servicesByProjectIdSelector servicesByProjectIdSelector,
serviceUiTooltipSelector
} from '@state/selectors'; } from '@state/selectors';
const Services = (props) => { const StyledContainer = styled.div`
const { position: relative;
org = {}, `;
project = {},
services = []
} = props;
const instances = 5; class Services extends React.Component {
const serviceList = services.map((service) => (
<ServiceItem
key={service.uuid}
org={org.id}
project={project.id}
service={service}
/>
));
return ( ref(name) {
<div> this._refs = this._refs || {};
{ serviceList }
{ instances && <UnmanagedInstances instances={instances} /> } return (el) => {
</div> this._refs[name] = el;
); };
}; }
render() {
const {
org = {},
project = {},
services = [],
toggleTooltip = (() => {}),
uiTooltip = {}
} = this.props;
const onQuickActions = (evt, service) => {
const list = this._refs.container;
const listRect = list.getBoundingClientRect();
const button = evt.currentTarget;
const buttonRect = button.getBoundingClientRect();
const position = {
left: buttonRect.left - listRect.left
+ (buttonRect.right - buttonRect.left)/2,
top: buttonRect.bottom - listRect.top
};
toggleTooltip({
service: service,
position: position
});
};
const instances = 5;
const serviceList = services.map((service) => (
<ServiceItem
key={service.uuid}
onQuickActions={onQuickActions}
org={org.id}
project={project.id}
service={service}
uiTooltip={uiTooltip}
/>
));
return (
<div>
{ instances && <UnmanagedInstances instances={instances} /> }
<StyledContainer>
<div ref={this.ref('container')}>
{serviceList}
<ServicesTooltip {...uiTooltip} />
</div>
</StyledContainer>
</div>
);
}
}
Services.propTypes = { Services.propTypes = {
org: PropTypes.org, org: PropTypes.org,
project: PropTypes.project, project: PropTypes.project,
services: React.PropTypes.arrayOf(PropTypes.service) services: React.PropTypes.arrayOf(PropTypes.service),
toggleTooltip: React.PropTypes.func,
uiTooltip: React.PropTypes.object
}; };
const mapStateToProps = (state, { const mapStateToProps = (state, {
@ -49,9 +98,15 @@ const mapStateToProps = (state, {
}) => ({ }) => ({
org: orgByIdSelector(match.params.org)(state), org: orgByIdSelector(match.params.org)(state),
project: projectByIdSelector(match.params.projectId)(state), project: projectByIdSelector(match.params.projectId)(state),
services: servicesByProjectIdSelector(match.params.projectId)(state) services: servicesByProjectIdSelector(match.params.projectId)(state),
uiTooltip: serviceUiTooltipSelector(state)
});
const mapDispatchToProps = (dispatch) => ({
toggleTooltip: (data) => dispatch(toggleTooltip(data))
}); });
export default connect( export default connect(
mapStateToProps mapStateToProps,
mapDispatchToProps
)(Services); )(Services);

View File

@ -1,26 +1,49 @@
import React from 'react'; import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import styled from 'styled-components';
import PropTypes from '@root/prop-types'; import PropTypes from '@root/prop-types';
import { TopologyGraph } from '@ui/components/topology'; import { TopologyGraph } from '@ui/components/topology';
import ServicesTooltip from '@components/services/tooltip';
import { toggleTooltip } from '@state/actions';
import { import {
orgByIdSelector, orgByIdSelector,
projectByIdSelector, projectByIdSelector,
servicesForTopologySelector servicesForTopologySelector,
serviceUiTooltipSelector
} from '@state/selectors'; } from '@state/selectors';
const StyledContainer = styled.div`
position: relative;
`;
const Services = (props) => { const Services = (props) => {
const { const {
services = [] services = [],
toggleTooltip,
uiTooltip
} = props; } = props;
const onQuickActions = (evt, tooltipData) => {
toggleTooltip(tooltipData);
};
return ( return (
<TopologyGraph services={services} /> <StyledContainer>
<TopologyGraph
onQuickActions={onQuickActions}
services={services}
/>
<ServicesTooltip {...uiTooltip} />
</StyledContainer>
); );
}; };
Services.propTypes = { Services.propTypes = {
services: React.PropTypes.arrayOf(PropTypes.service) services: React.PropTypes.arrayOf(PropTypes.service),
toggleTooltip: React.PropTypes.func,
uiTooltip: React.PropTypes.object
}; };
const mapStateToProps = (state, { const mapStateToProps = (state, {
@ -30,9 +53,15 @@ const mapStateToProps = (state, {
}) => ({ }) => ({
org: orgByIdSelector(match.params.org)(state), org: orgByIdSelector(match.params.org)(state),
project: projectByIdSelector(match.params.projectId)(state), project: projectByIdSelector(match.params.projectId)(state),
services: servicesForTopologySelector(match.params.projectId)(state) services: servicesForTopologySelector(match.params.projectId)(state),
uiTooltip: serviceUiTooltipSelector(state)
});
const mapDispatchToProps = (dispatch) => ({
toggleTooltip: (data) => dispatch(toggleTooltip(data))
}); });
export default connect( export default connect(
mapStateToProps mapStateToProps,
mapDispatchToProps
)(Services); )(Services);

View File

@ -627,7 +627,10 @@
"activity-feed", "activity-feed",
"service-manifest", "service-manifest",
"firewall" "firewall"
] ],
"tooltip": {
"show": false
}
}, },
"data": [{ "data": [{
"uuid": "081a792c-47e0-4439-924b-2efa9788ae9e", "uuid": "081a792c-47e0-4439-924b-2efa9788ae9e",

View File

@ -52,3 +52,5 @@ export const switchMonitorViewPage =
createAction(`${APP}/SWITCH_MONITOR_VIEW_PAGE`); createAction(`${APP}/SWITCH_MONITOR_VIEW_PAGE`);
export const handleNewProject = export const handleNewProject =
createAction(`${APP}/CREATE_NEW_PROJECT`); createAction(`${APP}/CREATE_NEW_PROJECT`);
export const toggleTooltip =
createAction(`${APP}/TOGGLE_QUICK_ACTIONS_TOOLTIP`);

View File

@ -1,5 +1,9 @@
import { handleActions } from 'redux-actions'; import { handleActions } from 'redux-actions';
import { addMetric, toggleServiceCollapsed } from '@state/actions'; import {
addMetric,
toggleServiceCollapsed,
toggleTooltip
} from '@state/actions';
import { toggleCollapsed } from '@state/reducers/common'; import { toggleCollapsed } from '@state/reducers/common';
const getMetrics = (stateMetrics, addMetric, metric) => { const getMetrics = (stateMetrics, addMetric, metric) => {
@ -42,5 +46,31 @@ export default handleActions({
action.payload.service, action.payload.service,
action.payload.metric action.payload.metric
) )
}) }),
[toggleTooltip.toString()]: (state, action) => {
const {
position,
service
} = action.payload;
const show = state.ui.tooltip.service !== service;
const tooltip = show ? {
show: true,
position: {
...position
},
service: service
} : {
show: false
};
return {
...state,
ui: {
...state.ui,
tooltip: tooltip
}
};
}
}, {}); }, {});

View File

@ -7,6 +7,7 @@ const account = (state) => get(state, 'account.data', {});
const accountUi = (state) => get(state, 'account.ui', {}); const accountUi = (state) => get(state, 'account.ui', {});
const orgUiSections = (state) => get(state, 'orgs.ui.sections', []); const orgUiSections = (state) => get(state, 'orgs.ui.sections', []);
const projectUiSections = (state) => get(state, 'projects.ui.sections', []); const projectUiSections = (state) => get(state, 'projects.ui.sections', []);
const serviceUiTooltip = (state) => get(state, 'services.ui.tooltip', []);
const serviceUiSections = (state) => get(state, 'services.ui.sections', []); const serviceUiSections = (state) => get(state, 'services.ui.sections', []);
const orgs = (state) => get(state, 'orgs.data', []); const orgs = (state) => get(state, 'orgs.data', []);
const orgUI = (state) => get(state, 'orgs.ui', []); const orgUI = (state) => get(state, 'orgs.ui', []);
@ -249,5 +250,6 @@ export {
members as membersSelector, members as membersSelector,
peopleByProjectId as peopleByProjectIdSelector, peopleByProjectId as peopleByProjectIdSelector,
projectsUI as projectUISelector, projectsUI as projectUISelector,
projectIndexById as projectIndexByIdSelect projectIndexById as projectIndexByIdSelect,
serviceUiTooltip as serviceUiTooltipSelector
}; };

View File

@ -0,0 +1,36 @@
import styled from 'styled-components';
import { colors } from '../../shared/constants';
import { unitcalc } from '../../shared/functions';
import Button from '../button';
const TooltipButton = styled(Button)`
width: 100%;
padding: ${unitcalc(1)} ${unitcalc(3)};
background-color: ${colors.base.white};
color: ${colors.base.secondary};
text-align: left;
border: none;
box-shadow: none;
&:focus {
background-color: ${colors.base.white};
color: ${colors.base.primary};
border: none;
}
&:hover {
background-color: ${colors.base.white};
color: ${colors.base.primary};
border: none;
}
&:active,
&:active:hover,
&:active:focus {
background-color: ${colors.base.white};
color: ${colors.base.primary};
border: none;
}
`;
export default TooltipButton;

View File

@ -1,42 +1,60 @@
import { remcalc } from '../../shared/functions'; import { remcalc, unitcalc } from '../../shared/functions';
import { import {
absolutePosition,
baseBox, baseBox,
pseudoEl, pseudoEl,
Baseline, Baseline,
moveZ moveZ,
getMeasurement
} from '../../shared/composers'; } from '../../shared/composers';
import { colors } from '../../shared/constants'; import { boxes, colors, tooltipShadow } from '../../shared/constants';
import styled from 'styled-components'; import styled from 'styled-components';
import React from 'react'; import React from 'react';
const ItemPadder = 9; const ItemPadder = 9;
const WrapperPadder = 24; const WrapperPadder = 24;
const StyledContainer = styled.div`
${(props) => absolutePosition(props)}
`;
const StyledList = styled.ul` const StyledList = styled.ul`
background: ${colors.base.white}; background: ${colors.base.white};
box-sizing: border-box;
color: ${colors.base.text}; color: ${colors.base.text};
display: inline-block; display: inline-block;
font-family: sans-serif; font-family: sans-serif;
list-style-type: none; list-style-type: none;
margin: 0; margin: 0;
padding: 0; padding: ${unitcalc(2)} 0;
min-width: ${remcalc(200)}; /*min-width: ${remcalc(200)};*/
position: absolute;
top: 4px;
${(props) => {
return props.arrowPosition.left ?
`left: -${getMeasurement(props.arrowPosition.left)}` :
props.arrowPosition.right ?
`right: -${getMeasurement(props.arrowPosition.right)}` : null;
}};
${props => props.styles} ${props => props.styles}
${baseBox()} ${baseBox({
shadow: tooltipShadow
})}
${moveZ({ ${moveZ({
amount: 1 amount: 1
})} })}
& > * { /*& > * {
padding: ${remcalc(ItemPadder)} ${remcalc(WrapperPadder)}; padding: ${remcalc(ItemPadder)} ${remcalc(WrapperPadder)};
&:hover { &:hover {
background: ${colors.base.grey}; background: ${colors.base.grey};
} }
} }*/
&:after, &:before { &:after, &:before {
border: solid transparent; border: solid transparent;
@ -49,14 +67,14 @@ const StyledList = styled.ul`
&:after { &:after {
border-color: rgba(255, 255, 255, 0); border-color: rgba(255, 255, 255, 0);
border-bottom-color: ${colors.base.white}; border-bottom-color: ${colors.base.white};
border-width: ${remcalc(10)}; border-width: ${remcalc(3)};
margin-left: ${remcalc(-10)}; margin-left: ${remcalc(-3)};
} }
&:before { &:before {
border-color: rgba(216, 216, 216, 0); border-color: rgba(216, 216, 216, 0);
border-bottom-color: ${colors.base.greyDark}; border-bottom-color: ${colors.base.grey};
border-width: ${remcalc(12)}; border-width: ${remcalc(5)};
margin-left: ${remcalc(-12)}; margin-left: ${remcalc(-5)};
} }
`; `;
@ -64,13 +82,15 @@ const Tooltip = ({
children, children,
arrowPosition = { arrowPosition = {
bottom: '100%', bottom: '100%',
left: '10%' left: '50%'
}, },
...props ...props
}) => ( }) => (
<StyledList arrowPosition={arrowPosition} {...props}> <StyledContainer {...props}>
{children} <StyledList arrowPosition={arrowPosition} {...props}>
</StyledList> {children}
</StyledList>
</StyledContainer>
); );
Tooltip.propTypes = { Tooltip.propTypes = {
@ -81,3 +101,10 @@ Tooltip.propTypes = {
export default Baseline( export default Baseline(
Tooltip Tooltip
); );
export { default as TooltipButton } from './button';
export const TooltipDivider = styled.div`
border-top: ${boxes.border.unchecked};
margin: ${unitcalc(1)} 0 ${unitcalc(1.5)} 0;
`;

View File

@ -13,7 +13,8 @@ const GraphNode = ({
connected, connected,
data, data,
index, index,
onDragStart onDragStart,
onQuickActions
}) => { }) => {
const { const {
@ -38,7 +39,26 @@ const GraphNode = ({
} }
const onButtonClick = (evt) => { const onButtonClick = (evt) => {
// console.log('Rect clicked!!!');
const tooltipPosition = {
x: data.x + Constants.buttonRect.x + Constants.buttonRect.width/2,
y: data.y + Constants.buttonRect.y + Constants.buttonRect.height
};
if ( connected ) {
tooltipPosition.x = tooltipPosition.x + left;
tooltipPosition.y = tooltipPosition.y + top;
}
const d = {
service: data.uuid,
position: {
left: tooltipPosition.x,
top: tooltipPosition.y
}
};
onQuickActions(evt, d);
}; };
const onStart = (evt) => { const onStart = (evt) => {
@ -102,7 +122,8 @@ GraphNode.propTypes = {
connected: React.PropTypes.bool, connected: React.PropTypes.bool,
data: React.PropTypes.object.isRequired, data: React.PropTypes.object.isRequired,
index: React.PropTypes.number.isRequired, index: React.PropTypes.number.isRequired,
onDragStart: React.PropTypes.func onDragStart: React.PropTypes.func,
onQuickActions: React.PropTypes.func
}; };
export default Baseline( export default Baseline(

View File

@ -88,7 +88,7 @@ class TopologyGraph extends React.Component {
const n = Math.ceil( const n = Math.ceil(
Math.log( Math.log(
nextSimulation.alphaMin()) / Math.log( nextSimulation.alphaMin()) / Math.log(
1 - nextSimulation.alphaDecay())) - 200; 1 - nextSimulation.alphaDecay()));
for (var i = 0; i < n; ++i) { for (var i = 0; i < n; ++i) {
nextSimulation.tick(); nextSimulation.tick();
} }
@ -104,7 +104,10 @@ class TopologyGraph extends React.Component {
render() { render() {
const services = this.props.services; const {
onQuickActions,
services
} = this.props;
const { const {
nodes, nodes,
@ -218,6 +221,7 @@ class TopologyGraph extends React.Component {
data={n} data={n}
index={index} index={index}
onDragStart={onDragStart} onDragStart={onDragStart}
onQuickActions={onQuickActions}
connected={n.id !== 'consul'} connected={n.id !== 'consul'}
/> />
)); ));
@ -250,6 +254,7 @@ class TopologyGraph extends React.Component {
} }
TopologyGraph.propTypes = { TopologyGraph.propTypes = {
onQuickActions: React.PropTypes.func,
services: React.PropTypes.array services: React.PropTypes.array
}; };

View File

@ -1,5 +1,6 @@
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import camelCase from 'camel-case'; import camelCase from 'camel-case';
import isString from 'lodash.isstring';
import { boxes, colors } from '../constants'; import { boxes, colors } from '../constants';
import { unitcalc, remcalc } from '../functions'; import { unitcalc, remcalc } from '../functions';
@ -82,15 +83,25 @@ export const baseBox = ({
box-shadow: ${shadow}; box-shadow: ${shadow};
`; `;
export const getMeasurement = (measurement) =>
isString(measurement) ? measurement :
!isNaN(measurement) ? `${measurement}px`: 'auto';
export const absolutePosition = (
positions = {}
) => css`
position: absolute;
top: ${getMeasurement(positions.top)};
right: ${getMeasurement(positions.right)};
bottom: ${getMeasurement(positions.bottom)};
left: ${getMeasurement(positions.left)};
`;
export const pseudoEl = ( export const pseudoEl = (
positions = {} positions = {}
) => css` ) => css`
content: ""; content: "";
position: absolute; ${absolutePosition(positions)};
top: ${positions.top || 'auto'};
right: ${positions.right || 'auto'};
bottom: ${positions.bottom || 'auto'};
left: ${positions.left || 'auto'};
`; `;
export const clearfix = css` export const clearfix = css`

View File

@ -5,6 +5,8 @@ export const borderRadius = remcalc(4);
export const bottomShaddow = `0 ${remcalc(2)} 0 0 rgba(0, 0, 0, 0.05)`; export const bottomShaddow = `0 ${remcalc(2)} 0 0 rgba(0, 0, 0, 0.05)`;
export const bottomShaddowDarker = `0 ${remcalc(2)} 0 0 rgba(0, 0, 0, 0.1)`; export const bottomShaddowDarker = `0 ${remcalc(2)} 0 0 rgba(0, 0, 0, 0.1)`;
export const insetShaddow = `inset 0 ${remcalc(3)} 0 0 rgba(0, 0, 0, 0.05)`; export const insetShaddow = `inset 0 ${remcalc(3)} 0 0 rgba(0, 0, 0, 0.05)`;
export const tooltipShadow =
`0 ${remcalc(2)} ${remcalc(6)} ${remcalc(1)} rgba(0, 0, 0, 0.1)`;
export const border = { export const border = {
checked: `${remcalc(1)} solid ${base.primary}`, checked: `${remcalc(1)} solid ${base.primary}`,