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",
|
||||
"clipboard-copy": "^1.2.0",
|
||||
"date-fns": "^1.29.0",
|
||||
"declarative-redux-form": "^1.0.4",
|
||||
"joyent-manifest-editor": "^1.4.0",
|
||||
"declarative-redux-form": "^2.0.8",
|
||||
"joyent-ui-toolkit": "^4.0.0",
|
||||
"joyent-manifest-editor": "^1.4.0",
|
||||
"joyent-ui-toolkit": "^4.0.1",
|
||||
"lodash.find": "^4.6.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isstring": "^4.0.1",
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders <Toolbar /> without throwing 1`] = `
|
||||
exports[`renders <Title /> without throwing 1`] = `
|
||||
Array [
|
||||
.c2 {
|
||||
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 [
|
||||
.c2 {
|
||||
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 [
|
||||
.c2 {
|
||||
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 [
|
||||
.c2 {
|
||||
color: rgba(73,73,73,1);
|
||||
|
@ -7,7 +7,7 @@ import { NameIcon } from 'joyent-ui-toolkit';
|
||||
import Title from '../title';
|
||||
import Theme from '@mocks/theme';
|
||||
|
||||
it('renders <Toolbar /> without throwing', () => {
|
||||
it('renders <Title /> without throwing', () => {
|
||||
expect(
|
||||
renderer
|
||||
.create(
|
||||
@ -19,7 +19,7 @@ it('renders <Toolbar /> without throwing', () => {
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders <Toolbar label="Test"/> without throwing', () => {
|
||||
it('renders <Title label="Test"/> without throwing', () => {
|
||||
expect(
|
||||
renderer
|
||||
.create(
|
||||
@ -31,7 +31,7 @@ it('renders <Toolbar label="Test"/> without throwing', () => {
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders <Toolbar icon="NameIcon"/> without throwing', () => {
|
||||
it('renders <Title icon="NameIcon"/> without throwing', () => {
|
||||
expect(
|
||||
renderer
|
||||
.create(
|
||||
@ -43,7 +43,7 @@ it('renders <Toolbar icon="NameIcon"/> without throwing', () => {
|
||||
).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders <Toolbar icon="Test" label="Instance name"/> without throwing', () => {
|
||||
it('renders <Title icon="Test" label="Instance name"/> without throwing', () => {
|
||||
expect(
|
||||
renderer
|
||||
.create(
|
||||
|
@ -1972,11 +1972,6 @@ exports[`renders <Tag /> without throwing 1`] = `
|
||||
}
|
||||
|
||||
.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-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -1992,6 +1987,12 @@ exports[`renders <Tag /> without throwing 1`] = `
|
||||
}
|
||||
|
||||
.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-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -2009,16 +2010,16 @@ exports[`renders <Tag /> without throwing 1`] = `
|
||||
<div
|
||||
className="c0"
|
||||
>
|
||||
<li
|
||||
<div
|
||||
className="c1"
|
||||
>
|
||||
<li
|
||||
className="c2"
|
||||
onClick={undefined}
|
||||
>
|
||||
<div
|
||||
className="c2"
|
||||
>
|
||||
:
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@ -2029,11 +2030,6 @@ exports[`renders <Tag name value/> without throwing 1`] = `
|
||||
}
|
||||
|
||||
.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-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -2049,6 +2045,12 @@ exports[`renders <Tag name value/> without throwing 1`] = `
|
||||
}
|
||||
|
||||
.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-flex;
|
||||
display: -ms-flexbox;
|
||||
@ -2066,17 +2068,17 @@ exports[`renders <Tag name value/> without throwing 1`] = `
|
||||
<div
|
||||
className="c0"
|
||||
>
|
||||
<li
|
||||
className="c1"
|
||||
onClick={undefined}
|
||||
>
|
||||
<div
|
||||
className="c1"
|
||||
>
|
||||
<li
|
||||
className="c2"
|
||||
onClick={undefined}
|
||||
>
|
||||
name
|
||||
:
|
||||
value
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import { Margin } from 'styled-components-spacing';
|
||||
import { KeyValue } from '@components/instances';
|
||||
|
||||
import { TagItem, TagItemContainer } from 'joyent-ui-toolkit';
|
||||
import { TagItem } from 'joyent-ui-toolkit';
|
||||
|
||||
export const AddForm = props => (
|
||||
<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 />
|
||||
);
|
||||
|
||||
export default ({ name, value, onClick }) => (
|
||||
export default ({ name, value, onClick, onRemoveClick }) => (
|
||||
<Margin right={1} bottom={1} key={`${name}-${value}`}>
|
||||
<TagItem onClick={onClick}>
|
||||
<TagItemContainer>
|
||||
<TagItem onClick={onClick} onRemoveClick={onRemoveClick}>
|
||||
{name}: {value}
|
||||
</TagItemContainer>
|
||||
</TagItem>
|
||||
</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 { withRouter } from 'react-router';
|
||||
import { compose, graphql } from 'react-apollo';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { connect } from 'react-redux';
|
||||
@ -45,7 +44,6 @@ const ImageContainer = ({
|
||||
);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(
|
||||
(state, ownProps) => {
|
||||
return {
|
||||
|
@ -5,17 +5,25 @@ import { ViewContainer, H2 } from 'joyent-ui-toolkit';
|
||||
|
||||
import Name from '@containers/create-instance/name';
|
||||
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>
|
||||
<Margin top={4} bottom={4}>
|
||||
<H2>Create Instances</H2>
|
||||
</Margin>
|
||||
<Margin bottom={4}>
|
||||
<Name expanded={step === 'name'} />
|
||||
<Name {...props} expanded={step === 'name'} />
|
||||
</Margin>
|
||||
<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>
|
||||
</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 { withRouter } from 'react-router';
|
||||
import { compose } from 'react-apollo';
|
||||
import ReduxForm from 'declarative-redux-form';
|
||||
import { connect } from 'react-redux';
|
||||
@ -32,7 +31,6 @@ const NameContainer = ({ expanded, name, handleSubmit, handleCancel }) => (
|
||||
);
|
||||
|
||||
export default compose(
|
||||
withRouter,
|
||||
connect(
|
||||
(state, 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 {
|
||||
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);
|
||||
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-flex;
|
||||
@ -6818,21 +6834,6 @@ exports[`renders <Tags tags /> without throwing 1`] = `
|
||||
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) {
|
||||
.c2 {
|
||||
-webkit-flex-basis: 58.333333333333336%;
|
||||
@ -6986,52 +6987,52 @@ exports[`renders <Tags tags /> without throwing 1`] = `
|
||||
>
|
||||
<div
|
||||
className="c18"
|
||||
>
|
||||
<li
|
||||
className="c19"
|
||||
onClick={undefined}
|
||||
>
|
||||
<div
|
||||
className="c19"
|
||||
>
|
||||
<li
|
||||
className="c20"
|
||||
onClick={undefined}
|
||||
>
|
||||
name1
|
||||
:
|
||||
value1
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="c18"
|
||||
>
|
||||
<li
|
||||
className="c19"
|
||||
onClick={undefined}
|
||||
>
|
||||
<div
|
||||
className="c19"
|
||||
>
|
||||
<li
|
||||
className="c20"
|
||||
onClick={undefined}
|
||||
>
|
||||
name2
|
||||
:
|
||||
value2
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="c18"
|
||||
>
|
||||
<li
|
||||
className="c19"
|
||||
onClick={undefined}
|
||||
>
|
||||
<div
|
||||
className="c19"
|
||||
>
|
||||
<li
|
||||
className="c20"
|
||||
onClick={undefined}
|
||||
>
|
||||
name3
|
||||
:
|
||||
value3
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
@ -338,7 +338,9 @@ export default compose(
|
||||
});
|
||||
|
||||
// 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 })
|
||||
);
|
||||
|
||||
|
@ -29,6 +29,11 @@ export default () => (
|
||||
|
||||
{/* Breadcrumb */}
|
||||
<Switch>
|
||||
<Route
|
||||
path="/instances/~create/:section?"
|
||||
exact
|
||||
component={Breadcrumb}
|
||||
/>
|
||||
<Route
|
||||
path="/instances/~:action/:instance?"
|
||||
exact
|
||||
@ -99,7 +104,7 @@ export default () => (
|
||||
<Route
|
||||
path="/instances/~create/"
|
||||
exact
|
||||
component={({ match }) => <Redirect to="/instances/~create/name" />}
|
||||
component={() => <Redirect to="/instances/~create/name" />}
|
||||
/>
|
||||
<Route
|
||||
path="/instances/~create/name"
|
||||
|
@ -2,7 +2,6 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Subscriber } from 'joy-react-broadcast';
|
||||
import remcalc from 'remcalc';
|
||||
import rndId from 'rnd-id';
|
||||
import isUndefined from 'lodash.isundefined';
|
||||
import Label from './label';
|
||||
import is from 'styled-is';
|
||||
|
@ -65,7 +65,7 @@ export {
|
||||
Anchor as SectionListAnchor
|
||||
} from './section-list';
|
||||
|
||||
export { TagItem, TagList, TagItemContainer } from './tags';
|
||||
export { TagItem, TagList } from './tags';
|
||||
|
||||
export {
|
||||
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 TagList } from './list';
|
||||
export { default as TagItemContainer } from './container';
|
||||
|
@ -1,12 +1,30 @@
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import remcalc from 'remcalc';
|
||||
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};
|
||||
box-sizing: border-box;
|
||||
border-radius: ${remcalc(3)};
|
||||
font-size: ${remcalc(13)};
|
||||
line-height: ${remcalc(18)};
|
||||
padding: ${remcalc(6)} ${remcalc(12)};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -16,3 +34,12 @@ export default styled.li`
|
||||
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