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 PropTypes from '@root/prop-types';
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 { 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`
color: ${colors.base.secondaryDark};
font-size: ${remcalc(16)};
@ -76,7 +70,7 @@ const StyledAvatar = styled(Avatar)`
const arrowPosition = {
bottom: '100%',
right: '10%'
right: 18
};
const Header = ({
@ -96,19 +90,21 @@ const Header = ({
};
const tooltipComponent = !tooltip ? null : (
<StyledTooltipWrapper>
<Tooltip arrowPosition={arrowPosition}>
<li>
<Link to='/'>My Account</Link>
</li>
<li>
<Link to='/'>Settings</Link>
</li>
<li>
<Link to='/'>About</Link>
</li>
</Tooltip>
</StyledTooltipWrapper>
<Tooltip
arrowPosition={arrowPosition}
right={0}
top={39}
>
<li>
<TooltipButton to='/'>My Account</TooltipButton>
</li>
<li>
<TooltipButton to='/'>Settings</TooltipButton>
</li>
<li>
<TooltipButton to='/'>About</TooltipButton>
</li>
</Tooltip>
);
return (

View File

@ -30,6 +30,7 @@ const TitleInnerContainer = styled.div`
`;
const ServiceItem = ({
onQuickActions=() => {},
org = '',
project = '',
service = {}
@ -72,6 +73,10 @@ const ServiceItem = ({
<ListItemDescription>Flags</ListItemDescription>
);
const onOptionsClick = (evt) => {
onQuickActions(evt, service.uuid);
};
const header = isChild ? null : (
<ListItemHeader>
<ListItemMeta>
@ -79,7 +84,9 @@ const ServiceItem = ({
{subtitle}
{description}
</ListItemMeta>
<ListItemOptions></ListItemOptions>
<ListItemOptions onClick={onOptionsClick}>
</ListItemOptions>
</ListItemHeader>
);
@ -113,6 +120,7 @@ const ServiceItem = ({
};
ServiceItem.propTypes = {
onQuickActions: React.PropTypes.func,
org: React.PropTypes.string,
project: React.PropTypes.string,
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 styled from 'styled-components';
import { connect } from 'react-redux';
import PropTypes from '@root/prop-types';
import ServiceItem from '@components/service/item';
import UnmanagedInstances from '@components/services/unmanaged-instances';
import { toggleTooltip } from '@state/actions';
import ServicesTooltip from '@components/services/tooltip';
import {
orgByIdSelector,
projectByIdSelector,
servicesByProjectIdSelector
servicesByProjectIdSelector,
serviceUiTooltipSelector
} from '@state/selectors';
const Services = (props) => {
const {
org = {},
project = {},
services = []
} = props;
const StyledContainer = styled.div`
position: relative;
`;
const instances = 5;
const serviceList = services.map((service) => (
<ServiceItem
key={service.uuid}
org={org.id}
project={project.id}
service={service}
/>
));
class Services extends React.Component {
return (
<div>
{ serviceList }
{ instances && <UnmanagedInstances instances={instances} /> }
</div>
);
};
ref(name) {
this._refs = this._refs || {};
return (el) => {
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 = {
org: PropTypes.org,
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, {
@ -49,9 +98,15 @@ const mapStateToProps = (state, {
}) => ({
org: orgByIdSelector(match.params.org)(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(
mapStateToProps
mapStateToProps,
mapDispatchToProps
)(Services);

View File

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

View File

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

View File

@ -52,3 +52,5 @@ export const switchMonitorViewPage =
createAction(`${APP}/SWITCH_MONITOR_VIEW_PAGE`);
export const handleNewProject =
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 { addMetric, toggleServiceCollapsed } from '@state/actions';
import {
addMetric,
toggleServiceCollapsed,
toggleTooltip
} from '@state/actions';
import { toggleCollapsed } from '@state/reducers/common';
const getMetrics = (stateMetrics, addMetric, metric) => {
@ -42,5 +46,31 @@ export default handleActions({
action.payload.service,
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 orgUiSections = (state) => get(state, 'orgs.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 orgs = (state) => get(state, 'orgs.data', []);
const orgUI = (state) => get(state, 'orgs.ui', []);
@ -249,5 +250,6 @@ export {
members as membersSelector,
peopleByProjectId as peopleByProjectIdSelector,
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 {
absolutePosition,
baseBox,
pseudoEl,
Baseline,
moveZ
moveZ,
getMeasurement
} from '../../shared/composers';
import { colors } from '../../shared/constants';
import { boxes, colors, tooltipShadow } from '../../shared/constants';
import styled from 'styled-components';
import React from 'react';
const ItemPadder = 9;
const WrapperPadder = 24;
const StyledContainer = styled.div`
${(props) => absolutePosition(props)}
`;
const StyledList = styled.ul`
background: ${colors.base.white};
box-sizing: border-box;
color: ${colors.base.text};
display: inline-block;
font-family: sans-serif;
list-style-type: none;
margin: 0;
padding: 0;
min-width: ${remcalc(200)};
padding: ${unitcalc(2)} 0;
/*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}
${baseBox()}
${baseBox({
shadow: tooltipShadow
})}
${moveZ({
amount: 1
})}
& > * {
/*& > * {
padding: ${remcalc(ItemPadder)} ${remcalc(WrapperPadder)};
&:hover {
background: ${colors.base.grey};
}
}
}*/
&:after, &:before {
border: solid transparent;
@ -49,14 +67,14 @@ const StyledList = styled.ul`
&:after {
border-color: rgba(255, 255, 255, 0);
border-bottom-color: ${colors.base.white};
border-width: ${remcalc(10)};
margin-left: ${remcalc(-10)};
border-width: ${remcalc(3)};
margin-left: ${remcalc(-3)};
}
&:before {
border-color: rgba(216, 216, 216, 0);
border-bottom-color: ${colors.base.greyDark};
border-width: ${remcalc(12)};
margin-left: ${remcalc(-12)};
border-bottom-color: ${colors.base.grey};
border-width: ${remcalc(5)};
margin-left: ${remcalc(-5)};
}
`;
@ -64,13 +82,15 @@ const Tooltip = ({
children,
arrowPosition = {
bottom: '100%',
left: '10%'
left: '50%'
},
...props
}) => (
<StyledList arrowPosition={arrowPosition} {...props}>
{children}
</StyledList>
<StyledContainer {...props}>
<StyledList arrowPosition={arrowPosition} {...props}>
{children}
</StyledList>
</StyledContainer>
);
Tooltip.propTypes = {
@ -81,3 +101,10 @@ Tooltip.propTypes = {
export default Baseline(
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,
data,
index,
onDragStart
onDragStart,
onQuickActions
}) => {
const {
@ -38,7 +39,26 @@ const GraphNode = ({
}
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) => {
@ -102,7 +122,8 @@ GraphNode.propTypes = {
connected: React.PropTypes.bool,
data: React.PropTypes.object.isRequired,
index: React.PropTypes.number.isRequired,
onDragStart: React.PropTypes.func
onDragStart: React.PropTypes.func,
onQuickActions: React.PropTypes.func
};
export default Baseline(

View File

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

View File

@ -1,5 +1,6 @@
import styled, { css } from 'styled-components';
import camelCase from 'camel-case';
import isString from 'lodash.isstring';
import { boxes, colors } from '../constants';
import { unitcalc, remcalc } from '../functions';
@ -82,15 +83,25 @@ export const baseBox = ({
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 = (
positions = {}
) => css`
content: "";
position: absolute;
top: ${positions.top || 'auto'};
right: ${positions.right || 'auto'};
bottom: ${positions.bottom || 'auto'};
left: ${positions.left || 'auto'};
${absolutePosition(positions)};
`;
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 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 tooltipShadow =
`0 ${remcalc(2)} ${remcalc(6)} ${remcalc(1)} rgba(0, 0, 0, 0.1)`;
export const border = {
checked: `${remcalc(1)} solid ${base.primary}`,