feat(my-joy-beta): tags and metadata in instance creation
fixes #983 fixes #982
This commit is contained in:
parent
ca7e34c8f5
commit
ed801eba0a
@ -24,9 +24,10 @@
|
|||||||
"apr-intercept": "^1.0.4",
|
"apr-intercept": "^1.0.4",
|
||||||
"clipboard-copy": "^1.2.0",
|
"clipboard-copy": "^1.2.0",
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
"declarative-redux-form": "^1.0.4",
|
"declarative-redux-form": "^2.0.8",
|
||||||
"joyent-manifest-editor": "^1.4.0",
|
|
||||||
"joyent-ui-toolkit": "^4.0.0",
|
"joyent-ui-toolkit": "^4.0.0",
|
||||||
|
"joyent-manifest-editor": "^1.4.0",
|
||||||
|
"joyent-ui-toolkit": "^4.0.1",
|
||||||
"lodash.find": "^4.6.0",
|
"lodash.find": "^4.6.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.isstring": "^4.0.1",
|
"lodash.isstring": "^4.0.1",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`renders <Toolbar /> without throwing 1`] = `
|
exports[`renders <Title /> without throwing 1`] = `
|
||||||
Array [
|
Array [
|
||||||
.c2 {
|
.c2 {
|
||||||
color: rgba(73,73,73,1);
|
color: rgba(73,73,73,1);
|
||||||
@ -97,7 +97,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders <Toolbar icon="NameIcon"/> without throwing 1`] = `
|
exports[`renders <Title icon="NameIcon"/> without throwing 1`] = `
|
||||||
Array [
|
Array [
|
||||||
.c2 {
|
.c2 {
|
||||||
color: rgba(73,73,73,1);
|
color: rgba(73,73,73,1);
|
||||||
@ -214,7 +214,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders <Toolbar icon="Test" label="Instance name"/> without throwing 1`] = `
|
exports[`renders <Title icon="Test" label="Instance name"/> without throwing 1`] = `
|
||||||
Array [
|
Array [
|
||||||
.c2 {
|
.c2 {
|
||||||
color: rgba(73,73,73,1);
|
color: rgba(73,73,73,1);
|
||||||
@ -331,7 +331,7 @@ Array [
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`renders <Toolbar label="Test"/> without throwing 1`] = `
|
exports[`renders <Title label="Test"/> without throwing 1`] = `
|
||||||
Array [
|
Array [
|
||||||
.c2 {
|
.c2 {
|
||||||
color: rgba(73,73,73,1);
|
color: rgba(73,73,73,1);
|
||||||
|
@ -7,7 +7,7 @@ import { NameIcon } from 'joyent-ui-toolkit';
|
|||||||
import Title from '../title';
|
import Title from '../title';
|
||||||
import Theme from '@mocks/theme';
|
import Theme from '@mocks/theme';
|
||||||
|
|
||||||
it('renders <Toolbar /> without throwing', () => {
|
it('renders <Title /> without throwing', () => {
|
||||||
expect(
|
expect(
|
||||||
renderer
|
renderer
|
||||||
.create(
|
.create(
|
||||||
@ -19,7 +19,7 @@ it('renders <Toolbar /> without throwing', () => {
|
|||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders <Toolbar label="Test"/> without throwing', () => {
|
it('renders <Title label="Test"/> without throwing', () => {
|
||||||
expect(
|
expect(
|
||||||
renderer
|
renderer
|
||||||
.create(
|
.create(
|
||||||
@ -31,7 +31,7 @@ it('renders <Toolbar label="Test"/> without throwing', () => {
|
|||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders <Toolbar icon="NameIcon"/> without throwing', () => {
|
it('renders <Title icon="NameIcon"/> without throwing', () => {
|
||||||
expect(
|
expect(
|
||||||
renderer
|
renderer
|
||||||
.create(
|
.create(
|
||||||
@ -43,7 +43,7 @@ it('renders <Toolbar icon="NameIcon"/> without throwing', () => {
|
|||||||
).toMatchSnapshot();
|
).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders <Toolbar icon="Test" label="Instance name"/> without throwing', () => {
|
it('renders <Title icon="Test" label="Instance name"/> without throwing', () => {
|
||||||
expect(
|
expect(
|
||||||
renderer
|
renderer
|
||||||
.create(
|
.create(
|
||||||
|
@ -1972,11 +1972,6 @@ exports[`renders <Tag /> without throwing 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c1 {
|
.c1 {
|
||||||
border: 0.0625rem solid rgb(216,216,216);
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 0.1875rem;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
@ -1992,6 +1987,12 @@ exports[`renders <Tag /> without throwing 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c2 {
|
.c2 {
|
||||||
|
border: 0.0625rem solid rgb(216,216,216);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 0.1875rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.125rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
@ -2009,16 +2010,16 @@ exports[`renders <Tag /> without throwing 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="c0"
|
className="c0"
|
||||||
>
|
>
|
||||||
<li
|
<div
|
||||||
className="c1"
|
className="c1"
|
||||||
|
>
|
||||||
|
<li
|
||||||
|
className="c2"
|
||||||
onClick={undefined}
|
onClick={undefined}
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="c2"
|
|
||||||
>
|
>
|
||||||
:
|
:
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -2029,11 +2030,6 @@ exports[`renders <Tag name value/> without throwing 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c1 {
|
.c1 {
|
||||||
border: 0.0625rem solid rgb(216,216,216);
|
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: 0.1875rem;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
@ -2049,6 +2045,12 @@ exports[`renders <Tag name value/> without throwing 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c2 {
|
.c2 {
|
||||||
|
border: 0.0625rem solid rgb(216,216,216);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 0.1875rem;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.125rem;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
@ -2066,17 +2068,17 @@ exports[`renders <Tag name value/> without throwing 1`] = `
|
|||||||
<div
|
<div
|
||||||
className="c0"
|
className="c0"
|
||||||
>
|
>
|
||||||
<li
|
|
||||||
className="c1"
|
|
||||||
onClick={undefined}
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
|
className="c1"
|
||||||
|
>
|
||||||
|
<li
|
||||||
className="c2"
|
className="c2"
|
||||||
|
onClick={undefined}
|
||||||
>
|
>
|
||||||
name
|
name
|
||||||
:
|
:
|
||||||
value
|
value
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Margin } from 'styled-components-spacing';
|
import { Margin } from 'styled-components-spacing';
|
||||||
import { KeyValue } from '@components/instances';
|
import { KeyValue } from '@components/instances';
|
||||||
|
|
||||||
import { TagItem, TagItemContainer } from 'joyent-ui-toolkit';
|
import { TagItem } from 'joyent-ui-toolkit';
|
||||||
|
|
||||||
export const AddForm = props => (
|
export const AddForm = props => (
|
||||||
<KeyValue {...props} method="add" input="input" type="tag" expanded />
|
<KeyValue {...props} method="add" input="input" type="tag" expanded />
|
||||||
@ -12,12 +12,10 @@ export const EditForm = props => (
|
|||||||
<KeyValue {...props} method="edit" input="input" type="tag" expanded />
|
<KeyValue {...props} method="edit" input="input" type="tag" expanded />
|
||||||
);
|
);
|
||||||
|
|
||||||
export default ({ name, value, onClick }) => (
|
export default ({ name, value, onClick, onRemoveClick }) => (
|
||||||
<Margin right={1} bottom={1} key={`${name}-${value}`}>
|
<Margin right={1} bottom={1} key={`${name}-${value}`}>
|
||||||
<TagItem onClick={onClick}>
|
<TagItem onClick={onClick} onRemoveClick={onRemoveClick}>
|
||||||
<TagItemContainer>
|
|
||||||
{name}: {value}
|
{name}: {value}
|
||||||
</TagItemContainer>
|
|
||||||
</TagItem>
|
</TagItem>
|
||||||
</Margin>
|
</Margin>
|
||||||
);
|
);
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import 'jest-styled-components';
|
||||||
|
|
||||||
|
import Theme from '@mocks/theme';
|
||||||
|
import { Metadata } from '../metadata';
|
||||||
|
|
||||||
|
it('renders <Metadata /> without throwing', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Metadata />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Metadata expanded /> without throwing', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Metadata expanded />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Metadata metadata=[] /> without throwing', () => {
|
||||||
|
const metadata = [
|
||||||
|
{ name: 'hello', value: 'world' },
|
||||||
|
{ name: 'hello2', value: 'world2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Metadata metadata={metadata} />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Metadata metadata=[] expanded /> without throwing', () => {
|
||||||
|
const metadata = [
|
||||||
|
{ name: 'hello', value: 'world' },
|
||||||
|
{ name: 'hello2', value: 'world2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Metadata metadata={metadata} expanded />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Metadata addOpen expanded /> without throwing', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Metadata addOpen expanded />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
@ -0,0 +1,76 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import renderer from 'react-test-renderer';
|
||||||
|
import 'jest-styled-components';
|
||||||
|
|
||||||
|
import Theme from '@mocks/theme';
|
||||||
|
import { Tags } from '../tags';
|
||||||
|
|
||||||
|
it('renders <Tags /> without throwing', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Tags />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Tags expanded /> without throwing', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Tags expanded />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Tags metadata=[] /> without throwing', () => {
|
||||||
|
const metadata = [
|
||||||
|
{ name: 'hello', value: 'world' },
|
||||||
|
{ name: 'hello2', value: 'world2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Tags metadata={metadata} />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Tags metadata=[] expanded /> without throwing', () => {
|
||||||
|
const metadata = [
|
||||||
|
{ name: 'hello', value: 'world' },
|
||||||
|
{ name: 'hello2', value: 'world2' }
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Tags metadata={metadata} expanded />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders <Tags addOpen expanded /> without throwing', () => {
|
||||||
|
expect(
|
||||||
|
renderer
|
||||||
|
.create(
|
||||||
|
<Theme>
|
||||||
|
<Tags addOpen expanded />
|
||||||
|
</Theme>
|
||||||
|
)
|
||||||
|
.toJSON()
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
@ -1,5 +1,4 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
import { compose, graphql } from 'react-apollo';
|
import { compose, graphql } from 'react-apollo';
|
||||||
import ReduxForm from 'declarative-redux-form';
|
import ReduxForm from 'declarative-redux-form';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@ -45,7 +44,6 @@ const ImageContainer = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
|
||||||
connect(
|
connect(
|
||||||
(state, ownProps) => {
|
(state, ownProps) => {
|
||||||
return {
|
return {
|
||||||
|
@ -5,17 +5,25 @@ import { ViewContainer, H2 } from 'joyent-ui-toolkit';
|
|||||||
|
|
||||||
import Name from '@containers/create-instance/name';
|
import Name from '@containers/create-instance/name';
|
||||||
import Image from '@containers/create-instance/image';
|
import Image from '@containers/create-instance/image';
|
||||||
|
import Metadata from '@containers/create-instance/metadata';
|
||||||
|
import Tags from '@containers/create-instance/tags';
|
||||||
|
|
||||||
export default ({ step }) => (
|
export default ({ step, ...props }) => (
|
||||||
<ViewContainer>
|
<ViewContainer>
|
||||||
<Margin top={4} bottom={4}>
|
<Margin top={4} bottom={4}>
|
||||||
<H2>Create Instances</H2>
|
<H2>Create Instances</H2>
|
||||||
</Margin>
|
</Margin>
|
||||||
<Margin bottom={4}>
|
<Margin bottom={4}>
|
||||||
<Name expanded={step === 'name'} />
|
<Name {...props} expanded={step === 'name'} />
|
||||||
</Margin>
|
</Margin>
|
||||||
<Margin bottom={4}>
|
<Margin bottom={4}>
|
||||||
<Image expanded={step === 'image'} />
|
<Image {...props} expanded={step === 'image'} />
|
||||||
|
</Margin>
|
||||||
|
<Margin bottom={4}>
|
||||||
|
<Tags {...props} expanded={step === 'tags'} />
|
||||||
|
</Margin>
|
||||||
|
<Margin bottom={4}>
|
||||||
|
<Metadata {...props} expanded={step === 'metadata'} />
|
||||||
</Margin>
|
</Margin>
|
||||||
</ViewContainer>
|
</ViewContainer>
|
||||||
);
|
);
|
||||||
|
198
packages/my-joy-beta/src/containers/create-instance/metadata.js
Normal file
198
packages/my-joy-beta/src/containers/create-instance/metadata.js
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { set } from 'react-redux-values';
|
||||||
|
import { Margin } from 'styled-components-spacing';
|
||||||
|
import { compose } from 'react-apollo';
|
||||||
|
import { destroy, reset } from 'redux-form';
|
||||||
|
import ReduxForm from 'declarative-redux-form';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
|
||||||
|
import { MetadataIcon, P, Button, H3 } from 'joyent-ui-toolkit';
|
||||||
|
|
||||||
|
import Title from '@components/create-instance/title';
|
||||||
|
import KeyValue from '@components/instances/key-value';
|
||||||
|
|
||||||
|
const FORM_NAME_CREATE = 'CREATE-INSTANCE-METADATA-ADD';
|
||||||
|
const FORM_NAME_EDIT = i => `CREATE-INSTANCE-METADATA-EDIT-${i}`;
|
||||||
|
|
||||||
|
export const Metadata = ({
|
||||||
|
metadata = [],
|
||||||
|
expanded,
|
||||||
|
addOpen,
|
||||||
|
handleAddMetadata,
|
||||||
|
handleRemoveMetadata,
|
||||||
|
handleUpdateMetadata,
|
||||||
|
handleToggleExpanded,
|
||||||
|
handleCancelEdit,
|
||||||
|
handleChangeAddOpen,
|
||||||
|
handleNext,
|
||||||
|
handleEdit
|
||||||
|
}) => (
|
||||||
|
<Fragment>
|
||||||
|
<Title icon={<MetadataIcon />}>Metadata</Title>
|
||||||
|
{expanded ? (
|
||||||
|
<Margin bottom={3}>
|
||||||
|
<P>
|
||||||
|
Metadata can be used to pass data to the instance. It can also be used
|
||||||
|
to inject a custom boot script. Unlike tags, metadata is only viewable
|
||||||
|
inside the instance.{' '}
|
||||||
|
<a
|
||||||
|
target="__blank"
|
||||||
|
href="https://docs.joyent.com/public-cloud/tags-metadata/metadata"
|
||||||
|
>
|
||||||
|
Read the docs
|
||||||
|
</a>
|
||||||
|
</P>
|
||||||
|
</Margin>
|
||||||
|
) : null}
|
||||||
|
<Margin bottom={4}>
|
||||||
|
<H3>
|
||||||
|
{metadata.length} key:value pair{metadata.length === 1 ? '' : 's'}
|
||||||
|
</H3>
|
||||||
|
</Margin>
|
||||||
|
{metadata.map(({ name, value, expanded }, index) => (
|
||||||
|
<ReduxForm
|
||||||
|
form={FORM_NAME_EDIT(index)}
|
||||||
|
key={index}
|
||||||
|
initialValues={{ name, value }}
|
||||||
|
destroyOnUnmount={false}
|
||||||
|
forceUnregisterOnUnmount={true}
|
||||||
|
onSubmit={newValue => handleUpdateMetadata(index, newValue)}
|
||||||
|
>
|
||||||
|
{props => (
|
||||||
|
<KeyValue
|
||||||
|
{...props}
|
||||||
|
initialValues={{ name, value }}
|
||||||
|
expanded={expanded}
|
||||||
|
method="edit"
|
||||||
|
input="textarea"
|
||||||
|
type="metadata"
|
||||||
|
onToggleExpanded={() => handleToggleExpanded(index)}
|
||||||
|
onCancel={() => handleCancelEdit(index)}
|
||||||
|
onRemove={() => handleRemoveMetadata(index)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ReduxForm>
|
||||||
|
))}
|
||||||
|
{expanded && addOpen ? (
|
||||||
|
<ReduxForm
|
||||||
|
form={FORM_NAME_CREATE}
|
||||||
|
destroyOnUnmount={false}
|
||||||
|
forceUnregisterOnUnmount={true}
|
||||||
|
onSubmit={handleAddMetadata}
|
||||||
|
>
|
||||||
|
{props => (
|
||||||
|
<KeyValue
|
||||||
|
{...props}
|
||||||
|
method="add"
|
||||||
|
input="textarea"
|
||||||
|
type="metadata"
|
||||||
|
expanded
|
||||||
|
onCancel={() => handleChangeAddOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ReduxForm>
|
||||||
|
) : null}
|
||||||
|
<div>
|
||||||
|
{expanded ? (
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleChangeAddOpen(true)}
|
||||||
|
secondary
|
||||||
|
>
|
||||||
|
Add Metadata
|
||||||
|
</Button>
|
||||||
|
<Button type="button" onClick={handleNext}>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</Fragment>
|
||||||
|
) : (
|
||||||
|
<Button type="button" onClick={handleEdit} secondary>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
connect(({ values }, ownProps) => ({
|
||||||
|
addOpen: get(values, 'create-instance-metadata-add-open', false),
|
||||||
|
metadata: get(values, 'create-instance-metadata', [])
|
||||||
|
})),
|
||||||
|
connect(null, (dispatch, { metadata = [], history }) => ({
|
||||||
|
handleNext: () => {
|
||||||
|
return history.push(`/instances/~create/networks`);
|
||||||
|
},
|
||||||
|
handleEdit: () => {
|
||||||
|
return history.push(`/instances/~create/metadata`);
|
||||||
|
},
|
||||||
|
handleAddMetadata: value => {
|
||||||
|
const toggleToClosed = set({
|
||||||
|
name: `create-instance-metadata-add-open`,
|
||||||
|
value: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const appendMetadata = set({
|
||||||
|
name: `create-instance-metadata`,
|
||||||
|
value: metadata.concat([{ ...value, expanded: false }])
|
||||||
|
});
|
||||||
|
|
||||||
|
return dispatch([
|
||||||
|
destroy(FORM_NAME_CREATE),
|
||||||
|
toggleToClosed,
|
||||||
|
appendMetadata
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
handleUpdateMetadata: (index, newMetadata) => {
|
||||||
|
metadata[index] = {
|
||||||
|
...newMetadata,
|
||||||
|
expanded: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch([
|
||||||
|
destroy(FORM_NAME_EDIT(index)),
|
||||||
|
set({ name: `create-instance-metadata`, value: metadata.slice() })
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
handleChangeAddOpen: value => {
|
||||||
|
return dispatch([
|
||||||
|
reset(FORM_NAME_CREATE),
|
||||||
|
set({ name: `create-instance-metadata-add-open`, value })
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
handleToggleExpanded: index => {
|
||||||
|
metadata[index] = {
|
||||||
|
...metadata[index],
|
||||||
|
expanded: !metadata[index].expanded
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch(
|
||||||
|
set({
|
||||||
|
name: `create-instance-metadata`,
|
||||||
|
value: metadata.slice()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleCancelEdit: index => {
|
||||||
|
metadata[index] = {
|
||||||
|
...metadata[index],
|
||||||
|
expanded: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch([
|
||||||
|
reset(FORM_NAME_EDIT(index)),
|
||||||
|
set({ name: `create-instance-metadata`, value: metadata.slice() })
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
handleRemoveMetadata: index => {
|
||||||
|
metadata.splice(index, 1);
|
||||||
|
|
||||||
|
return dispatch([
|
||||||
|
destroy(FORM_NAME_EDIT(index)),
|
||||||
|
set({ name: `create-instance-metadata`, value: metadata.slice() })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
)(Metadata);
|
@ -1,5 +1,4 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import { withRouter } from 'react-router';
|
|
||||||
import { compose } from 'react-apollo';
|
import { compose } from 'react-apollo';
|
||||||
import ReduxForm from 'declarative-redux-form';
|
import ReduxForm from 'declarative-redux-form';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
@ -32,7 +31,6 @@ const NameContainer = ({ expanded, name, handleSubmit, handleCancel }) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
withRouter,
|
|
||||||
connect(
|
connect(
|
||||||
(state, ownProps) => ({
|
(state, ownProps) => ({
|
||||||
...ownProps,
|
...ownProps,
|
||||||
|
180
packages/my-joy-beta/src/containers/create-instance/tags.js
Normal file
180
packages/my-joy-beta/src/containers/create-instance/tags.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import { set } from 'react-redux-values';
|
||||||
|
import { Margin } from 'styled-components-spacing';
|
||||||
|
import { compose } from 'react-apollo';
|
||||||
|
import { destroy, reset } from 'redux-form';
|
||||||
|
import ReduxForm from 'declarative-redux-form';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
|
||||||
|
import { TagsIcon, P, Button, H3, TagList } from 'joyent-ui-toolkit';
|
||||||
|
|
||||||
|
import Title from '@components/create-instance/title';
|
||||||
|
import Tag from '@components/instances/tags';
|
||||||
|
import KeyValue from '@components/instances/key-value';
|
||||||
|
|
||||||
|
const FORM_NAME_CREATE = 'CREATE-INSTANCE-TAGS-ADD';
|
||||||
|
const FORM_NAME_EDIT = i => `CREATE-INSTANCE-TAGS-EDIT-${i}`;
|
||||||
|
|
||||||
|
export const Tags = ({
|
||||||
|
tags = [],
|
||||||
|
expanded,
|
||||||
|
addOpen,
|
||||||
|
handleAddTag,
|
||||||
|
handleRemoveTag,
|
||||||
|
handleUpdateTag,
|
||||||
|
handleToggleExpanded,
|
||||||
|
handleCancelEdit,
|
||||||
|
handleChangeAddOpen,
|
||||||
|
handleNext,
|
||||||
|
handleEdit
|
||||||
|
}) => (
|
||||||
|
<Fragment>
|
||||||
|
<Title icon={<TagsIcon />}>Tags</Title>
|
||||||
|
{expanded ? (
|
||||||
|
<Margin bottom={3}>
|
||||||
|
<P>
|
||||||
|
Tags can be used to identify your instances, group multiple instances
|
||||||
|
together, define firewall and affinity rules, and more.{' '}
|
||||||
|
<a
|
||||||
|
target="__blank"
|
||||||
|
href="https://docs.joyent.com/public-cloud/tags-metadata/tags"
|
||||||
|
>
|
||||||
|
Read the docs
|
||||||
|
</a>
|
||||||
|
</P>
|
||||||
|
</Margin>
|
||||||
|
) : null}
|
||||||
|
<Margin bottom={4}>
|
||||||
|
<H3>
|
||||||
|
{tags.length} Tag{tags.length === 1 ? '' : 's'}
|
||||||
|
</H3>
|
||||||
|
</Margin>
|
||||||
|
<TagList>
|
||||||
|
{tags.map(({ name, value }, index) => (
|
||||||
|
<Tag
|
||||||
|
key={index}
|
||||||
|
name={name}
|
||||||
|
value={value}
|
||||||
|
onRemoveClick={() => handleRemoveTag(index)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TagList>
|
||||||
|
{expanded && addOpen ? (
|
||||||
|
<ReduxForm
|
||||||
|
form={FORM_NAME_CREATE}
|
||||||
|
destroyOnUnmount={false}
|
||||||
|
forceUnregisterOnUnmount={true}
|
||||||
|
onSubmit={handleAddTag}
|
||||||
|
>
|
||||||
|
{props => (
|
||||||
|
<KeyValue
|
||||||
|
{...props}
|
||||||
|
method="add"
|
||||||
|
input="input"
|
||||||
|
type="tag"
|
||||||
|
expanded
|
||||||
|
onCancel={() => handleChangeAddOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ReduxForm>
|
||||||
|
) : null}
|
||||||
|
<div>
|
||||||
|
{expanded ? (
|
||||||
|
<Fragment>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
onClick={() => handleChangeAddOpen(true)}
|
||||||
|
secondary
|
||||||
|
>
|
||||||
|
Add Tag
|
||||||
|
</Button>
|
||||||
|
<Button type="button" onClick={handleNext}>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</Fragment>
|
||||||
|
) : (
|
||||||
|
<Button type="button" onClick={handleEdit} secondary>
|
||||||
|
Edit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
connect(({ values }, ownProps) => ({
|
||||||
|
addOpen: get(values, 'create-instance-tags-add-open', false),
|
||||||
|
tags: get(values, 'create-instance-tags', [])
|
||||||
|
})),
|
||||||
|
connect(null, (dispatch, { tags = [], history }) => ({
|
||||||
|
handleNext: () => {
|
||||||
|
return history.push(`/instances/~create/metadata`);
|
||||||
|
},
|
||||||
|
handleEdit: () => {
|
||||||
|
return history.push(`/instances/~create/tags`);
|
||||||
|
},
|
||||||
|
handleAddTag: value => {
|
||||||
|
const toggleToClosed = set({
|
||||||
|
name: `create-instance-tags-add-open`,
|
||||||
|
value: false
|
||||||
|
});
|
||||||
|
|
||||||
|
const appendTag = set({
|
||||||
|
name: `create-instance-tags`,
|
||||||
|
value: tags.concat([{ ...value, expanded: false }])
|
||||||
|
});
|
||||||
|
|
||||||
|
return dispatch([destroy(FORM_NAME_CREATE), toggleToClosed, appendTag]);
|
||||||
|
},
|
||||||
|
handleUpdateTag: (index, newTag) => {
|
||||||
|
tags[index] = {
|
||||||
|
...newTag,
|
||||||
|
expanded: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch([
|
||||||
|
destroy(FORM_NAME_EDIT(index)),
|
||||||
|
set({ name: `create-instance-tags`, value: tags.slice() })
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
handleChangeAddOpen: value => {
|
||||||
|
return dispatch([
|
||||||
|
reset(FORM_NAME_CREATE),
|
||||||
|
set({ name: `create-instance-tags-add-open`, value })
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
handleToggleExpanded: index => {
|
||||||
|
tags[index] = {
|
||||||
|
...tags[index],
|
||||||
|
expanded: !tags[index].expanded
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch(
|
||||||
|
set({
|
||||||
|
name: `create-instance-tags`,
|
||||||
|
value: tags.slice()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
handleCancelEdit: index => {
|
||||||
|
tags[index] = {
|
||||||
|
...tags[index],
|
||||||
|
expanded: false
|
||||||
|
};
|
||||||
|
|
||||||
|
return dispatch([
|
||||||
|
reset(FORM_NAME_EDIT(index)),
|
||||||
|
set({ name: `create-instance-tags`, value: tags.slice() })
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
handleRemoveTag: index => {
|
||||||
|
tags.splice(index, 1);
|
||||||
|
|
||||||
|
return dispatch([
|
||||||
|
destroy(FORM_NAME_EDIT(index)),
|
||||||
|
set({ name: `create-instance-tags`, value: tags.slice() })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
)(Tags);
|
@ -6786,10 +6786,26 @@ exports[`renders <Tags tags /> without throwing 1`] = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.c19 {
|
.c19 {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: -webkit-flex;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-webkit-align-items: center;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
-webkit-box-flex: 1;
|
||||||
|
-webkit-flex-grow: 1;
|
||||||
|
-ms-flex-positive: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.c20 {
|
||||||
border: 0.0625rem solid rgb(216,216,216);
|
border: 0.0625rem solid rgb(216,216,216);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: 0.1875rem;
|
border-radius: 0.1875rem;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.125rem;
|
||||||
padding: 0.375rem 0.75rem;
|
padding: 0.375rem 0.75rem;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
@ -6818,21 +6834,6 @@ exports[`renders <Tags tags /> without throwing 1`] = `
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c20 {
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-webkit-flex-grow: 1;
|
|
||||||
-ms-flex-positive: 1;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (min-width:0em) {
|
@media only screen and (min-width:0em) {
|
||||||
.c2 {
|
.c2 {
|
||||||
-webkit-flex-basis: 58.333333333333336%;
|
-webkit-flex-basis: 58.333333333333336%;
|
||||||
@ -6986,52 +6987,52 @@ exports[`renders <Tags tags /> without throwing 1`] = `
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="c18"
|
className="c18"
|
||||||
>
|
|
||||||
<li
|
|
||||||
className="c19"
|
|
||||||
onClick={undefined}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
className="c19"
|
||||||
|
>
|
||||||
|
<li
|
||||||
className="c20"
|
className="c20"
|
||||||
|
onClick={undefined}
|
||||||
>
|
>
|
||||||
name1
|
name1
|
||||||
:
|
:
|
||||||
value1
|
value1
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="c18"
|
className="c18"
|
||||||
>
|
|
||||||
<li
|
|
||||||
className="c19"
|
|
||||||
onClick={undefined}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
className="c19"
|
||||||
|
>
|
||||||
|
<li
|
||||||
className="c20"
|
className="c20"
|
||||||
|
onClick={undefined}
|
||||||
>
|
>
|
||||||
name2
|
name2
|
||||||
:
|
:
|
||||||
value2
|
value2
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className="c18"
|
className="c18"
|
||||||
>
|
|
||||||
<li
|
|
||||||
className="c19"
|
|
||||||
onClick={undefined}
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
|
className="c19"
|
||||||
|
>
|
||||||
|
<li
|
||||||
className="c20"
|
className="c20"
|
||||||
|
onClick={undefined}
|
||||||
>
|
>
|
||||||
name3
|
name3
|
||||||
:
|
:
|
||||||
value3
|
value3
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
@ -338,7 +338,9 @@ export default compose(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// reverts the individual item mutation flags
|
// reverts the individual item mutation flags
|
||||||
const setMutatingFalse = name !== 'remove' && selected.map(({ id }) =>
|
const setMutatingFalse =
|
||||||
|
name !== 'remove' &&
|
||||||
|
selected.map(({ id }) =>
|
||||||
set({ name: `${id}-mutating`, value: false })
|
set({ name: `${id}-mutating`, value: false })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -29,6 +29,11 @@ export default () => (
|
|||||||
|
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<Switch>
|
<Switch>
|
||||||
|
<Route
|
||||||
|
path="/instances/~create/:section?"
|
||||||
|
exact
|
||||||
|
component={Breadcrumb}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/instances/~:action/:instance?"
|
path="/instances/~:action/:instance?"
|
||||||
exact
|
exact
|
||||||
@ -99,7 +104,7 @@ export default () => (
|
|||||||
<Route
|
<Route
|
||||||
path="/instances/~create/"
|
path="/instances/~create/"
|
||||||
exact
|
exact
|
||||||
component={({ match }) => <Redirect to="/instances/~create/name" />}
|
component={() => <Redirect to="/instances/~create/name" />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/instances/~create/name"
|
path="/instances/~create/name"
|
||||||
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Subscriber } from 'joy-react-broadcast';
|
import { Subscriber } from 'joy-react-broadcast';
|
||||||
import remcalc from 'remcalc';
|
import remcalc from 'remcalc';
|
||||||
import rndId from 'rnd-id';
|
|
||||||
import isUndefined from 'lodash.isundefined';
|
import isUndefined from 'lodash.isundefined';
|
||||||
import Label from './label';
|
import Label from './label';
|
||||||
import is from 'styled-is';
|
import is from 'styled-is';
|
||||||
|
@ -65,7 +65,7 @@ export {
|
|||||||
Anchor as SectionListAnchor
|
Anchor as SectionListAnchor
|
||||||
} from './section-list';
|
} from './section-list';
|
||||||
|
|
||||||
export { TagItem, TagList, TagItemContainer } from './tags';
|
export { TagItem, TagList } from './tags';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
Actions as ActionsIcon,
|
Actions as ActionsIcon,
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
import styled from 'styled-components';
|
|
||||||
|
|
||||||
export default styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
flex-grow: 1;
|
|
||||||
`;
|
|
@ -1,3 +1,2 @@
|
|||||||
export { default as TagItem } from './item';
|
export { default as TagItem } from './item';
|
||||||
export { default as TagList } from './list';
|
export { default as TagList } from './list';
|
||||||
export { default as TagItemContainer } from './container';
|
|
||||||
|
@ -1,12 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import remcalc from 'remcalc';
|
import remcalc from 'remcalc';
|
||||||
import is from 'styled-is';
|
import is from 'styled-is';
|
||||||
|
|
||||||
export default styled.li`
|
import { Close } from '../icons';
|
||||||
|
|
||||||
|
const CloseIcon = Close.extend`
|
||||||
|
margin-left: ${remcalc(12)};
|
||||||
|
|
||||||
|
${is('onClick')`
|
||||||
|
cursor: pointer;
|
||||||
|
`};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Container = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-grow: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Tag = styled.li`
|
||||||
border: ${remcalc(1)} solid ${props => props.theme.grey};
|
border: ${remcalc(1)} solid ${props => props.theme.grey};
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-radius: ${remcalc(3)};
|
border-radius: ${remcalc(3)};
|
||||||
font-size: ${remcalc(13)};
|
font-size: ${remcalc(13)};
|
||||||
|
line-height: ${remcalc(18)};
|
||||||
padding: ${remcalc(6)} ${remcalc(12)};
|
padding: ${remcalc(6)} ${remcalc(12)};
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -16,3 +34,12 @@ export default styled.li`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
`};
|
`};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export default ({ children, onRemoveClick, ...rest }) => (
|
||||||
|
<Container>
|
||||||
|
<Tag {...rest}>
|
||||||
|
{children}
|
||||||
|
{onRemoveClick ? <CloseIcon disabled onClick={onRemoveClick} /> : null}
|
||||||
|
</Tag>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user