feat(images): mutate tags
This commit is contained in:
parent
eae5345e62
commit
bf9a85e4fd
@ -32,7 +32,7 @@ export const Toolbar = ({
|
|||||||
<FormGroup right>
|
<FormGroup right>
|
||||||
<Divider height={remcalc(21)} transparent />
|
<Divider height={remcalc(21)} transparent />
|
||||||
<Button
|
<Button
|
||||||
type={onActionClick ? 'button' : 'submit'}
|
type="button"
|
||||||
disabled={!actionable}
|
disabled={!actionable}
|
||||||
onClick={onActionClick}
|
onClick={onActionClick}
|
||||||
icon
|
icon
|
||||||
|
@ -29,5 +29,9 @@ export const Forms = {
|
|||||||
FORM_TAGS_EDIT: i => `CREATE-IMAGE-TAGS-EDIT-${i}`,
|
FORM_TAGS_EDIT: i => `CREATE-IMAGE-TAGS-EDIT-${i}`,
|
||||||
FORM_DETAILS: 'CREATE-IMAGE-DETAILS',
|
FORM_DETAILS: 'CREATE-IMAGE-DETAILS',
|
||||||
CREATE_FORM: 'CREATE-IMAGE',
|
CREATE_FORM: 'CREATE-IMAGE',
|
||||||
CREATE_TAGS: 'CREATE-IMAGE-TAGS'
|
CREATE_TAGS: 'CREATE-IMAGE-TAGS',
|
||||||
|
LIST_TOGGLE_TYPE_FORM: 'LIST-TOGGLE-TYPE-FORM',
|
||||||
|
LIST_TOOLBAR_FORM: 'LIST-TOOLBAR-FORM',
|
||||||
|
TAGS_TOOLBAR_FORM: 'TAGS-TOOLBAR-FORM',
|
||||||
|
TAGS_ADD_FORM: 'TAGS-ADD-FORM'
|
||||||
};
|
};
|
||||||
|
@ -22,14 +22,13 @@ import {
|
|||||||
|
|
||||||
import ToolbarForm from '@components/toolbar';
|
import ToolbarForm from '@components/toolbar';
|
||||||
import Empty from '@components/empty';
|
import Empty from '@components/empty';
|
||||||
import { ImageType } from '@root/constants';
|
import { ImageType, Forms } from '@root/constants';
|
||||||
import ListImages from '@graphql/list-images.gql';
|
import ListImages from '@graphql/list-images.gql';
|
||||||
import { Image, Filters } from '@components/image';
|
import { Image, Filters } from '@components/image';
|
||||||
import RemoveImage from '@graphql/remove-image.gql';
|
import RemoveImage from '@graphql/remove-image.gql';
|
||||||
import parseError from '@state/parse-error';
|
import parseError from '@state/parse-error';
|
||||||
|
|
||||||
const TOGGLE_FORM_DETAILS = 'images-list-toggle';
|
const { LIST_TOOLBAR_FORM, LIST_TOGGLE_TYPE_FORM } = Forms;
|
||||||
const MENU_FORM_DETAILS = 'images-list-menu';
|
|
||||||
|
|
||||||
export const List = ({
|
export const List = ({
|
||||||
images = [],
|
images = [],
|
||||||
@ -42,7 +41,7 @@ export const List = ({
|
|||||||
}) => (
|
}) => (
|
||||||
<ViewContainer main>
|
<ViewContainer main>
|
||||||
<Divider height={remcalc(30)} transparent />
|
<Divider height={remcalc(30)} transparent />
|
||||||
<ReduxForm form={MENU_FORM_DETAILS}>
|
<ReduxForm form={LIST_TOOLBAR_FORM}>
|
||||||
{props => <ToolbarForm {...props} actionable={!loading} />}
|
{props => <ToolbarForm {...props} actionable={!loading} />}
|
||||||
</ReduxForm>
|
</ReduxForm>
|
||||||
<Divider height={remcalc(1)} />
|
<Divider height={remcalc(1)} />
|
||||||
@ -66,7 +65,7 @@ export const List = ({
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<Margin bottom={4}>
|
<Margin bottom={4}>
|
||||||
<ReduxForm
|
<ReduxForm
|
||||||
form={TOGGLE_FORM_DETAILS}
|
form={LIST_TOGGLE_TYPE_FORM}
|
||||||
initialValues={{ 'image-type': 'all' }}
|
initialValues={{ 'image-type': 'all' }}
|
||||||
>
|
>
|
||||||
{props =>
|
{props =>
|
||||||
@ -91,25 +90,26 @@ export const List = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
graphql(RemoveImage, { name: 'removeImage' }),
|
graphql(RemoveImage, {
|
||||||
|
name: 'removeImage'
|
||||||
|
}),
|
||||||
graphql(ListImages, {
|
graphql(ListImages, {
|
||||||
options: { pollInterval: 5000 },
|
fetchPolicy: 'network-only',
|
||||||
props: ({ data: { images, loading, error, refetch } }) => {
|
pollInterval: 1000,
|
||||||
return {
|
props: ({ data: { images, loading, error, refetch } }) => ({
|
||||||
images,
|
images,
|
||||||
loading,
|
loading,
|
||||||
error
|
error
|
||||||
};
|
})
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
connect(
|
connect(
|
||||||
({ form, values }, { index, error, images = [] }) => {
|
({ form, values }, { index, error, images = [] }) => {
|
||||||
const filter = get(form, `${MENU_FORM_DETAILS}.values.filter`, false);
|
const filter = get(form, `${LIST_TOOLBAR_FORM}.values.filter`, false);
|
||||||
const mutationError = get(values, 'remove-mutation-error', null);
|
const mutationError = get(values, 'remove-mutation-error', null);
|
||||||
|
|
||||||
const typeValue = get(
|
const typeValue = get(
|
||||||
form,
|
form,
|
||||||
`${TOGGLE_FORM_DETAILS}.values.image-type`,
|
`${LIST_TOGGLE_TYPE_FORM}.values.image-type`,
|
||||||
'all'
|
'all'
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -171,9 +171,10 @@ export default compose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
dispatch([
|
dispatch(
|
||||||
set({ name: `remove-mutation-${id}-loading`, value: false })
|
set({ name: `remove-mutation-${id}-loading`, value: false })
|
||||||
]);
|
);
|
||||||
|
|
||||||
history.push(`/`);
|
history.push(`/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ export default compose(
|
|||||||
},
|
},
|
||||||
(dispatch, { removeImage, image, history }) => ({
|
(dispatch, { removeImage, image, history }) => ({
|
||||||
handleRemove: async () => {
|
handleRemove: async () => {
|
||||||
dispatch([set({ name: 'remove-mutation-loading', value: true })]);
|
dispatch(set({ name: 'remove-mutation-loading', value: true }));
|
||||||
|
|
||||||
const [err, res] = await intercept(
|
const [err, res] = await intercept(
|
||||||
removeImage({
|
removeImage({
|
||||||
@ -110,7 +110,7 @@ export default compose(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
dispatch([set({ name: 'remove-mutation-loading', value: false })]);
|
dispatch(set({ name: 'remove-mutation-loading', value: false }));
|
||||||
history.push(`/`);
|
history.push(`/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { compose, graphql } from 'react-apollo';
|
import { compose, graphql } from 'react-apollo';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
import { Margin } from 'styled-components-spacing';
|
import { Margin } from 'styled-components-spacing';
|
||||||
import ReduxForm from 'declarative-redux-form';
|
import ReduxForm from 'declarative-redux-form';
|
||||||
|
import { destroy } from 'redux-form';
|
||||||
|
import { set } from 'react-redux-values';
|
||||||
|
import intercept from 'apr-intercept';
|
||||||
import find from 'lodash.find';
|
import find from 'lodash.find';
|
||||||
import get from 'lodash.get';
|
import get from 'lodash.get';
|
||||||
import remcalc from 'remcalc';
|
import remcalc from 'remcalc';
|
||||||
@ -17,22 +21,36 @@ import {
|
|||||||
TagList
|
TagList
|
||||||
} from 'joyent-ui-toolkit';
|
} from 'joyent-ui-toolkit';
|
||||||
|
|
||||||
import Tag from '@components/tags';
|
import { Forms } from '@root/constants';
|
||||||
|
import Tag, { AddForm } from '@components/tags';
|
||||||
import ToolbarForm from '@components/toolbar';
|
import ToolbarForm from '@components/toolbar';
|
||||||
|
import UpdateImageTags from '@graphql/update-image-tags.gql';
|
||||||
import GetTags from '@graphql/get-tags.gql';
|
import GetTags from '@graphql/get-tags.gql';
|
||||||
|
import parseError from '@state/parse-error';
|
||||||
|
|
||||||
const MENU_FORM_DETAILS = 'image-tags-list-menu';
|
const { TAGS_TOOLBAR_FORM, TAGS_ADD_FORM } = Forms;
|
||||||
|
|
||||||
export const Tags = ({ tags = [], loading = false, error = null }) => (
|
export const Tags = ({
|
||||||
|
tags = [],
|
||||||
|
addOpen = false,
|
||||||
|
loading = false,
|
||||||
|
error = null,
|
||||||
|
mutationError = null,
|
||||||
|
mutating = false,
|
||||||
|
handleToggleAddOpen,
|
||||||
|
handleRemoveTag,
|
||||||
|
handleAddTag
|
||||||
|
}) => (
|
||||||
<ViewContainer main>
|
<ViewContainer main>
|
||||||
<ReduxForm form={MENU_FORM_DETAILS}>
|
<ReduxForm form={TAGS_TOOLBAR_FORM}>
|
||||||
{props => (
|
{props => (
|
||||||
<Margin bottom="4">
|
<Margin bottom="4">
|
||||||
<ToolbarForm
|
<ToolbarForm
|
||||||
{...props}
|
{...props}
|
||||||
searchable={!loading}
|
searchable={!loading}
|
||||||
actionLabel="Add Tag"
|
actionLabel="Add Tag"
|
||||||
actionable={!loading}
|
actionable={!loading && !mutating && !addOpen}
|
||||||
|
onActionClick={handleToggleAddOpen}
|
||||||
action
|
action
|
||||||
/>
|
/>
|
||||||
<Divider height={remcalc(1)} />
|
<Divider height={remcalc(1)} />
|
||||||
@ -49,6 +67,27 @@ export const Tags = ({ tags = [], loading = false, error = null }) => (
|
|||||||
</Message>
|
</Message>
|
||||||
</Margin>
|
</Margin>
|
||||||
) : null}
|
) : null}
|
||||||
|
{mutationError ? (
|
||||||
|
<Margin bottom={4}>
|
||||||
|
<Message error>
|
||||||
|
<MessageTitle>Ooops!</MessageTitle>
|
||||||
|
<MessageDescription>{mutationError}</MessageDescription>
|
||||||
|
</Message>
|
||||||
|
</Margin>
|
||||||
|
) : null}
|
||||||
|
<ReduxForm form={TAGS_ADD_FORM} onSubmit={handleAddTag}>
|
||||||
|
{props =>
|
||||||
|
addOpen ? (
|
||||||
|
<Margin bottom={4}>
|
||||||
|
<AddForm
|
||||||
|
{...props}
|
||||||
|
onToggleExpanded={() => handleToggleAddOpen(!addOpen)}
|
||||||
|
onCancel={() => handleToggleAddOpen(!addOpen)}
|
||||||
|
/>
|
||||||
|
</Margin>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
</ReduxForm>
|
||||||
{!loading ? (
|
{!loading ? (
|
||||||
<Margin bottom={4}>
|
<Margin bottom={4}>
|
||||||
<H3>
|
<H3>
|
||||||
@ -59,29 +98,114 @@ export const Tags = ({ tags = [], loading = false, error = null }) => (
|
|||||||
{loading && !tags.length ? <StatusLoader /> : null}
|
{loading && !tags.length ? <StatusLoader /> : null}
|
||||||
<TagList>
|
<TagList>
|
||||||
{tags.map(({ name, value }) => (
|
{tags.map(({ name, value }) => (
|
||||||
<Tag key={value} name={name} value={value} active />
|
<Tag
|
||||||
|
key={value}
|
||||||
|
name={name}
|
||||||
|
value={value}
|
||||||
|
onRemoveClick={!mutating && (() => handleRemoveTag(name))}
|
||||||
|
active
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</TagList>
|
</TagList>
|
||||||
</ViewContainer>
|
</ViewContainer>
|
||||||
);
|
);
|
||||||
|
|
||||||
export default compose(
|
export default compose(
|
||||||
|
graphql(UpdateImageTags, {
|
||||||
|
name: 'updateTags'
|
||||||
|
}),
|
||||||
graphql(GetTags, {
|
graphql(GetTags, {
|
||||||
options: ({ match }) => ({
|
options: ({ match }) => ({
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
pollInterval: 1000,
|
||||||
variables: {
|
variables: {
|
||||||
name: get(match, 'params.image')
|
name: get(match, 'params.image')
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
props: ({
|
props: ({ data }) => {
|
||||||
data: { loading = false, error = null, variables, ...rest }
|
const {
|
||||||
}) => ({
|
loading = false,
|
||||||
tags: get(
|
error = null,
|
||||||
find(get(rest, 'images', []), ['name', variables.name]),
|
variables,
|
||||||
'tags',
|
refetch,
|
||||||
[]
|
...rest
|
||||||
),
|
} = data;
|
||||||
loading,
|
const image = find(get(rest, 'images', []), ['name', variables.name]);
|
||||||
error
|
const tags = get(image || {}, 'tags', []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
image: image || {},
|
||||||
|
tags,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
refetch
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
connect(
|
||||||
|
({ values }, { image }) => ({
|
||||||
|
addOpen: get(values, `${image.id}-add-open`, false),
|
||||||
|
mutationError: get(values, `${image.id}-mutation-error`, false),
|
||||||
|
mutating: get(values, `${image.id}-mutating`, false)
|
||||||
|
}),
|
||||||
|
(dispatch, { image, tags = [], updateTags, refetch }) => ({
|
||||||
|
handleToggleAddOpen: addOpen => {
|
||||||
|
dispatch(set({ name: `${image.id}-add-open`, value: addOpen }));
|
||||||
|
},
|
||||||
|
handleRemoveTag: async name => {
|
||||||
|
dispatch(set({ name: `${image.id}-mutating`, value: true }));
|
||||||
|
|
||||||
|
const [err, res] = await intercept(
|
||||||
|
updateTags({
|
||||||
|
variables: {
|
||||||
|
id: image.id,
|
||||||
|
tags: tags
|
||||||
|
.map(({ name, value }) => ({ name, value }))
|
||||||
|
.filter(tag => tag.name !== name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
dispatch([
|
||||||
|
set({ name: `${image.id}-mutation-error`, value: parseError(err) }),
|
||||||
|
set({ name: `${image.id}-mutating`, value: false })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await refetch();
|
||||||
|
|
||||||
|
dispatch(set({ name: `${image.id}-mutating`, value: false }));
|
||||||
|
},
|
||||||
|
handleAddTag: async ({ name, value }) => {
|
||||||
|
dispatch(set({ name: `${image.id}-mutating`, value: true }));
|
||||||
|
|
||||||
|
const [err, res] = await intercept(
|
||||||
|
updateTags({
|
||||||
|
variables: {
|
||||||
|
id: image.id,
|
||||||
|
tags: tags
|
||||||
|
.map(({ name, value }) => ({ name, value }))
|
||||||
|
.concat([{ name, value }])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (err) {
|
||||||
|
dispatch([
|
||||||
|
set({ name: `${image.id}-mutation-error`, value: parseError(err) }),
|
||||||
|
set({ name: `${image.id}-mutating`, value: false })
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
await refetch();
|
||||||
|
|
||||||
|
dispatch([
|
||||||
|
set({ name: `${image.id}-mutating`, value: false }),
|
||||||
|
dispatch(set({ name: `${image.id}-add-open`, value: false })),
|
||||||
|
destroy(TAGS_ADD_FORM)
|
||||||
|
]);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
)(Tags);
|
)(Tags);
|
||||||
|
5
packages/images/src/graphql/update-image-tags.gql
Normal file
5
packages/images/src/graphql/update-image-tags.gql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mutation updateImageTags($id: ID!, $tags: [KeyValueInput]!) {
|
||||||
|
updateImage(id: $id, tags: $tags) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user