feat(my-joy-beta): instance list

fixes #869 #871
This commit is contained in:
Sérgio Ramos 2017-11-23 12:18:38 +00:00 committed by Sérgio Ramos
parent c087f0bd9f
commit 9cf7a94e17
47 changed files with 3409 additions and 1154 deletions

View File

@ -1,6 +1,6 @@
{
"name": "joyent-icons",
"version": "1.0.1",
"version": "1.1.0",
"license": "MPL-2.0",
"repository": "github:yldio/joyent-portal",
"main": "dist/umd/index.js",

View File

@ -1,7 +1,7 @@
import React from 'react';
import Colors from './colors';
export default ({ light = false, disabled, ...rest, }) => (
export default ({ light = false, disabled, ...rest }) => (
<Colors white secondary>
{({ white, text }) => (
<svg

View File

@ -21,21 +21,19 @@
"apr-intercept": "^1.0.4",
"clipboard-copy": "^1.2.0",
"date-fns": "^1.29.0",
"declarative-redux-form": "^1.0.3",
"joyent-ui-toolkit": "^2.0.1",
"declarative-redux-form": "^1.0.4",
"joyent-ui-toolkit": "^2.4.0",
"lodash.find": "^4.6.0",
"lodash.get": "^4.4.2",
"lodash.isstring": "^4.0.1",
"lodash.sortby": "^4.7.0",
"lunr": "^2.1.4",
"moment": "^2.19.2",
"normalized-styled-components": "^1.0.17",
"param-case": "^2.1.1",
"prop-types": "^15.6.0",
"react": "^16.1.1",
"react-apollo": "^1.4.16",
"react-dom": "^16.1.1",
"react-json-view": "^1.13.3",
"react-redux": "^5.0.6",
"react-redux-values": "^1.0.2",
"react-router": "^4.2.0",

View File

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="theme-color" content="#1E313B">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">

View File

@ -0,0 +1,478 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders <CopiableField /> without throwing 1`] = `
.c0 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c1 {
box-sizing: border-box;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
.c4 {
box-sizing: border-box;
font-family: 'Libre Franklin', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, Helvetica, sans-serif;
width: 100%;
height: 3rem;
min-height: 3rem;
margin-bottom: 0.5rem;
margin-top: 0.5rem;
padding: 0.8125rem 1.125rem;
border-radius: 0.25rem;
border: 0.0625rem solid;
max-width: 100%;
font-size: 0.9375rem;
line-height: normal !important;
font-weight: 400;
font-style: normal;
font-stretch: normal;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
outline: 0;
}
.c4:invalid {
box-shadow: none;
}
.c4:focus {
outline: 0;
}
.c2 {
font-weight: 400;
font-size: 0.9375rem;
font-style: normal;
font-stretch: normal;
display: block;
text-align: left;
margin-right: 0.75rem;
font-weight: bold;
white-space: pre;
font-size: 0.8125rem;
}
.c3 {
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
margin-bottom: 0.625rem;
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.c3 input {
padding-right: 1.875rem;
}
.c3 div {
position: relative;
left: -1.625rem;
}
.c5 {
cursor: pointer;
}
@media only screen and (min-width:0em) {
.c1 {
-webkit-flex-basis: 100%;
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
display: block;
}
}
@media only screen and (min-width:64em) {
.c1 {
-webkit-flex-basis: 58.333333333333336%;
-ms-flex-preferred-size: 58.333333333333336%;
flex-basis: 58.333333333333336%;
max-width: 58.333333333333336%;
display: block;
}
}
<div
className="c0"
>
<div
className="c1"
>
<label
className="c2"
htmlFor=""
>
test
</label>
<div
className="c3"
>
<input
className="c4"
disabled={false}
id=""
value="test"
/>
<div
onClick={undefined}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
tag={false}
>
<svg
className="c5 "
height="16"
onClick={[Function]}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
width="13"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g
transform="translate(-2367 443)"
>
<use
fill="#464646"
transform="translate(2370 -437)"
xlinkHref="#e"
/>
<use
fill="#464646"
transform="translate(2367 -443)"
xlinkHref="#f"
/>
</g>
<defs>
<path
d="M6 3L4 5V0H3v5L1 3 0 4l3.5 3L7 4 6 3z"
id="e"
/>
<path
d="M12 1h-2V0H3v1H1c-.6 0-1 .4-1 1v13c0 .6.4 1 1 1h11c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM4 1h5v2H4V1zm8 14H1V2h2v2h7V2h2v13z"
id="f"
/>
</defs>
</svg>
</div>
</div>
</div>
</div>
`;
exports[`renders <CopyToClipboardTooltip /> without throwing 1`] = `
.c0 {
cursor: pointer;
}
<div
onClick={undefined}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
tag={false}
>
<svg
className="c0 "
height="16"
onClick={[Function]}
style={
Object {
"transform": "rotate(0deg)",
}
}
version="1.1"
width="13"
xmlnsXlink="http://www.w3.org/1999/xlink"
>
<g
transform="translate(-2367 443)"
>
<use
fill="#464646"
transform="translate(2370 -437)"
xlinkHref="#e"
/>
<use
fill="#464646"
transform="translate(2367 -443)"
xlinkHref="#f"
/>
</g>
<defs>
<path
d="M6 3L4 5V0H3v5L1 3 0 4l3.5 3L7 4 6 3z"
id="e"
/>
<path
d="M12 1h-2V0H3v1H1c-.6 0-1 .4-1 1v13c0 .6.4 1 1 1h11c.6 0 1-.4 1-1V2c0-.6-.4-1-1-1zM4 1h5v2H4V1zm8 14H1V2h2v2h7V2h2v13z"
id="f"
/>
</defs>
</svg>
</div>
`;
exports[`renders <Meta /> without throwing 1`] = `
Array [
.c0 {
box-sizing: border-box;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-flex: 0 1 auto;
-ms-flex: 0 1 auto;
flex: 0 1 auto;
-webkit-flex-direction: row;
-ms-flex-direction: row;
flex-direction: row;
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
margin-right: -0.5rem;
margin-left: -0.5rem;
}
.c1 {
box-sizing: border-box;
-webkit-flex: 0 0 auto;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
padding-right: 0.5rem;
padding-left: 0.5rem;
}
.c2 {
margin: 0;
font-weight: 400;
line-height: 1.875rem;
font-size: 1.5rem;
}
.c2 + p,
.c2 + small,
.c2 + h1,
.c2 + h2,
.c2 + label,
.c2 + h3,
.c2 + h4,
.c2 + h5,
.c2 + div,
.c2 + span {
margin-top: 1.5rem;
}
@media only screen and (min-width:0em) {
.c0 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
}
@media only screen and (min-width:0em) {
.c1 {
-webkit-flex-basis: 100%;
-ms-flex-preferred-size: 100%;
flex-basis: 100%;
max-width: 100%;
display: block;
}
}
<div
className="c0"
>
<div
className="c1"
>
<h2
className="c2"
/>
</div>
</div>,
.c0 {
margin-top: 0.5rem;
margin-bottom: 1rem;
}
.c5 {
margin-top: 0.25rem;
}
.c2 {
font-weight: 400;
font-size: 0.9375rem;
font-style: normal;
font-stretch: normal;
display: block;
text-align: left;
}
.c4 {
width: 0.375rem;
height: 0.375rem;
border-radius: 0.1875rem;
display: inline-block;
margin-top: 0.0625rem;
margin-right: 0.375rem;
height: 0.6875rem;
width: 0.6875rem;
border-radius: 50%;
}
.c6 {
opacity: 0.5;
padding-right: 0.1875rem;
}
.c1 {
-webkit-align-items: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: start;
-webkit-justify-content: flex-start;
-ms-flex-pack: start;
justify-content: flex-start;
}
.c3 {
width: 0.0625rem;
height: 1.5rem;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
display: flex;
-webkit-align-self: flex-end;
-ms-flex-item-align: end;
align-self: flex-end;
margin: 0 1.125rem;
}
@media (max-width:47.9375rem) {
.c1 {
display: block;
}
}
@media (max-width:47.9375rem) {
.c3 {
display: none;
}
}
<div
className="c0"
>
<div
className="c1"
>
<label
className="c2"
>
</label>
<div
className="c3"
/>
<label
className="c2"
>
Hardware Virtual Machine
</label>
<div
className="c3"
/>
<div
className="c1"
>
<span
className="c4"
color="green"
height="0.6875rem"
width="0.6875rem"
/>
Running
</div>
</div>
<div
className="c5"
>
<div
className="c1"
>
<div
className="c1"
>
<label
className="c6 c2"
>
Created:
</label>
<label
className="c2"
>
about 2 months
ago
</label>
</div>
<div
className="c3"
/>
<div
className="c1"
>
<label
className="c6 c2"
>
Updated:
</label>
<label
className="c2"
>
1 day
ago
</label>
</div>
</div>
</div>
</div>,
]
`;

View File

@ -54,21 +54,24 @@ it('renders <Meta /> without throwing', () => {
const tree = renderer
.create(
<Store>
<Meta {...{
"id": "2252839a-e698-ceec-afac-9549ad0c6624",
"state": "RUNNING",
"name": "2252839a",
"created": "2017-10-13T11:36:04.463Z",
"updated": "2017-11-27T13:46:28.000Z",
"primary_ip": "72.2.119.146",
"ips": {},
"docker": null,
"dns_names": {},
"compute_node": "70bb1cee-dba3-11e3-a799-002590e4f2b0",
"image": {},
"package": {},
"__typename": "Machine"
}} onAction={() => console.log('sup??')}/>
<Meta
{...{
id: '2252839a-e698-ceec-afac-9549ad0c6624',
state: 'RUNNING',
name: '2252839a',
created: '2017-10-13T11:36:04.463Z',
updated: '2017-11-27T13:46:28.000Z',
primary_ip: '72.2.119.146',
ips: {},
docker: null,
dns_names: {},
compute_node: '70bb1cee-dba3-11e3-a799-002590e4f2b0',
image: {},
package: {},
__typename: 'Machine'
}}
onAction={() => console.log('sup??')}
/>
</Store>
)
.toJSON();
@ -80,7 +83,7 @@ it('renders <CopyToClipboardTooltip /> without throwing', () => {
const tree = renderer
.create(
<Store>
<CopyToClipboardTooltiptip>{"test"}</CopyToClipboardTooltiptip>
<CopyToClipboardTooltip>{'test'}</CopyToClipboardTooltip>
</Store>
)
.toJSON();

View File

@ -0,0 +1,137 @@
import 'jest-styled-components';
import React from 'react';
import renderer from 'react-test-renderer';
import ReduxForm from 'declarative-redux-form';
import Store from '@mocks/store';
import Theme from '@mocks/theme';
import ListForm, { MenuForm, Actions, Item } from '../list';
it('renders <MenuForm /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-form">{MenuForm}</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <MenuForm searchable /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-form" searchable>
{MenuForm}
</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions submitting />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions submitting allowedActions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions submitting allowedActions={{ stop: true }} />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions allowedActions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions allowedActions={{ stop: true }} />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Actions allowedActions /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<Actions allowedActions={{ stop: true }} />
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <ListForm /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-form">{ListForm}</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});
it('renders <Item /> without throwing', () => {
expect(
renderer
.create(
<Store>
<Theme>
<ReduxForm form="instance-list-item-form">{Item}</ReduxForm>
</Theme>
</Store>
)
.toJSON()
).toMatchSnapshot();
});

View File

@ -1,6 +1,6 @@
import React, { Component } from 'react';
import { Row, Col } from 'react-styled-flexboxgrid';
import styled, { withTheme} from 'styled-components';
import styled, { withTheme } from 'styled-components';
import { Margin, Padding } from 'styled-components-spacing';
import remcalc from 'remcalc';
import is from 'styled-is';
@ -147,7 +147,14 @@ export const CopiableField = ({ label, text, ...rest }) => (
</Row>
);
export const Meta = ({ created, updated, state, brand, image, ...instance }) => [
export const Meta = ({
created,
updated,
state,
brand,
image,
...instance
}) => [
<Row middle="xs">
<Col xs={12}>
<H2>{instance.package.name}</H2>
@ -155,9 +162,7 @@ export const Meta = ({ created, updated, state, brand, image, ...instance }) =>
</Row>,
<Margin top={2} bottom={3}>
<Flex>
<Label>
{image ? titleCase(image.name) : 'Custom Image'}
</Label>
<Label>{image ? titleCase(image.name) : 'Custom Image'}</Label>
<HorizontalDivider />
<Label>
{brand === 'LX'
@ -193,99 +198,99 @@ export const Meta = ({ created, updated, state, brand, image, ...instance }) =>
</Margin>
];
export default withTheme(({
instance,
starting,
stopping,
rebooting,
deleteing,
onAction,
theme
}) => (
<Row>
<Col xs={12} sm={12} md={9}>
<Card>
<CardOutlet big>
<Meta {...instance} />
<Flex>
export default withTheme(
({ instance, starting, stopping, rebooting, deleteing, onAction, theme }) => (
<Row>
<Col xs={12} sm={12} md={9}>
<Card>
<CardOutlet big>
<Meta {...instance} />
<Flex>
<Button
secondary
bold
icon
loading={starting}
disabled={instance.state === 'RUNNING'}
onClick={() => onAction('start')}
>
<Padding right={3} style={{ height: 18 }}>
<StartIcon disabled={instance.state === 'RUNNING'} />
</Padding>
<span>Start</span>
</Button>
<Button
secondary
bold
icon
loading={stopping}
disabled={instance.state === 'STOPPED'}
onClick={() => onAction('stop')}
>
<Padding right={3} style={{ height: 18 }}>
<StopIcon disabled={instance.state === 'STOPPED'} />
</Padding>
<span>Stop</span>
</Button>
<Button
secondary
bold
icon
loading={rebooting}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('reboot')}
>
<Padding right={3} style={{ height: 18 }}>
<ResetIcon disabled={instance.state === 'PROVISIONING'} />
</Padding>
<span>Restart</span>
</Button>
<Flex>
<Button
secondary
bold
icon
loading={starting}
disabled={instance.state === 'RUNNING'}
onClick={() => onAction('start')}
>
<Padding right={3} style={{ height: 18 }}>
<StartIcon disabled={instance.state === 'RUNNING'} />
</Padding>
<span>Start</span>
</Button>
<Button
secondary
bold
icon
loading={stopping}
disabled={instance.state === 'STOPPED'}
onClick={() => onAction('stop')}
>
<Padding right={3} style={{ height: 18 }}>
<StopIcon disabled={instance.state === 'STOPPED'} />
</Padding>
<span>Stop</span>
</Button>
<Button
secondary
bold
icon
loading={rebooting}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('reboot')}
>
<Padding right={3} style={{ height: 18 }}>
<ResetIcon disabled={instance.state === 'PROVISIONING'} />
</Padding>
<span>Restart</span>
</Button>
</Flex>
<FlexEnd>
<Button
error
bold
icon
loading={deleteing}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('delete')}
>
<Padding right={3} style={{ height: 18 }}>
<DeleteIcon
fill={theme.red}
disabled={instance.state === 'PROVISIONING'}
/>
</Padding>
<span>Delete</span>
</Button>
</FlexEnd>
</Flex>
<FlexEnd>
<Button
error
bold
icon
loading={deleteing}
disabled={instance.state === 'PROVISIONING'}
onClick={() => onAction('delete')}
>
<Padding right={3} style={{ height: 18 }}>
<DeleteIcon fill={theme.red} disabled={instance.state === 'PROVISIONING'} />
</Padding>
<span>Delete</span>
</Button>
</FlexEnd>
</Flex>
<Margin bottom={5} top={4}>
<Divider height={remcalc(1)} />
</Margin>
<CopiableField text={instance.id.split('-')[0]} label="Short ID" />
<CopiableField text={instance.id} label="ID" />
<CopiableField text={instance.compute_node} label="CN UUID" />
{instance.image && (
<CopiableField text={instance.image.id} label="Image UUID" />
)}
<CopiableField text={`$ ssh root@${instance.primary_ip}`} label="Login" />
{instance.ips.map((ip, i) => (
<Margin bottom={5} top={4}>
<Divider height={remcalc(1)} />
</Margin>
<CopiableField text={instance.id.split('-')[0]} label="Short ID" />
<CopiableField text={instance.id} label="ID" />
<CopiableField text={instance.compute_node} label="CN UUID" />
{instance.image && (
<CopiableField text={instance.image.id} label="Image UUID" />
)}
<CopiableField
key={i}
noMargin={i === instance.ips.length - 1}
text={ip}
label={`IP address ${i + 1}`}
text={`$ ssh root@${instance.primary_ip}`}
label="Login"
/>
))}
</CardOutlet>
</Card>
</Col>
</Row>
));
{instance.ips.map((ip, i) => (
<CopiableField
key={i}
noMargin={i === instance.ips.length - 1}
text={ip}
label={`IP address ${i + 1}`}
/>
))}
</CardOutlet>
</Card>
</Col>
</Row>
)
);

View File

@ -1,88 +0,0 @@
import React from 'react';
import titleCase from 'title-case';
import {
Card,
CardMeta,
CardAction,
CardTitle,
CardLabel,
CardView,
CardOptions,
Checkbox,
FormGroup,
QueryBreakpoints,
StatusLoader,
PopoverContainer,
PopoverTarget,
PopoverItem,
PopoverDivider,
Popover
} from 'joyent-ui-toolkit';
const { SmallOnly, Small } = QueryBreakpoints;
const stateColor = {
PROVISIONING: 'primary',
RUNNING: 'green',
STOPPING: 'grey',
STOPPED: 'grey',
DELETED: 'secondaryActive',
FAILED: 'red'
};
// eslint-disable-next-line camelcase
export default ({ name, state, primary_ip, loading, last, first }) => (
<Card collapsed flat={!last} topMargin={first} bottomless={!last} gapless>
<CardView>
<CardMeta>
<CardAction>
<FormGroup name={name} reduxForm>
<Checkbox />
</FormGroup>
</CardAction>
<CardTitle to={`/instances/${name}`}>{name}</CardTitle>
{loading && (
<CardLabel>
<StatusLoader small />
</CardLabel>
)}
{!loading && (
<Small>
<CardLabel>{primary_ip}</CardLabel>
</Small>
)}
{!loading && (
<Small>
<CardLabel
color={stateColor[state]}
title={`The instance is ${state}`}
>
{titleCase(state)}
</CardLabel>
</Small>
)}
{!loading && (
<SmallOnly>
<CardLabel
color={stateColor[state]}
title={`The instance is ${state}`}
/>
</SmallOnly>
)}
</CardMeta>
<PopoverContainer clickable>
<PopoverTarget>
<CardOptions />
</PopoverTarget>
<Popover placement="right-start">
<PopoverItem>Scale</PopoverItem>
<PopoverItem>Restart</PopoverItem>
<PopoverItem>Stop</PopoverItem>
<PopoverDivider />
<PopoverItem>Delete</PopoverItem>
</Popover>
</PopoverContainer>
</CardView>
</Card>
);

View File

@ -1,37 +1,42 @@
import React from 'react';
import { Row, Col } from 'react-styled-flexboxgrid';
import forceArray from 'force-array';
import find from 'lodash.find';
import distanceInWordsToNow from 'date-fns/distance_in_words_to_now';
import Value from 'react-redux-values';
import remcalc from 'remcalc';
import titleCase from 'title-case';
import {
Row,
Col,
Anchor,
FormGroup,
Input,
FormLabel,
ViewContainer,
StatusLoader,
Select,
Message,
MessageTitle,
MessageDescription,
Checkbox,
Button,
QueryBreakpoints,
Table,
TableThead,
TableTr,
TableTh,
TableTbody,
TableTd,
Checkbox,
P,
DotIcon,
ActionsIcon,
TableTbody,
Footer,
StatusLoader,
Message,
MessageTitle,
MessageDescription,
Popover,
PopoverContainer,
PopoverTarget,
Popover,
PopoverItem,
PopoverDivider,
Anchor
QueryBreakpoints,
DotIcon,
StartIcon,
StopIcon,
ResetIcon,
DeleteIcon,
ArrowIcon,
ActionsIcon
} from 'joyent-ui-toolkit';
const { SmallOnly, Medium } = QueryBreakpoints;
@ -45,293 +50,374 @@ const stateColor = {
FAILED: 'red'
};
const Item = ({
id,
export const MenuForm = ({ handleSubmit, searchable }) => (
<form onSubmit={handleSubmit}>
<Row>
<Col xs={7} sm={5}>
<FormGroup name="filter" fluid reduxForm>
<FormLabel>Filter instances</FormLabel>
<Input
placeholder="Search for name, state, tags, etc..."
disabled={!searchable}
fluid
/>
</FormGroup>
</Col>
<Col xs={5} sm={7}>
<FormGroup right>
<FormLabel>&#8291;</FormLabel>
<Button type="submit" small icon fluid>
Create Instance
</Button>
</FormGroup>
</Col>
</Row>
</form>
);
export const Actions = ({
submitting = false,
allowedActions = {},
onStart = () => null,
onStop = () => null,
onReboot = () => null,
onDelete = () => null
}) => (
<Footer fixed bottom>
<Row between="xs" middle="xs">
<Col xs={7}>
<Value name="instance-list-starting">
{({ value: starting }) => [
<SmallOnly key="small-only">
<Button
type="button"
onClick={onStart}
disabled={submitting || !allowedActions.start}
loading={submitting && starting}
secondary
small
icon
rect
>
<StartIcon disabled={submitting || !allowedActions.start} />
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onStart}
disabled={submitting || !allowedActions.start}
loading={submitting && starting}
secondary
icon
rect
>
<StartIcon disabled={submitting || !allowedActions.start} />
<span>Start</span>
</Button>
</Medium>
]}
</Value>
<Value name="instance-list-stoping">
{({ value: stoping }) => [
<SmallOnly key="small-only">
<Button
type="button"
onClick={onStop}
disabled={submitting || !allowedActions.stop}
loading={submitting && stoping}
secondary
small
icon
rect
>
<StopIcon disabled={submitting || !allowedActions.stop} />
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onStop}
disabled={submitting || !allowedActions.stop}
loading={submitting && stoping}
secondary
icon
rect
>
<StopIcon disabled={submitting || !allowedActions.stop} />
<span>Stop</span>
</Button>
</Medium>
]}
</Value>
<Value name="instance-list-restarting">
{({ value: restarting }) => [
<SmallOnly key="small-only">
<Button
type="button"
onClick={onReboot}
disabled={submitting || !allowedActions.reboot}
loading={submitting && restarting}
secondary
small
icon
rect
>
<ResetIcon disabled={submitting || !allowedActions.reboot} />
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onReboot}
disabled={submitting || !allowedActions.reboot}
loading={submitting && restarting}
secondary
icon
rect
>
<ResetIcon disabled={submitting || !allowedActions.reboot} />
<span>Reboot</span>
</Button>
</Medium>
]}
</Value>
</Col>
<Col xs={5}>
<Value name="instance-list-deleting">
{({ value: deleting }) => [
<SmallOnly key="small-only">
<Button
type="button"
onClick={onDelete}
disabled={submitting}
loading={submitting && deleting}
secondary
right
small
icon
rect
>
<DeleteIcon disabled={submitting} />
</Button>
</SmallOnly>,
<Medium key="medium">
<Button
type="button"
onClick={onDelete}
disabled={submitting}
loading={submitting && deleting}
secondary
right
icon
rect
>
<DeleteIcon disabled={submitting} />
<span>Delete</span>
</Button>
</Medium>
]}
</Value>
</Col>
</Row>
</Footer>
);
export const Item = ({
id = '',
name,
state,
allowedActions,
onStop,
created,
allowedActions = {},
submitting,
onStart,
onStop,
onReboot,
onResize,
onEnableFw,
onDisableFw,
onCreateSnap,
onStartSnap
onDelete
}) => (
<TableTr>
<TableTd paddingRight="0" paddingLeft="18px" left middle>
<FormGroup name={name} reduxForm>
<TableTd padding="0" paddingLeft={remcalc(12)} middle left>
<FormGroup name={id} paddingTop={remcalc(4)} reduxForm>
<Checkbox />
</FormGroup>
</TableTd>
<TableTd>
<code>{id.substring(0, 7)}</code>
</TableTd>
<TableTd>
<TableTd middle left>
<Anchor to={`/instances/${name}`}>{name}</Anchor>
</TableTd>
<TableTd middle>
<DotIcon color={stateColor[state]} /> {titleCase(state)}
<TableTd middle left>
<Value name={`${id}-mutating`}>
{({ value: mutating }) =>
mutating ? (
<StatusLoader small />
) : (
<span>
<DotIcon
width={remcalc(11)}
height={remcalc(11)}
borderRadius={remcalc(11)}
color={stateColor[state]}
/>{' '}
{titleCase(state)}
</span>
)
}
</Value>
</TableTd>
<TableTd border="left" middle center actionable>
<PopoverContainer clickable>
<PopoverTarget>
<TableTd xs="0" sm="140" middle left>
{distanceInWordsToNow(created)}
</TableTd>
<TableTd xs="0" sm="95" middle left>
<code>{id.substring(0, 7)}</code>
</TableTd>
<PopoverContainer clickable>
<TableTd padding="0">
<PopoverTarget box>
<ActionsIcon />
</PopoverTarget>
<Popover placement="right-start">
{!allowedActions.stop ? null : (
<PopoverItem onClick={onStop}>Stop</PopoverItem>
)}
{!allowedActions.start ? null : (
<PopoverItem onClick={onStart}>Start</PopoverItem>
)}
{!allowedActions.reboot ? null : (
<PopoverItem onClick={onReboot}>Reboot</PopoverItem>
)}
{!allowedActions.enableFw ? null : (
<PopoverItem onClick={onEnableFw}>Enable Firewall</PopoverItem>
)}
{!allowedActions.disableFw ? null : (
<PopoverItem onClick={onDisableFw}>Disable Firewall</PopoverItem>
)}
{!allowedActions.disableFw ? null : (
<PopoverItem onClick={onDisableFw}>Disable Firewall</PopoverItem>
)}
<Popover placement="right">
<PopoverItem
disabled={!allowedActions.start}
onClick={() => onStart({ id })}
>
Start
</PopoverItem>
<PopoverItem
disabled={!allowedActions.stop}
onClick={() => onStop({ id })}
>
Stop
</PopoverItem>
<PopoverItem onClick={() => onReboot({ id })}>Reboot</PopoverItem>
<PopoverDivider />
{!allowedActions.resize ? null : (
<PopoverItem onClick={onResize}>Resize</PopoverItem>
)}
{!allowedActions.createSnap ? null : (
<PopoverItem onClick={onCreateSnap}>Create Snapshot</PopoverItem>
)}
{!allowedActions.startSnap ? null : (
<PopoverItem onClick={onStartSnap}>Start from Snapshot</PopoverItem>
)}
<PopoverItem>Delete</PopoverItem>
<PopoverItem onClick={() => onDelete({ id })}>Delete</PopoverItem>
</Popover>
</PopoverContainer>
</TableTd>
</TableTd>
</PopoverContainer>
</TableTr>
);
export default ({
instances = [],
selected = [],
loading,
error,
handleChange = () => null,
onAction = () => null,
handleSubmit,
items = [],
allowedActions = {},
sortBy = 'name',
sortOrder = 'desc',
error = null,
submitting = false,
pristine = true,
...rest
}) => {
const allowedActions = {
stop: selected.some(({ state }) => state === 'RUNNING'),
start: selected.some(({ state }) => state !== 'RUNNING'),
reboot: true,
resize:
selected.length === 1 && selected.every(({ brand }) => brand === 'KVM'),
// eslint-disable-next-line camelcase
enableFw: selected.some(({ firewall_enabled }) => !firewall_enabled),
// eslint-disable-next-line camelcase
disableFw: selected.some(({ firewall_enabled }) => firewall_enabled),
createSnap: selected.length === 1,
startSnap:
selected.length === 1 &&
selected.every(({ snapshots = [] }) => snapshots.length)
};
const handleActions = ev => {
ev.stopPropagation();
ev.preventDefault();
onAction({
name: ev.target.value,
items: selected
});
};
const items = forceArray(instances).map(instance => {
// eslint-disable-next-line camelcase
const { id, state, firewall_enabled, snapshots, brand } = instance;
const isSelected = Boolean(find(selected, ['id', id]));
const isSubmitting = isSelected && submitting;
const allowedActions = {
stop: state === 'RUNNING',
start: state !== 'RUNNING',
reboot: true,
resize: brand === 'KVM',
// eslint-disable-next-line camelcase
enableFw: !firewall_enabled,
// eslint-disable-next-line camelcase
disableFw: firewall_enabled,
createSnap: true,
startSnap: Boolean(snapshots.length)
};
return {
...instance,
isSubmitting,
isSelected,
allowedActions,
onStop: () => onAction({ name: 'stop', items: [instance] }),
onStart: () => onAction({ name: 'start', items: [instance] }),
onReboot: () => onAction({ name: 'reboot', items: [instance] }),
onResize: () => onAction({ name: 'resize', items: [instance] }),
onEnableFw: () => onAction({ name: 'enableFw', items: [instance] }),
onDisableFw: () => onAction({ name: 'disableFw', items: [instance] }),
onCreateSnap: () => onAction({ name: 'createSnap', items: [instance] }),
onStartSnap: () => onAction({ name: 'startSnap', items: [instance] })
};
});
const _loading =
!items.length && loading ? (
<ViewContainer center>
<StatusLoader />
</ViewContainer>
) : null;
const _error = error &&
!submitting && (
actionable = false,
allSelected = false,
toggleSelectAll = () => null,
onStart = () => null,
onStop = () => null,
onReboot = () => null,
onDelete = () => null,
onSortBy = () => null
}) => (
<form>
{error ? (
<Message error>
<MessageTitle>Ooops!</MessageTitle>
<MessageDescription>{error}</MessageDescription>
</Message>
);
const _table = !items.length ? null : (
) : null}
<Table>
<TableThead>
<TableTr>
<TableTh xs="32" padding="0" paddingLeft={remcalc(12)} middle left>
<FormGroup paddingTop={remcalc(4)}>
<Checkbox
checked={allSelected}
disabled={submitting}
onChange={toggleSelectAll}
/>
</FormGroup>
</TableTh>
<TableTh onClick={() => onSortBy('name')} left middle actionable>
<span>Name </span>
{sortBy !== 'name' ? null : (
<ArrowIcon
marginLeft={remcalc(9)}
marginBottom={remcalc(2)}
direction={sortOrder === 'asc' ? 'down' : 'up'}
/>
)}
</TableTh>
<TableTh
xs="140"
onClick={() => onSortBy('state')}
left
middle
actionable
>
<span>Status </span>
{sortBy !== 'state' ? null : (
<ArrowIcon
marginLeft={remcalc(9)}
marginBottom={remcalc(2)}
direction={sortOrder === 'asc' ? 'down' : 'up'}
/>
)}
</TableTh>
<TableTh
xs="0"
sm="140"
onClick={() => onSortBy('created')}
left
middle
actionable
>
<span>Created </span>
{sortBy !== 'created' ? null : (
<ArrowIcon
marginLeft={remcalc(9)}
marginBottom={remcalc(2)}
direction={sortOrder === 'asc' ? 'down' : 'up'}
/>
)}
</TableTh>
<TableTh
xs="0"
sm="95"
onClick={() => onSortBy('id')}
left
middle
actionable
>
<span>Short ID </span>
{sortBy !== 'id' ? null : (
<ArrowIcon
marginLeft={remcalc(9)}
marginBottom={remcalc(2)}
direction={sortOrder === 'asc' ? 'down' : 'up'}
/>
)}
</TableTh>
<TableTh xs="38" padding="0" />
<TableTh xs="80" left bottom>
<P>Id</P>
</TableTh>
<TableTh left bottom>
<P>Name</P>
</TableTh>
<TableTh xs="105" left bottom>
<P>Status</P>
</TableTh>
<TableTh xs="48" />
</TableTr>
</TableThead>
<TableTbody>
{items.map(instance => <Item key={instance.id} {...instance} />)}
{items.map(({ id, ...rest }) => (
<Item
key={id}
id={id}
{...rest}
submitting={submitting}
onStart={onStart}
onStop={onStop}
onReboot={onReboot}
onDelete={onDelete}
/>
))}
</TableTbody>
</Table>
);
return (
<form>
<Row between="xs">
<Col xs={8} sm={8} lg={8}>
<Row>
<Col xs={7} sm={7} md={6} lg={6}>
<FormGroup name="filter" fluid reduxForm>
<FormLabel>Filter instances</FormLabel>
<Input
placeholder="Search for name, state, tags, etc..."
disabled={pristine && !items.length}
fluid
/>
</FormGroup>
</Col>
<Col xs={5} sm={3} lg={4}>
<FormGroup name="sort" fluidreduxForm>
<FormLabel>Sort</FormLabel>
<Select disabled={!items.length} fluid>
<option value="name">Name</option>
<option value="state">State</option>
<option value="primary_ip">IP</option>
<option value="image.name">Image</option>
<option value="firewall_enabled">Firewall</option>
<option value="created">Created</option>
<option value="updated">Updated</option>
<option value="brand">Brand</option>
<option value="memory">Memory</option>
<option value="disk">Disk</option>
<option value="package.name">Package</option>
</Select>
</FormGroup>
</Col>
</Row>
</Col>
<Col xs={4} sm={4} lg={4}>
<Row end="xs">
<Col xs={6} sm={4} md={3} lg={3}>
<FormGroup fluid>
<FormLabel>&#8291;</FormLabel>
<Select
value="actions"
disabled={!items.length || !selected.length}
onChange={handleActions}
fluid
>
<option value="actions" selected disabled>
&#8801;
</option>
<option value="stop" disabled={!allowedActions.stop}>
Stop
</option>
<option value="start" disabled={!allowedActions.start}>
Start
</option>
<option value="reboot" disabled={!allowedActions.reboot}>
Reboot
</option>
<option value="resize" disabled={!allowedActions.resize}>
Resize
</option>
<option value="enableFw" disabled={!allowedActions.enableFw}>
Enable Firewall
</option>
<option
value="disableFw"
disabled={!allowedActions.disableFw}
>
Disable Firewall
</option>
<option
value="createSnap"
disabled={!allowedActions.createSnap}
>
Create Snapshot
</option>
<option
value="startSnap"
disabled={!allowedActions.startSnap}
>
Start from Snapshot
</option>
</Select>
</FormGroup>
</Col>
<Col xs={6} sm={6} md={5} lg={4}>
<FormGroup fluid>
<FormLabel>&#8291;</FormLabel>
<Button
type="button"
small
icon
fluid
onClick={() => onAction({ name: 'create' })}
>
<SmallOnly>+</SmallOnly>
<Medium>Create</Medium>
</Button>
</FormGroup>
</Col>
</Row>
</Col>
</Row>
{_error}
{_loading}
{_table}
</form>
);
};
{actionable ? (
<Actions
allowedActions={allowedActions}
submitting={submitting}
onStart={onStart}
onStop={onStop}
onReboot={onReboot}
onDelete={onDelete}
/>
) : null}
</form>
);

View File

@ -1,8 +1,7 @@
import React from 'react';
import ReactJson from 'react-json-view';
export default ({ instance, packages, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<ReactJson src={{ instance, packages }} />
<pre>{JSON.stringify({ instance, packages }, null, 2)}</pre>;
</form>
);

View File

@ -10,39 +10,48 @@ import {
UserIcon,
HeaderNav,
HeaderAnchor,
HeaderItem
HeaderItem,
QueryBreakpoints
} from 'joyent-ui-toolkit';
const Logo = styled(TritonIcon)`
padding-top: ${remcalc(11)};
`;
const { Medium } = QueryBreakpoints;
export default () => (
<Header fluid>
<HeaderBrand beta>
<HeaderAnchor to="/">
<Logo beta alt="Triton" light />
<Logo beta light alt="Triton" />
</HeaderAnchor>
</HeaderBrand>
<HeaderNav>
<Medium>
<HeaderNav>
<li>
<HeaderAnchor to="/">Compute</HeaderAnchor>
</li>
</HeaderNav>
</Medium>
<Medium>
<HeaderItem>
<HeaderAnchor to="/">Compute</HeaderAnchor>
<HeaderAnchor href="https://my.joyent.com">
Return to existing portal
</HeaderAnchor>
</HeaderItem>
</HeaderNav>
<HeaderItem>
<HeaderAnchor href="https://my.joyent.com">
Return to existing portal
</HeaderAnchor>
</HeaderItem>
</Medium>
<HeaderItem>
<HeaderAnchor>
<DataCenterIcon light />eu-east-1
</HeaderAnchor>
</HeaderItem>
<HeaderItem>
<HeaderAnchor>
<UserIcon light />Nicola
</HeaderAnchor>
</HeaderItem>
<Medium>
<HeaderItem>
<HeaderAnchor>
<UserIcon light />Nicola
</HeaderAnchor>
</HeaderItem>
</Medium>
</Header>
);

View File

@ -80,6 +80,8 @@ exports[`renders <Metadata /> without throwing 1`] = `
.c0 {
margin-right: auto;
margin-left: auto;
box-sizing: border-box;
width: 100%;
max-width: 62.5rem;
padding-bottom: 1.125rem;
display: -webkit-box;

View File

@ -1,6 +1,5 @@
import React from 'react';
import renderer from 'react-test-renderer';
import { reduxForm } from 'redux-form';
import Store from '@mocks/store';
import 'jest-styled-components';

View File

@ -1,5 +1,4 @@
import React from 'react';
import ReactJson from 'react-json-view';
import PropTypes from 'prop-types';
import { compose, graphql } from 'react-apollo';
import find from 'lodash.find';
@ -20,7 +19,8 @@ const DNS = ({ instance, loading, error }) => {
const { name, dns_names } = instance || {};
const _title = <Title>DNS</Title>;
const _loading = loading && !name && !dns_names && <StatusLoader />;
const _summary = !_loading && instance && <ReactJson src={dns_names} />;
const _summary = !_loading &&
instance && <pre>{JSON.stringify(dns_names, null, 2)}</pre>;
const _error = error &&
!_loading &&

View File

@ -32,7 +32,7 @@ const Home = ({
starting,
stopping,
rebooting,
deleteing,
deleteing
}) => {
const { name } = instance || {};
@ -118,7 +118,7 @@ export default compose(
};
},
(disptach, ownProps) => ({
handleAction: async (action) => {
handleAction: async action => {
const { instance } = ownProps;
const { id } = instance;
@ -126,17 +126,21 @@ export default compose(
const name = `${id}-home-${gerund}`;
// sets loading to true
disptach(set({
name,
value: true
}));
disptach(
set({
name,
value: true
})
);
// calls mutation and waits while loading is still true
const [err] = await intercept(ownProps[action]({
variables: { id }
}));
const [err] = await intercept(
ownProps[action]({
variables: { id }
})
);
if (!err && (action === 'delete')) {
if (!err && action === 'delete') {
const { history } = ownProps;
return history.push(`/instances/`);
}
@ -148,10 +152,12 @@ export default compose(
});
// if error, sets error value
const mutationError = err && set({
name: `${id}-home-mutation-error`,
value: parseError(err)
});
const mutationError =
err &&
set({
name: `${id}-home-mutation-error`,
value: parseError(err)
});
return disptach([mutationError, setLoadingFalse].filter(Boolean));
}

View File

@ -1,26 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
import { compose, graphql } from 'react-apollo';
import { connect } from 'react-redux';
import { stopSubmit, startSubmit, reset, change } from 'redux-form';
import { set } from 'react-redux-values';
import ReduxForm from 'declarative-redux-form';
import forceArray from 'force-array';
import get from 'lodash.get';
import sortBy from 'lodash.sortby';
import intercept from 'apr-intercept';
import find from 'lodash.find';
import {
reduxForm,
SubmissionError,
stopSubmit,
startSubmit,
change
} from 'redux-form';
import sort from 'lodash.sortby';
import remcalc from 'remcalc';
import {
ViewContainer,
Title,
Message,
MessageDescription,
MessageTitle
MessageTitle,
StatusLoader,
Divider
} from 'joyent-ui-toolkit';
import ListInstances from '@graphql/list-instances.gql';
@ -32,27 +29,37 @@ import EnableInstanceFw from '@graphql/enable-instance-fw.gql';
import DisableInstanceFw from '@graphql/disable-instance-fw.gql';
import CreateSnapshot from '@graphql/create-snapshot.gql';
import StartSnapshot from '@graphql/start-from-snapshot.gql';
import Index from '@state/gen-index';
import { List as InstanceList } from '@components/instances';
import GenIndex from '@state/gen-index';
import {
default as InstanceList,
MenuForm as InstanceListMenuForm
} from '@components/instances/list';
const InstanceListForm = reduxForm({
form: `instance-list`,
initialValues: {
sort: 'name'
}
})(InstanceList);
const TABLE_FORM_NAME = 'instance-list-table';
const MENU_FORM_NAME = 'instance-list-menu';
const List = ({
selected = [],
instances = [],
selected = [],
allowedActions,
sortBy = 'name',
sortOrder = 'desc',
loading = false,
error,
handleAction
error = null,
handleAction,
toggleSelectAll,
handleSortBy
}) => {
const _title = <Title>Instances</Title>;
const _instances = forceArray(instances);
const _loading = !instances.length && loading;
const _loading =
loading && !_instances.length
? [
<Divider key="divider" height={remcalc(30)} transparent />,
<StatusLoader key="spinner" />
]
: null;
const _error =
error && !_instances.length && !_loading ? (
@ -64,30 +71,47 @@ const List = ({
</Message>
) : null;
const _table = !loading ? (
<ReduxForm
form={TABLE_FORM_NAME}
items={_instances}
actionable={selected.length}
allowedActions={allowedActions}
allSelected={instances.length && selected.length === instances.length}
sortBy={sortBy}
sortOrder={sortOrder}
toggleSelectAll={toggleSelectAll}
onSortBy={handleSortBy}
onStart={({ id } = {}) =>
handleAction({ name: 'start', selected: id ? [{ id }] : selected })
}
onStop={({ id } = {}) =>
handleAction({ name: 'stop', selected: id ? [{ id }] : selected })
}
onReboot={({ id } = {}) =>
handleAction({ name: 'restart', selected: id ? [{ id }] : selected })
}
onDelete={({ id } = {}) =>
handleAction({ name: 'delete', selected: id ? [{ id }] : selected })
}
>
{InstanceList}
</ReduxForm>
) : null;
return (
<ViewContainer main>
{_title}
{!_loading && _error}
<InstanceListForm
instances={_instances}
loading={_loading}
onAction={handleAction}
selected={selected}
/>
<Divider height={remcalc(30)} transparent />
<ReduxForm form={MENU_FORM_NAME} searchable={!_loading}>
{InstanceListMenuForm}
</ReduxForm>
{_error}
{_loading}
{_table}
</ViewContainer>
);
};
List.propTypes = {
loading: PropTypes.bool,
instances: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
name: PropTypes.string
})
)
};
export default compose(
graphql(StopInstance, { name: 'stop' }),
graphql(StartInstance, { name: 'start' }),
@ -101,112 +125,194 @@ export default compose(
options: () => ({
pollInterval: 1000
}),
props: ({ ownProps, data: { machines, loading, error } }) => {
const _instances = forceArray(machines);
props: ({ data: { machines, loading, error, refetch } }) => {
const instances = forceArray(machines).map(({ state, ...machine }) => ({
...machine,
state,
allowedActions: {
start: state !== 'RUNNING',
stop: state === 'RUNNING'
}
}));
return {
instances: _instances,
instances,
loading,
error,
index: GenIndex(_instances)
index: Index(instances),
refetch
};
}
}),
connect(
(state, ownProps) => {
const { index, instances = [], ...rest } = ownProps;
({ form, values }, { index, instances = [] }) => {
// get search value
const filter = get(form, `${MENU_FORM_NAME}.values.filter`, false);
// check checked items ids
const checked = get(form, `${TABLE_FORM_NAME}.values`, {});
// get sort values
const sortBy = get(values, 'instance-list-sort-by', 'name');
const sortOrder = get(values, 'instance-list-sort-order', 'asc');
const form = get(state, 'form.instance-list.values', {});
const filter = get(form, 'filter');
const sort = get(form, 'sort');
const values = filter
// if user is searching something, get items that match that query
const filtered = filter
? index.search(filter).map(({ ref }) => find(instances, ['id', ref]))
: instances;
const selected = Object.keys(form)
.filter(key => Boolean(form[key]))
.map(name => find(values, ['name', name]))
.filter(Boolean)
.map(({ id }) => find(instances, ['id', id]))
// from filtered instances, sort asc
const ascSorted = sort(filtered, [sortBy]);
// if "select-all" is checked, all the instances are selected
// otherwise, map through the checked ids and get the instance value
const selected = Object.keys(checked)
.filter(id => Boolean(checked[id]))
.map(id => find(ascSorted, ['id', id]))
.filter(Boolean);
const allowedActions = {
start: selected.some(({ state }) => state !== 'RUNNING'),
stop: selected.some(({ state }) => state === 'RUNNING')
};
return {
...rest,
instances: sortBy(values, value => get(value, sort)),
selected
// is sortOrder !== asc, reverse order
instances: sortOrder === 'asc' ? ascSorted : ascSorted.reverse(),
allowedActions,
selected,
index,
sortOrder,
sortBy
};
},
(
dispatch,
{ stop, start, reboot, enableFw, disableFw, history, location }
) => ({
handleAction: ({ name, items = [] }) => {
const form = 'instance-list';
const types = {
stop: () =>
Promise.resolve(dispatch(startSubmit(form))).then(() =>
Promise.all(items.map(({ id }) => stop({ variables: { id } })))
),
start: () =>
Promise.resolve(dispatch(startSubmit(form))).then(() =>
Promise.all(items.map(({ id }) => start({ variables: { id } })))
),
reboot: () =>
Promise.resolve(dispatch(startSubmit(form))).then(() =>
Promise.all(items.map(({ id }) => reboot({ variables: { id } })))
),
resize: () =>
Promise.resolve(
history.push(`/instances/~resize/${items.shift().name}`)
),
enableFw: () =>
Promise.resolve(dispatch(startSubmit(form))).then(() =>
Promise.all(
items.map(({ id }) => enableFw({ variables: { id } }))
)
),
disableFw: () =>
Promise.resolve(dispatch(startSubmit(form))).then(() =>
Promise.all(
items.map(({ id }) => disableFw({ variables: { id } }))
)
),
createSnap: () =>
Promise.resolve(
history.push(`/instances/~create-snapshot/${items.shift().name}`)
),
startSnap: () =>
Promise.resolve(
history.push(`/instances/${items.shift().name}/snapshots`)
),
create: () =>
Promise.resolve(history.push(`/instances/~create-instance`))
};
const handleError = error => {
throw new SubmissionError({
_error: error.graphQLErrors.map(({ message }) => message).join('\n')
});
};
const handleSuccess = error =>
dispatch(
items
.map(({ name: field }) => change(form, field, false))
.concat([stopSubmit(form)])
(dispatch, { refetch, ...ownProps }) => ({
handleSortBy: ({ sortBy: currentSortBy, sortOrder }) => newSortBy => {
// sort prop is the same, toggle
if (currentSortBy === newSortBy) {
return dispatch(
set({
name: `instance-list-sort-order`,
value: sortOrder === 'desc' ? 'asc' : 'desc'
})
);
}
const fn = types[name];
dispatch([
set({
name: `instance-list-sort-order`,
value: 'desc'
}),
set({
name: `instance-list-sort-by`,
value: newSortBy
})
]);
},
toggleSelectAll: ({ selected = [], instances = [] }) => () => {
const same = selected.length === instances.length;
const hasSelected = selected.length > 0;
return (
fn &&
fn()
.catch(handleError)
.then(handleSuccess)
// none are selected, toggle to all
if (!hasSelected) {
return dispatch(
instances.map(({ id }) => change(TABLE_FORM_NAME, id, true))
);
}
// all are selected, toggle to none
if (hasSelected && same) {
return dispatch(
instances.map(({ id }) => change(TABLE_FORM_NAME, id, false))
);
}
// some are selected, toggle to all
if (hasSelected && !same) {
return dispatch(
instances.map(({ id }) => change(TABLE_FORM_NAME, id, true))
);
}
},
handleAction: async ({ selected, name }) => {
const action = ownProps[name];
const gerund = `${name}ing`;
// flips submitting flag to true so that we can disable everything
const flipSubmitTrue = startSubmit(TABLE_FORM_NAME);
// sets (starting/restarting/etc) to true so that we can, for instance,
// have a spinner on the correct button
const setIngTrue = set({
name: `instance-list-${gerund}`,
value: true
});
// sets the individual item mutation flags so that we can show a
// spinner in the row
const setMutatingTrue = selected.map(({ id }) =>
set({ name: `${id}-mutating`, value: true })
);
// wait for everything to finish and catch the error
const [err] = await intercept(
Promise.resolve(
dispatch([flipSubmitTrue, setIngTrue, ...setMutatingTrue])
).then(() => {
// starts all the mutations for all the selected items
return Promise.all(
selected.map(({ id }) => action({ variables: { id } }))
);
})
);
// parses the error to handle existance or not of graphQLErrors
const parseError = ({ graphQLErrors = [], message = '' }) =>
graphQLErrors.length
? graphQLErrors.map(({ message }) => message).join('\n')
: message;
// reverts submitting flag to false and propagates the error if it exists
const flipSubmitFalse = stopSubmit(TABLE_FORM_NAME, {
_error: err && parseError(err)
});
// if no error, clears selected
const clearSelected = !err && reset(TABLE_FORM_NAME);
// reverts (starting/restarting/etc) to false
const setIngFalse = set({
name: `instance-list-${gerund}`,
value: false
});
// reverts the individual item mutation flags
const setMutatingFalse = selected.map(({ id }) =>
set({ name: `${id}-mutating`, value: false })
);
const actions = [
flipSubmitFalse,
clearSelected,
setIngFalse,
...setMutatingFalse
].filter(Boolean);
// refetch list - even though we poll anyway - after clearing everything
return Promise.resolve(dispatch(actions)).then(() => refetch());
}
})
}),
(stateProps, dispatchProps, ownProps) => {
const { selected, instances, sortBy, sortOrder } = stateProps;
const { toggleSelectAll, handleSortBy } = dispatchProps;
return {
...ownProps,
...stateProps,
selected,
instances,
...dispatchProps,
toggleSelectAll: toggleSelectAll({ selected, instances }),
handleSortBy: handleSortBy({ sortBy, sortOrder })
};
}
)
)(List);

View File

@ -9,7 +9,7 @@ export default ({ match }) => {
const links = [
{
name: '/',
name: 'Instances',
pathname: '/instances'
}
]

View File

@ -1,19 +1,19 @@
query instance($name: String) {
machines(name: $name) {
id
state,
name,
created,
updated,
primary_ip,
ips,
docker,
dns_names,
compute_node,
state
name
created
updated
primary_ip
ips
docker
dns_names
compute_node
image {
id,
id
name
},
}
package {
name
}

View File

@ -89,9 +89,7 @@ export default () => (
path="/instances/:instance"
exact
component={({ match }) => (
<Redirect
to={`/instances/${get(match, 'params.instance')}/home`}
/>
<Redirect to={`/instances/${get(match, 'params.instance')}/home`} />
)}
/>
</Switch>

View File

@ -1,4 +1,4 @@
export default ({ graphQLErrors = [], message = '' }) =>
graphQLErrors.length
? graphQLErrors.map(({ message }) => message).join('\n')
: message;
: message;

View File

@ -1,6 +1,6 @@
{
"name": "joyent-ui-toolkit",
"version": "2.0.1",
"version": "2.4.0",
"license": "MPL-2.0",
"repository": "github:yldio/joyent-portal",
"main": "dist/umd/index.js",
@ -27,14 +27,12 @@
},
"dependencies": {
"camel-case": "^3.0.0",
"disable-scroll": "^0.3.0",
"fontfaceobserver": "^2.0.13",
"joy-react-broadcast": "^0.6.9",
"joyent-icons": "^1.0.1",
"joyent-manifest-editor": "^3.0.1",
"joyent-icons": "^1.1.0",
"joyent-manifest-editor": "^1.4.0",
"lodash.isboolean": "^3.0.3",
"lodash.isstring": "^4.0.1",
"moment": "^2.19.2",
"normalized-styled-components": "^1.0.17",
"outy": "^0.1.2",
"pascal-case": "^2.0.1",
@ -88,6 +86,7 @@
"webpack": "^3.8.1"
},
"peerDependencies": {
"joyent-manifest-editor": "^1.4.0",
"react": "^16.1.1",
"react-dom": "^16.1.1",
"react-router-dom": "^4.2.2",

View File

@ -36,7 +36,7 @@ const screens = {
largeOnly: `only screen and (min-width: ${remcalc(breakpoints.large.lower)})
and (max-width: ${remcalc(breakpoints.large.upper)})`,
largeDown: `only screen and (max-width: ${remcalc(breakpoints.large.upper)})`,
large: `only screen and (min-width: ${remcalc(breakpoints.large.upper)})`,
large: `only screen and (min-width: ${remcalc(breakpoints.large.lower)})`,
xlarge: `only screen and (min-width: ${remcalc(breakpoints.xlarge.lower)})
and (max-width: ${remcalc(breakpoints.xlarge.upper)})`,
xlargeUp: `only screen and (min-width: ${remcalc(breakpoints.xlarge.lower)})`

View File

@ -17,6 +17,9 @@ const style = css`
justify-content: center;
align-items: center;
min-height: ${remcalc(48)};
height: ${remcalc(48)};
min-width: ${remcalc(120)};
margin-bottom: ${remcalc(8)};
margin-top: ${remcalc(8)};
padding: ${remcalc(13)} ${remcalc(18)};
@ -180,12 +183,20 @@ const style = css`
${is('small')`
padding: ${remcalc(14)} ${remcalc(14)};
min-width: ${remcalc(48)};
`};
${is('icon')`
min-height: ${remcalc(48)};
display: flex;
display: inline-flex;
align-items: center;
& svg + span {
margin-left: ${remcalc(12)};
}
& svg {
max-height: ${remcalc(18)};
}
`};
${is('fluid')`
@ -200,6 +211,10 @@ const style = css`
${is('bold')`
font-weight: 500;
`};
${is('right')`
float: right;
`};
`;
const StyledButton = NButton.extend`

View File

@ -0,0 +1,10 @@
import remcalc from 'remcalc';
import Header from '../header';
export default Header.extend`
background-color: rgba(241, 241, 241, 1);
border-top: ${remcalc(1)} solid ${props => props.theme.grey};
height: ${remcalc(70)};
max-height: ${remcalc(70)};
`;

View File

@ -41,13 +41,13 @@ const style = css`
color: ${color};
&::-webkit-input-placeholder {
color: rgba(73, 73, 73, 0.5);
color: ${props => props.theme.textDisabled};
}
&::-moz-placeholder {
color: rgba(73, 73, 73, 0.5);
color: ${props => props.theme.textDisabled};
}
&:-ms-input-placeholder {
color: rgba(73, 73, 73, 0.5);
color: ${props => props.theme.textDisabled};
}
&:invalid {
@ -56,38 +56,38 @@ const style = css`
${is('disabled')`
background-color: ${props => props.theme.disabled};
color: ${props => props.theme.textDisabled};
color: ${props => props.theme.grey};
::-webkit-input-placeholder { /* WebKit, Blink, Edge */
color: ${props => props.theme.placeholder};
color: ${props => props.theme.grey};
}
::-moz-placeholder { /* Mozilla Firefox 19+ */
color: ${props => props.theme.placeholder};
color: ${props => props.theme.grey};
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: ${props => props.theme.placeholder};
color: ${props => props.theme.grey};
}
`};
&:disabled {
background-color: ${props => props.theme.disabled};
color: ${props => props.theme.textDisabled};
color: ${props => props.theme.grey};
::-webkit-input-placeholder {
/* WebKit, Blink, Edge */
color: ${props => props.theme.placeholder};
color: ${props => props.theme.textDisabled};
}
::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: ${props => props.theme.placeholder};
color: ${props => props.theme.textDisabled};
}
:-ms-input-placeholder {
/* Internet Explorer 10-11 */
color: ${props => props.theme.placeholder};
color: ${props => props.theme.textDisabled};
}
}
@ -95,7 +95,7 @@ const style = css`
width: ${remcalc(120)}
`};
${is('fluid')`
${is('fluid')`
max-width: 100%;
`};

View File

@ -22,6 +22,10 @@ const StyledFieldset = styled.div`
${is('fluid')`
width: 100%;
`};
${is('right')`
float: right;
`};
`;
const Fieldset = ({ children, ...rest }) => (

View File

@ -9,6 +9,4 @@ export { default as FormMeta } from './meta';
export { default as Radio, RadioList } from './radio';
export { default as Select } from './select';
export { default as Toggle, ToggleList } from './toggle';
export { default as NumberInput } from './number-input';
export { default as InputDropdown } from './input-dropdown';
export { NumberInputNormalize } from './number-input';

View File

@ -1,78 +0,0 @@
import React from 'react';
import BaseInput, { Stylable } from './base/input';
import { Subscriber } from 'joy-react-broadcast';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import unitcalc from 'unitcalc';
import Baseline from '../baseline';
import { Plus, Minus } from '../icons';
import IconButton from '../icon-button';
const StyledContainer = styled.div`
/* trick prettier */
margin-bottom: ${unitcalc(4)};
`;
const StyledNumberInput = styled(BaseInput(Stylable('input')))`
width: ${unitcalc(20)};
margin: 0 ${unitcalc(2)} 0 0;
vertical-align: middle;
`;
/**
* @example ./usage-number-input.md
*/
const NumberInput = BaseInput(props => {
const render = value => {
const handleMinusClick = evt => {
evt.preventDefault();
const nextValue = value.input.value - 1;
value.input.onChange(nextValue);
};
const handlePlusClick = evt => {
evt.preventDefault();
const nextValue = value.input.value + 1;
value.input.onChange(nextValue);
};
return (
<StyledContainer>
<StyledNumberInput {...props} marginRight={2} />
<IconButton onClick={handleMinusClick}>
<Minus verticalAlign="middle" />
</IconButton>
<IconButton onClick={handlePlusClick} marginLeft={1}>
<Plus verticalAlign="middle" />
</IconButton>
</StyledContainer>
);
};
return <Subscriber channel="input-group">{render}</Subscriber>;
});
NumberInput.propTypes = {
value: PropTypes.number,
minValue: PropTypes.number,
maxValue: PropTypes.number,
onChange: PropTypes.func
};
export default Baseline(NumberInput);
export const NumberInputNormalize = ({ minValue, maxValue }) => {
return value => {
if (value === '') {
return '';
}
if (
!isNaN(value) &&
(isNaN(minValue) || value >= minValue) &&
(isNaN(maxValue) || value <= maxValue)
) {
return Number(value);
}
};
};

View File

@ -17,8 +17,11 @@ const SelectWrapper = styled.div`
position: relative;
display: inline-flex;
width: 100%;
max-width: 100%;
${isNot('fluid')`
min-width: ${remcalc(200)};
min-width: ${remcalc(300)};
`};
&:after {
@ -56,7 +59,7 @@ const StyledSelect = select.extend`
position: relative;
padding: ${remcalc(12)};
padding-right: ${remcalc(25)};
${is('disabled')`
border-color: ${props => props.theme.grey};
color: ${props => props.theme.grey};

View File

@ -1,5 +0,0 @@
```
const React = require('react');
<NumberInput placeholder='I am the placeholder' value='1' onChange={() => {}} />
```

View File

@ -0,0 +1,7 @@
import { Row as BaseRow } from 'react-styled-flexboxgrid';
export const Row = BaseRow.extend`
flex: 1 1 auto;
`;
export { Grid, Col } from 'react-styled-flexboxgrid';

View File

@ -8,7 +8,7 @@ const Brand = H2.extend`
text-transform: uppercase;
color: ${props => props.theme.white};
font-size: ${remcalc(29)};
margin: 0;
margin: 0 auto;
`;
const Box = styled.div`

View File

@ -1,7 +1,9 @@
import React from 'react';
import styled from 'styled-components';
import remcalc from 'remcalc';
import is from 'styled-is';
import Basealign from '../basealign';
import { ViewContainer } from '../layout';
const Container = ViewContainer.extend`
@ -9,8 +11,6 @@ const Container = ViewContainer.extend`
flex-wrap: nowrap;
align-content: stretch;
align-items: stretch;
max-height: ${remcalc(53)};
min-height: ${remcalc(53)};
`;
const Header = styled.div`
@ -23,16 +23,26 @@ const Header = styled.div`
max-height: ${remcalc(53)};
min-height: ${remcalc(53)};
line-height: ${remcalc(25)};
${is('fixed')`
position: fixed;
left: 0;
right: 0;
`};
${is('bottom', 'fixed')`
bottom: 0;
`};
`;
/**
* @example ./usage.md
*/
export default ({ children, fluid, ...rest }) => (
export default Basealign(({ children, fluid, ...rest }) => (
<Header {...rest}>
<Container fluid={fluid}>{children}</Container>
</Header>
);
));
export { default as HeaderItem, Anchor as HeaderAnchor } from './item';
export { default as HeaderBrand } from './brand';

View File

@ -7,7 +7,7 @@ import { A } from 'normalized-styled-components';
import P from '../text/p';
const style = css`
padding: ${remcalc(15)};
padding: ${remcalc(15)} 0;
line-height: ${remcalc(24)};
font-size: ${remcalc(15)};
color: ${props => props.theme.white};

View File

@ -1,12 +1,9 @@
import React from 'react';
import styled from 'styled-components';
const UL = styled.ul`
export default styled.ul`
padding: 0;
margin: 0;
display: flex;
list-style: none;
color: ${props => props.theme.white};
`;
export default ({ children, ...rest }) => <UL {...rest}>{children}</UL>;

View File

@ -1,128 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import styled, { css } from 'styled-components';
import remcalc from 'remcalc';
import { borderRadius } from '../boxes';
import Baseline from '../baseline';
import { A, Button as NButton } from 'normalized-styled-components';
import { Link } from 'react-router-dom';
const style = css`
border-radius: ${borderRadius};
border: solid ${remcalc(1)} ${props => props.theme.grey};
background-color: ${props => props.theme.white};
box-sizing: border-box;
display: inline-block;
justify-content: center;
align-items: center;
padding: ${remcalc(15)} ${remcalc(18)};
position: relative;
text-align: center;
line-height: normal;
vertical-align: middle;
touch-action: manipulation;
min-width: 0;
cursor: pointer;
> svg {
fill: ${props => props.theme.secondary};
}
&:focus {
outline: 0;
background-color: ${props => props.theme.white};
border-color: ${props => props.theme.grey};
}
&:hover {
background-color: ${props => props.theme.whiteHover};
border-color: ${props => props.theme.grey};
}
&:focus,
&:hover > svg {
fill: ${props => props.theme.secondaryHover};
}
&:active,
&:active:hover,
&:active:focus {
outline: 0;
background-color: ${props => props.theme.whiteActive};
border-color: ${props => props.theme.grey};
}
&:active,
&:active:hover,
&:active:focus > svg {
fill: ${props => props.theme.secondaryActive};
}
&[disabled] {
cursor: not-allowed;
pointer-events: none;
color: ${props => props.theme.grey};
background-color: ${props => props.theme.disabled};
border-color: ${props => props.theme.grey};
> svg {
fill: ${props => props.theme.grey};
}
&:focus,
&:hover,
&:active,
&:active:hover,
&:active:focus {
background-color: ${props => props.theme.disabled};
border-color: ${props => props.theme.disabled};
}
&:focus,
&:hover,
&:active,
&:active:hover,
&:active:focus > svg {
fill: ${props => props.theme.grey};
}
}
`;
const StyledButton = NButton.extend`
/* trick prettier */
${style};
`;
const StyledAnchor = A.extend`
display: inline-block;
${style};
`;
const StyledLink = styled(Link)`
display: inline-block;
${style};
`;
/**
* @example ./usage.md
*/
const IconButton = props => {
const { href = '', to = '', children } = props;
const Views = [
() => (to ? StyledLink : null),
() => (href ? StyledAnchor : null),
() => StyledButton
];
const View = Views.reduce((sel, view) => (sel ? sel : view()), null);
return <View {...props}>{children}</View>;
};
IconButton.propTypes = {
children: PropTypes.node,
onClick: PropTypes.func
};
export default Baseline(IconButton);

View File

@ -14,7 +14,8 @@ export { default as Modal, ModalHeading, ModalText } from './modal';
export { default as CloseButton } from './close-button';
export { default as Divider } from './divider';
export { default as Editor } from './editor';
export { default as IconButton } from './icon-button';
export { default as Footer } from './footer';
export { Grid, Row, Col } from './grid';
export { default as StatusLoader } from './status-loader';
export { default as Breadcrumb, Item as BreadcrumbItem } from './breadcrumb';

View File

@ -5,8 +5,10 @@ import is, { isNot } from 'styled-is';
import { styled as breakpoints } from '../breakpoints';
export default Grid.extend`
box-sizing: border-box;
width: 100%;
${is('fluid')`
width: 100%;
padding-left: 0;
padding-right: 0;
`};

View File

@ -1,7 +1,7 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import styled from 'styled-components';
import disableScroll from 'disable-scroll';
// import disableScroll from 'disable-scroll';
import remcalc from 'remcalc';
import { modalShadow } from '../boxes';
import CloseButton from '../close-button';
@ -40,11 +40,11 @@ const StyledClose = styled(CloseButton)`
class Modal extends Component {
componentDidMount() {
disableScroll.on();
// disableScroll.on();
}
componentWillUnmount() {
disableScroll.off();
// disableScroll.off();
}
render() {

View File

@ -4,8 +4,11 @@ import styled from 'styled-components';
import { Popper as BasePopper, Arrow } from 'react-popper';
import rndId from 'rnd-id';
import remcalc from 'remcalc';
import is from 'styled-is';
import style from '../tooltip/style';
import { default as BaseTarget } from '../tooltip/target';
import Baseline from '../baseline';
const arrowClassName = rndId();
@ -24,6 +27,25 @@ const Popper = styled(BasePopper)`
})};
`;
const StyledTarget = styled(BaseTarget)`
${is('box')`
cursor: pointer;
height: 100%;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
`};
`;
export const Target = Baseline(({ children, box = false, ...rest }) => (
<StyledTarget box={box} tag={box ? 'div' : false} {...rest}>
{children}
</StyledTarget>
));
export default class Popover extends Component {
static contextTypes = {
ttpContMangr: PropTypes.object.isRequired
@ -49,4 +71,3 @@ export default class Popover extends Component {
export { default as Divider } from './divider';
export { default as Item } from './item';
export { default as Container } from '../tooltip/container';
export { default as Target } from '../tooltip/target';

View File

@ -1,8 +1,12 @@
import React, { Component } from 'react';
import remcalc from 'remcalc';
import is, { isNot } from 'styled-is';
import PropTypes from 'prop-types';
import { H4 } from '../text/headings';
import { H4 as BaseH4 } from '../text/headings';
import Baseline from '../baseline';
export default H4.extend`
const BaseItem = BaseH4.extend`
font-size: ${remcalc(16)};
text-align: left;
@ -10,10 +14,41 @@ export default H4.extend`
font-weight: normal;
line-height: normal;
cursor: pointer;
color: ${props => props.theme.secondary};
&:hover {
color: ${props => props.theme.secondaryActive};
}
${isNot('disabled')`
cursor: pointer;
&:hover {
color: ${props => props.theme.secondaryHover};
}
`};
${is('disabled')`
color: ${props => props.theme.grey};
`};
`;
class Item extends Component {
static contextTypes = {
ttpContMangr: PropTypes.object.isRequired
};
render = () => {
const { children = null, onClick = () => null, ...rest } = this.props;
const { handleTargetClick = () => null } = this.context.ttpContMangr;
const handleClick = (...args) => {
handleTargetClick(...args);
onClick(...args);
};
return (
<BaseItem {...rest} onClick={handleClick}>
{children}
</BaseItem>
);
};
}
export default Baseline(Item);

View File

@ -12,12 +12,17 @@ import * as breakpoints from '../breakpoints';
const { styled: query } = breakpoints;
const handleBreakpoint = bp => props => {
const hidden =
(isBoolean(props[bp]) && !props[bp]) || Number(props[bp]) === 0;
const num = Number(props[bp]);
const hidden = (isBoolean(props[bp]) && !props[bp]) || num === 0;
const width = remcalc(props[bp]);
if (!hidden && Number.isNaN(num)) {
return null;
}
return `
width: ${width};
display: table-cell;
${hidden &&
`
@ -64,14 +69,7 @@ const Column = css`
${is('actionable')`
cursor: pointer;
&:hover {
background-color: ${props => props.theme.whiteHover};
}
&:active {
background-color: ${props => props.theme.whiteActive};
}
user-select: none;
`};
${is('baseline')`
@ -124,7 +122,7 @@ const BaseTable = styled.table`
max-width: 100%;
`;
const BaseThFooter = styled.tfoot`
const BaseTfoot = styled.tfoot`
width: 100%;
th:first-child {
@ -143,32 +141,17 @@ const BaseThFooter = styled.tfoot`
const BaseThead = styled.thead`
width: 100%;
${is('footer')`
th:first-child {
border-bottom-left-radius: ${remcalc(4)};
}
th:first-child {
border-top-left-radius: ${remcalc(4)};
}
th:last-child {
border-bottom-right-radius: ${remcalc(4)};
}
th:last-child {
border-top-right-radius: ${remcalc(4)};
}
th {
border-top-width: 0;
}
`};
${isNot('footer')`
th:first-child {
border-top-left-radius: ${remcalc(4)};
}
th:last-child {
border-top-right-radius: ${remcalc(4)};
}
th {
border-bottom-width: 0;
}
`};
th {
border-bottom-width: 0;
}
`;
const BaseTbody = styled.tbody`
@ -190,8 +173,6 @@ const BaseTbody = styled.tbody`
const BaseTh = styled.th`
${Column};
text-align: left;
padding: ${remcalc(12)} ${remcalc(24)};
height: ${remcalc(42)};
color: ${props => props.theme.greyLight};
font-weight: 500;
@ -209,21 +190,13 @@ const BaseTh = styled.th`
border-right-width: 0;
}
${is('right')`
text-align: right;
`};
${ColumnBorder};
`;
const BaseTd = styled.td`
${Column};
border-bottom-width: 0;
vertical-align: middle;
* {
vertical-align: middle;
}
border-bottom-width: 0;
&:not(:first-child) {
border-left-width: 0;
@ -233,8 +206,9 @@ const BaseTd = styled.td`
border-right-width: 0;
}
${is('right')`
text-align: right;
${is('selected')`
border-color: ${props => props.theme.primary};
background-color: rgba(59, 70, 204, 0.05);
`};
${ColumnBorder};
@ -253,10 +227,6 @@ const BaseTr = styled.tr`
${is('selected')`
box-shadow: none;
td {
border: 1px solid ${props => props.theme.primary};
background-color: rgba(59, 70, 204, 0.05);;
}
`};
&:last-child td {
@ -293,12 +263,12 @@ export default Baseline(({ children, ...rest }) => (
const Propagate = ({ children, ...rest }) => (
<Subscriber channel="almost-responsive-table">
{({ disabled, header }) => (
{({ disabled, header, selected } = {}) => (
<Broadcast
channel="almost-responsive-table"
value={{ disabled, header, ...rest }}
value={{ disabled, header, selected, ...rest }}
>
{children({ disabled, header, ...rest })}
{children({ disabled, header, selected, ...rest })}
</Broadcast>
)}
</Subscriber>
@ -314,12 +284,12 @@ export const Thead = Baseline(({ children, ...rest }) => (
</Propagate>
));
export const ThFooter = Baseline(({ children, ...rest }) => (
export const Tfoot = Baseline(({ children, ...rest }) => (
<Propagate {...rest} header={true}>
{value => (
<BaseThFooter {...value} name="thfoot">
<BaseTfoot {...value} name="thfoot">
{children}
</BaseThFooter>
</BaseTfoot>
)}
</Propagate>
));

View File

@ -90,7 +90,7 @@ export default ({ background, color, border, arrow }) => css`
border-color: transparent transparent transparent
${props => props.theme[border]};
border-style: solid;
left: ${remcalc(-2)};
left: ${remcalc(-6)};
width: 0;
height: 0;
top: ${remcalc(-7)};

View File

@ -8,7 +8,7 @@ export default class Target extends Component {
};
render = () => {
const { children, ...rest } = this.props;
const { children, tag = false, ...rest } = this.props;
const {
setRef,
@ -38,6 +38,7 @@ export default class Target extends Component {
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
tag={tag}
{...rest}
>
{children}

349
yarn.lock
View File

@ -2,53 +2,54 @@
# yarn lockfile v1
"@babel/code-frame@7.0.0-beta.32", "@babel/code-frame@^7.0.0-beta.31":
version "7.0.0-beta.32"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.32.tgz#04f231b8ec70370df830d9926ce0f5add074ec4c"
"@babel/code-frame@7.0.0-beta.31":
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0-beta.31.tgz#473d021ecc573a2cce1c07d5b509d5215f46ba35"
dependencies:
chalk "^2.0.0"
esutils "^2.0.2"
js-tokens "^3.0.0"
"@babel/helper-function-name@7.0.0-beta.32":
version "7.0.0-beta.32"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.32.tgz#6161af4419f1b4e3ed2d28c0c79c160e218be1f3"
"@babel/helper-function-name@7.0.0-beta.31":
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.31.tgz#afe63ad799209989348b1109b44feb66aa245f57"
dependencies:
"@babel/helper-get-function-arity" "7.0.0-beta.32"
"@babel/template" "7.0.0-beta.32"
"@babel/types" "7.0.0-beta.32"
"@babel/helper-get-function-arity" "7.0.0-beta.31"
"@babel/template" "7.0.0-beta.31"
"@babel/traverse" "7.0.0-beta.31"
"@babel/types" "7.0.0-beta.31"
"@babel/helper-get-function-arity@7.0.0-beta.32":
version "7.0.0-beta.32"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.32.tgz#93721a99db3757de575a83bab7c453299abca568"
"@babel/helper-get-function-arity@7.0.0-beta.31":
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.31.tgz#1176d79252741218e0aec872ada07efb2b37a493"
dependencies:
"@babel/types" "7.0.0-beta.32"
"@babel/types" "7.0.0-beta.31"
"@babel/template@7.0.0-beta.32":
version "7.0.0-beta.32"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.32.tgz#e1d9fdbd2a7bcf128f2f920744a67dab18072495"
"@babel/template@7.0.0-beta.31":
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.0.0-beta.31.tgz#577bb29389f6c497c3e7d014617e7d6713f68bda"
dependencies:
"@babel/code-frame" "7.0.0-beta.32"
"@babel/types" "7.0.0-beta.32"
babylon "7.0.0-beta.32"
"@babel/code-frame" "7.0.0-beta.31"
"@babel/types" "7.0.0-beta.31"
babylon "7.0.0-beta.31"
lodash "^4.2.0"
"@babel/traverse@^7.0.0-beta.31":
version "7.0.0-beta.32"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.32.tgz#b78b754c6e1af3360626183738e4c7a05951bc99"
"@babel/traverse@7.0.0-beta.31":
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.0.0-beta.31.tgz#db399499ad74aefda014f0c10321ab255134b1df"
dependencies:
"@babel/code-frame" "7.0.0-beta.32"
"@babel/helper-function-name" "7.0.0-beta.32"
"@babel/types" "7.0.0-beta.32"
babylon "7.0.0-beta.32"
"@babel/code-frame" "7.0.0-beta.31"
"@babel/helper-function-name" "7.0.0-beta.31"
"@babel/types" "7.0.0-beta.31"
babylon "7.0.0-beta.31"
debug "^3.0.1"
globals "^10.0.0"
invariant "^2.2.0"
lodash "^4.2.0"
"@babel/types@7.0.0-beta.32", "@babel/types@^7.0.0-beta.31":
version "7.0.0-beta.32"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.32.tgz#c317d0ecc89297b80bbcb2f50608e31f6452a5ff"
"@babel/types@7.0.0-beta.31":
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0-beta.31.tgz#42c9c86784f674c173fb21882ca9643334029de4"
dependencies:
esutils "^2.0.2"
lodash "^4.2.0"
@ -251,8 +252,8 @@ ajv@^4.9.1:
json-stable-stringify "^1.0.1"
ajv@^5.0.0, ajv@^5.1.0, ajv@^5.1.5, ajv@^5.2.0, ajv@^5.2.3, ajv@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.0.tgz#eb2840746e9dc48bd5e063a36e3fd400c5eab5a9"
version "5.5.1"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.1.tgz#b38bb8876d9e86bee994956a04e721e88b248eb2"
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
@ -690,7 +691,7 @@ automated-readability@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/automated-readability/-/automated-readability-1.0.2.tgz#6e78abc082bf6389ff033c5b6e35d68cf3eba405"
autoprefixer@7.1.6, autoprefixer@^7.1.2:
autoprefixer@7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.1.6.tgz#fb933039f74af74a83e71225ce78d9fd58ba84d7"
dependencies:
@ -712,6 +713,17 @@ autoprefixer@^6.3.1:
postcss "^5.2.16"
postcss-value-parser "^3.2.3"
autoprefixer@^7.1.2:
version "7.2.1"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-7.2.1.tgz#906b1447a0e6a9e13b371f7909bc4e36da5a5a79"
dependencies:
browserslist "^2.9.1"
caniuse-lite "^1.0.30000777"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
postcss "^6.0.14"
postcss-value-parser "^3.2.3"
aws-sign2@~0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
@ -801,13 +813,13 @@ babel-eslint@7.2.3:
babylon "^6.17.0"
babel-eslint@^8.0.1:
version "8.0.2"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.0.2.tgz#e44fb9a037d749486071d52d65312f5c20aa7530"
version "8.0.3"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.0.3.tgz#f29ecf02336be438195325cd47c468da81ee4e98"
dependencies:
"@babel/code-frame" "^7.0.0-beta.31"
"@babel/traverse" "^7.0.0-beta.31"
"@babel/types" "^7.0.0-beta.31"
babylon "^7.0.0-beta.31"
"@babel/code-frame" "7.0.0-beta.31"
"@babel/traverse" "7.0.0-beta.31"
"@babel/types" "7.0.0-beta.31"
babylon "7.0.0-beta.31"
babel-generator@^6.18.0, babel-generator@^6.26.0:
version "6.26.0"
@ -1698,10 +1710,6 @@ babylon@7.0.0-beta.31:
version "7.0.0-beta.31"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.31.tgz#7ec10f81e0e456fd0f855ad60fa30c2ac454283f"
babylon@7.0.0-beta.32, babylon@^7.0.0-beta.31:
version "7.0.0-beta.32"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.32.tgz#e9033cb077f64d6895f4125968b37dc0a8c3bc6e"
babylon@^6.10.0, babylon@^6.12.0, babylon@^6.17.0, babylon@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
@ -1718,10 +1726,6 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
base16@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/base16/-/base16-1.0.0.tgz#e297f60d7ec1014a7a971a39ebc8a98c0b681e70"
base64-js@^1.0.2:
version "1.2.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
@ -1812,7 +1816,7 @@ boom@5.x.x:
dependencies:
hoek "4.x.x"
boxen@1.2.2, boxen@^1.2.1:
boxen@1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.2.2.tgz#3f1d4032c30ffea9d4b02c322eaf2ea741dcbce5"
dependencies:
@ -1838,6 +1842,18 @@ boxen@^0.6.0:
string-width "^1.0.1"
widest-line "^1.0.0"
boxen@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
dependencies:
ansi-align "^2.0.0"
camelcase "^4.0.0"
chalk "^2.0.1"
cli-boxes "^1.0.0"
string-width "^2.0.0"
term-size "^1.2.0"
widest-line "^2.0.0"
brace-expansion@^1.0.0, brace-expansion@^1.1.7:
version "1.1.8"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
@ -1940,7 +1956,7 @@ browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6:
caniuse-db "^1.0.30000639"
electron-to-chromium "^1.2.7"
browserslist@^2.1.2, browserslist@^2.5.1:
browserslist@^2.1.2, browserslist@^2.5.1, browserslist@^2.9.1:
version "2.9.1"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.9.1.tgz#b72d3982ab01b5cd24da62ff6d45573886aff275"
dependencies:
@ -2094,12 +2110,12 @@ caniuse-api@^1.5.2:
lodash.uniq "^4.5.0"
caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639:
version "1.0.30000772"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000772.tgz#51aae891768286eade4a3d8319ea76d6a01b512b"
version "1.0.30000778"
resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000778.tgz#167c60e9542a2aa60537c446fb3881d853a3072a"
caniuse-lite@^1.0.30000748, caniuse-lite@^1.0.30000770:
version "1.0.30000772"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000772.tgz#78129622cabfed7af1ff38b64ab680a6a0865420"
caniuse-lite@^1.0.30000748, caniuse-lite@^1.0.30000770, caniuse-lite@^1.0.30000777:
version "1.0.30000778"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000778.tgz#f1e7cb8b13b1f6744402291d75f0bcd4c3160369"
capture-stack-trace@^1.0.0:
version "1.0.0"
@ -2296,14 +2312,6 @@ clipboard-copy@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/clipboard-copy/-/clipboard-copy-1.2.0.tgz#f6a3de65a8a252fa993fcb2a4e0cfe3aa4b8769e"
clipboard@^1.7.1:
version "1.7.1"
resolved "https://registry.yarnpkg.com/clipboard/-/clipboard-1.7.1.tgz#360d6d6946e99a7a1fef395e42ba92b5e9b5a16b"
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"
clipboardy@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.1.4.tgz#51b17574fc682588e2dd295cfa6e6aa109eab5ee"
@ -2358,7 +2366,7 @@ code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
codemirror@5.32.0, codemirror@^5.30.0, codemirror@^5.31.0:
codemirror@5.32.0, codemirror@^5.18.2, codemirror@^5.30.0, codemirror@^5.31.0:
version "5.32.0"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.32.0.tgz#cb6ff5d8ef36d0b10f031130e2d9ebeee92c902e"
@ -2436,8 +2444,8 @@ commander@2.1.x, commander@~2.1.0:
resolved "https://registry.yarnpkg.com/commander/-/commander-2.1.0.tgz#d121bbae860d9992a3d517ba96f56588e47c6781"
commander@2.12.x, commander@^2.11.0, commander@^2.9.0, commander@~2.12.1:
version "2.12.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.1.tgz#468635c4168d06145b9323356d1da84d14ac4a7a"
version "2.12.2"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.12.2.tgz#0f5946c427ed9ec0d91a46bb9def53e54650e555"
commitizen@^2.9.6:
version "2.9.6"
@ -2830,6 +2838,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
create-react-class@^15.5.1:
version "15.6.2"
resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.2.tgz#cf1ed15f12aad7f14ef5f2dfe05e6c42f91ef02a"
dependencies:
fbjs "^0.8.9"
loose-envify "^1.3.1"
object-assign "^4.1.1"
cross-env@^5.1.0, cross-env@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-5.1.1.tgz#b6d8ab97f304c0f71dae7277b75fe424c08dfa74"
@ -3105,7 +3121,7 @@ decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
declarative-redux-form@^1.0.3:
declarative-redux-form@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/declarative-redux-form/-/declarative-redux-form-1.0.4.tgz#34e08d473f9a655e261c7c295ef1106c40e8fef7"
dependencies:
@ -3183,10 +3199,6 @@ delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
delegate@^3.1.2:
version "3.1.3"
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.1.3.tgz#9a8251a777d7025faa55737bc3b071742127a9fd"
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
@ -3274,10 +3286,6 @@ dir-glob@^2.0.0:
arrify "^1.0.1"
path-type "^3.0.0"
disable-scroll@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/disable-scroll/-/disable-scroll-0.3.0.tgz#486d309ec9873edb18aec7891c5576bf8b506c59"
dlv@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.0.tgz#fee1a7c43f63be75f3f679e85262da5f102764a7"
@ -3835,8 +3843,8 @@ eslint@4.10.0:
text-table "~0.2.0"
eslint@^4.11.0, eslint@^4.9.0:
version "4.12.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.12.0.tgz#a7ce78eba8cc8f2443acfbbc870cc31a65135884"
version "4.12.1"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.12.1.tgz#5ec1973822b4a066b353770c3c6d69a2a188e880"
dependencies:
ajv "^5.3.0"
babel-code-frame "^6.22.0"
@ -4118,10 +4126,14 @@ extract-text-webpack-plugin@3.0.2:
schema-utils "^0.3.0"
webpack-sources "^1.0.1"
extsprintf@1.3.0, extsprintf@^1.2.0:
extsprintf@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
extsprintf@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
facet@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/facet/-/facet-0.3.0.tgz#1bf949d8a112e2045dda84259407def3deb62c1b"
@ -4179,13 +4191,7 @@ fb-watchman@^2.0.0:
dependencies:
bser "^2.0.0"
fbemitter@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/fbemitter/-/fbemitter-2.1.1.tgz#523e14fdaf5248805bb02f62efc33be703f51865"
dependencies:
fbjs "^0.8.4"
fbjs@^0.8.0, fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.4, fbjs@^0.8.5, fbjs@^0.8.9:
fbjs@^0.8.1, fbjs@^0.8.16, fbjs@^0.8.5, fbjs@^0.8.9:
version "0.8.16"
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db"
dependencies:
@ -4357,13 +4363,6 @@ flush-write-stream@^1.0.0:
inherits "^2.0.1"
readable-stream "^2.0.4"
flux@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/flux/-/flux-3.1.3.tgz#d23bed515a79a22d933ab53ab4ada19d05b2f08a"
dependencies:
fbemitter "^2.0.0"
fbjs "^0.8.0"
fontfaceobserver@^2.0.13:
version "2.0.13"
resolved "https://registry.yarnpkg.com/fontfaceobserver/-/fontfaceobserver-2.0.13.tgz#47adbb343261eda98cb44db2152196ff124d3221"
@ -4764,12 +4763,6 @@ glogg@^1.0.0:
dependencies:
sparkles "^1.0.0"
good-listener@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
dependencies:
delegate "^3.1.2"
got@^5.0.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/got/-/got-5.7.1.tgz#5f81635a61e4a6589f180569ea4e381680a51f35"
@ -4815,8 +4808,8 @@ graphql-anywhere@^3.0.1:
resolved "https://registry.yarnpkg.com/graphql-anywhere/-/graphql-anywhere-3.1.0.tgz#3ea0d8e8646b5cee68035016a9a7557c15c21e96"
graphql-config@^1.0.7:
version "1.0.8"
resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-1.0.8.tgz#6dd1cd76ff6fbb01662704f8bddc403f6b0c24d9"
version "1.0.9"
resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-1.0.9.tgz#8fa416a7c2bdb8f62f441324775dd3ff8a266652"
dependencies:
graphql "^0.11.7"
graphql-request "^1.4.0"
@ -5342,8 +5335,8 @@ internal-ip@1.2.0:
meow "^3.3.0"
interpret@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0"
version "1.1.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.1, invariant@^2.2.2:
version "2.2.2"
@ -5542,8 +5535,8 @@ is-path-in-cwd@^1.0.0:
is-path-inside "^1.0.0"
is-path-inside@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f"
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
dependencies:
path-is-inside "^1.0.1"
@ -6243,13 +6236,13 @@ joy-react-broadcast@^0.6.9:
prop-types "^15.5.6"
warning "^3.0.0"
joyent-manifest-editor@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/joyent-manifest-editor/-/joyent-manifest-editor-3.0.1.tgz#b99dd7d0806bd049fbc28e0ae93c3e018b3e1735"
joyent-manifest-editor@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/joyent-manifest-editor/-/joyent-manifest-editor-1.4.0.tgz#0c02efe6c02b0386a5b209ae4ddcc3492b9c22ac"
dependencies:
prettier "^1.7.4"
prop-types "^15.6.0"
react-codemirror2 "^3.0.3"
react-codemirror "^1.0.0"
joyent-react-scripts@^3.1.1:
version "3.1.1"
@ -6271,8 +6264,8 @@ joyent-react-scripts@^3.1.1:
webpack-sources "1.0.1"
js-base64@^2.1.9:
version "2.3.2"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf"
version "2.4.0"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.0.tgz#9e566fee624751a1d720c966cd6226d29d4025aa"
js-tokens@^3.0.0, js-tokens@^3.0.2:
version "3.0.2"
@ -6668,9 +6661,9 @@ lodash.cond@^4.3.0:
version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
lodash.curry@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.curry/-/lodash.curry-4.1.1.tgz#248e36072ede906501d75966200a86dab8b23170"
lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
lodash.defaults@4.2.0, lodash.defaults@^4.2.0:
version "4.2.0"
@ -6688,10 +6681,6 @@ lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
lodash.flow@^3.3.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/lodash.flow/-/lodash.flow-3.5.0.tgz#87bf40292b8cf83e4e8ce1a3ae4209e20071675a"
lodash.get@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
@ -6851,8 +6840,8 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
yallist "^2.1.2"
lunr@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.1.4.tgz#52be0a9d068321909a7fb46575620e24417d5591"
version "2.1.5"
resolved "https://registry.yarnpkg.com/lunr/-/lunr-2.1.5.tgz#826601ccaeac29148e224154b34760faf4d81b70"
macaddress@^0.2.8:
version "0.2.8"
@ -7035,8 +7024,8 @@ miller-rabin@^4.0.0:
brorand "^1.0.1"
"mime-db@>= 1.30.0 < 2":
version "1.31.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.31.0.tgz#a49cd8f3ebf3ed1a482b60561d9105ad40ca74cb"
version "1.32.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.32.0.tgz#485b3848b01a3cda5f968b4882c0771e58e09414"
mime-db@~1.30.0:
version "1.30.0"
@ -7125,9 +7114,9 @@ modify-values@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.0.tgz#e2b6cdeb9ce19f99317a53722f3dbf5df5eaaab2"
moment@^2.19.2, moment@^2.6.0:
version "2.19.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.2.tgz#8a7f774c95a64550b4c7ebd496683908f9419dbe"
moment@^2.6.0:
version "2.19.3"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.19.3.tgz#bdb99d270d6d7fda78cc0fbace855e27fe7da69f"
move-concurrently@^1.0.1:
version "1.0.1"
@ -8340,10 +8329,6 @@ punycode@^1.2.4, punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
pure-color@^1.2.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/pure-color/-/pure-color-1.3.0.tgz#1fe064fb0ac851f0de61320a8bf796836422f33e"
q-i@^1.1.4:
version "1.2.0"
resolved "https://registry.yarnpkg.com/q-i/-/q-i-1.2.0.tgz#2cd2ab41784dc3c583e35c70a541d93c3fde5d4a"
@ -8497,15 +8482,6 @@ react-apollo@^1.4.16:
object-assign "^4.0.1"
prop-types "^15.5.8"
react-base16-styling@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/react-base16-styling/-/react-base16-styling-0.5.3.tgz#3858f24e9c4dd8cbd3f702f3f74d581ca2917269"
dependencies:
base16 "^1.0.0"
lodash.curry "^4.0.1"
lodash.flow "^3.3.0"
pure-color "^1.2.0"
react-bundle@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/react-bundle/-/react-bundle-1.0.4.tgz#ea03cae97be357ff8e290e785f4e30d0e065b920"
@ -8517,9 +8493,16 @@ react-codemirror2@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-2.0.2.tgz#68b2ae8923174a2b3d8b6fe905d0fd3c91d97d97"
react-codemirror2@^3.0.3:
version "3.0.7"
resolved "https://registry.yarnpkg.com/react-codemirror2/-/react-codemirror2-3.0.7.tgz#d5d9888158263ae56da766539d7803486566ab9f"
react-codemirror@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/react-codemirror/-/react-codemirror-1.0.0.tgz#91467b53b1f5d80d916a2fd0b4c7adb85a9001ba"
dependencies:
classnames "^2.2.5"
codemirror "^5.18.2"
create-react-class "^15.5.1"
lodash.debounce "^4.0.8"
lodash.isequal "^4.5.0"
prop-types "^15.5.4"
react-create-component-from-tag-prop@^1.2.1:
version "1.3.1"
@ -8629,15 +8612,6 @@ react-input-range@^1.2.1:
autobind-decorator "^1.3.4"
prop-types "^15.5.8"
react-json-view@^1.13.3:
version "1.13.3"
resolved "https://registry.yarnpkg.com/react-json-view/-/react-json-view-1.13.3.tgz#0d67958badf6642f733cc8832d99bfad5c27d389"
dependencies:
clipboard "^1.7.1"
flux "^3.1.3"
react-base16-styling "^0.5.3"
react-textarea-autosize "^5.1.0"
react-modal@3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.1.3.tgz#bead112cd84681a1ccd18ee1e96c73d881793b75"
@ -8811,19 +8785,13 @@ react-styleguidist@^6.0.33:
webpack-merge "^4.1.0"
react-test-renderer@^16.0.0-0, react-test-renderer@^16.1.1:
version "16.1.1"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.1.1.tgz#a05184688d564be799f212449262525d1e350537"
version "16.2.0"
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211"
dependencies:
fbjs "^0.8.16"
object-assign "^4.1.1"
prop-types "^15.6.0"
react-textarea-autosize@^5.1.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/react-textarea-autosize/-/react-textarea-autosize-5.2.1.tgz#2b78f9067180f41b08ac59f78f1581abadd61e54"
dependencies:
prop-types "^15.6.0"
react@16.1.1, react@^15.5.4, react@^16.1.1:
version "16.1.1"
resolved "https://registry.yarnpkg.com/react/-/react-16.1.1.tgz#d5c4ef795507e3012282dd51261ff9c0e824fe1f"
@ -9638,10 +9606,6 @@ select-hose@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
select@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
selfsigned@^1.9.1:
version "1.10.1"
resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52"
@ -10200,8 +10164,8 @@ styled-components-spacing@^2.1.3:
styled-components-breakpoint "^1.0.0-preview.3"
styled-components@^2.2.3:
version "2.2.3"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.2.3.tgz#154575c269880c840f903f580287dab155cf684c"
version "2.2.4"
resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-2.2.4.tgz#dd87fd3dafd359e7a0d570aec1bd07d691c0b5a2"
dependencies:
buffer "^5.0.3"
css-to-react-native "^2.0.3"
@ -10210,7 +10174,7 @@ styled-components@^2.2.3:
is-function "^1.0.1"
is-plain-object "^2.0.1"
prop-types "^15.5.4"
stylis "3.x"
stylis "^3.4.0"
supports-color "^3.2.3"
styled-is@^1.1.0:
@ -10240,8 +10204,8 @@ stylelint-config-styled-components@^0.1.1:
resolved "https://registry.yarnpkg.com/stylelint-config-styled-components/-/stylelint-config-styled-components-0.1.1.tgz#b408388d7c687833ab4be4c4e6522d97d2827ede"
stylelint-processor-styled-components@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stylelint-processor-styled-components/-/stylelint-processor-styled-components-1.1.1.tgz#c16ee9d6cda0a8c150c3b620812960bdf30958dc"
version "1.2.0"
resolved "https://registry.yarnpkg.com/stylelint-processor-styled-components/-/stylelint-processor-styled-components-1.2.0.tgz#cb21ba75eaf5172c20e0ee2a8afba4ff7bcf0ec9"
dependencies:
babel-traverse "^6.16.0"
babylon "^6.12.0"
@ -10291,7 +10255,7 @@ stylelint@^8.2.0:
svg-tags "^1.0.0"
table "^4.0.1"
stylis@3.x:
stylis@3.x, stylis@^3.4.0:
version "3.4.5"
resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.4.5.tgz#d7b9595fc18e7b9c8775eca8270a9a1d3e59806e"
@ -10376,8 +10340,8 @@ syllable@^3.0.0:
trim "0.0.1"
symbol-observable@^1.0.2, symbol-observable@^1.0.3, symbol-observable@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d"
version "1.1.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32"
symbol-tree@^3.2.1:
version "3.2.2"
@ -10538,10 +10502,6 @@ timers-browserify@^2.0.4:
dependencies:
setimmediate "^1.0.4"
tiny-emitter@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
title-case@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/title-case/-/title-case-2.1.1.tgz#3e127216da58d2bc5becf137ab91dae3a7cd8faa"
@ -10720,8 +10680,8 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
typescript-eslint-parser@^9.0.0:
version "9.0.0"
resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-9.0.0.tgz#6a9e9c4bafbebc7e6df53c631bc7036597227d18"
version "9.0.1"
resolved "https://registry.yarnpkg.com/typescript-eslint-parser/-/typescript-eslint-parser-9.0.1.tgz#1497a565d192ca2a321bc5bbf89dcab0a2da75e8"
dependencies:
lodash.unescape "4.0.1"
semver "5.4.1"
@ -10731,15 +10691,15 @@ ua-parser-js@^0.7.9:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac"
uglify-es@^3.0.24:
version "3.2.0"
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.2.0.tgz#fbbfb9dc465ec7e5065701b9720d0de977d0bc24"
version "3.2.1"
resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.2.1.tgz#93de0aad8a1bb629c8a316f686351bc4d6ece687"
dependencies:
commander "~2.12.1"
source-map "~0.6.1"
uglify-js@3.2.x, uglify-js@^3.0.13:
version "3.2.0"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.2.0.tgz#cb411ee4ca0e0cadbfe3a4e1a1da97e6fa0d19c1"
version "3.2.1"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.2.1.tgz#d6427fd45a25fefc5d196689c0c772a6915e10fe"
dependencies:
commander "~2.12.1"
source-map "~0.6.1"
@ -11283,7 +11243,7 @@ webpack-sources@1.0.1, webpack-sources@^1.0.1:
source-list-map "^2.0.0"
source-map "~0.5.3"
webpack@3.8.1, webpack@^3.1.0, webpack@^3.8.1:
webpack@3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.8.1.tgz#b16968a81100abe61608b0153c9159ef8bb2bd83"
dependencies:
@ -11310,6 +11270,33 @@ webpack@3.8.1, webpack@^3.1.0, webpack@^3.8.1:
webpack-sources "^1.0.1"
yargs "^8.0.2"
webpack@^3.1.0, webpack@^3.8.1:
version "3.9.1"
resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.9.1.tgz#9a60aa544ed5d4d454c069e3f521aa007e02643c"
dependencies:
acorn "^5.0.0"
acorn-dynamic-import "^2.0.0"
ajv "^5.1.5"
ajv-keywords "^2.0.0"
async "^2.1.2"
enhanced-resolve "^3.4.0"
escope "^3.6.0"
interpret "^1.0.0"
json-loader "^0.5.4"
json5 "^0.5.1"
loader-runner "^2.3.0"
loader-utils "^1.1.0"
memory-fs "~0.4.1"
mkdirp "~0.5.0"
node-libs-browser "^2.0.0"
source-map "^0.5.3"
supports-color "^4.2.1"
tapable "^0.2.7"
uglifyjs-webpack-plugin "^0.4.6"
watchpack "^1.4.0"
webpack-sources "^1.0.1"
yargs "^8.0.2"
websocket-driver@>=0.5.1:
version "0.7.0"
resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb"
@ -11368,6 +11355,12 @@ widest-line@^1.0.0:
dependencies:
string-width "^1.0.1"
widest-line@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.0.tgz#0142a4e8a243f8882c0233aa0e0281aa76152273"
dependencies:
string-width "^2.1.1"
window-size@0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"