Skip to content

Commit c2945e2

Browse files
feat(Label): add edit and delete button to labels
1 parent 83ae860 commit c2945e2

File tree

7 files changed

+208
-24
lines changed

7 files changed

+208
-24
lines changed

lib/common/components/FeedLabel.js

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import React from 'react'
22
import tinycolor from 'tinycolor2'
33
import Icon from '@conveyal/woonerf/components/icon'
44

5+
import ConfirmModal from '../../common/components/ConfirmModal'
6+
import { LabelEditorModal } from '../../manager/components/LabelEditor'
7+
58
const getComplementaryColor = (cssHex, strength) => {
69
const color = tinycolor(cssHex)
710

@@ -11,20 +14,51 @@ const getComplementaryColor = (cssHex, strength) => {
1114
return complementary.toHexString()
1215
}
1316

14-
export default function Label (props) {
15-
const { name, color, small, adminOnly } = props
16-
17-
return (
18-
<span
19-
className={`feedLabel ${small ? 'small' : ''}`}
20-
style={{
21-
backgroundColor: color,
22-
color: getComplementaryColor(color, 45),
23-
borderColor: getComplementaryColor(color, 10)
24-
}}
25-
>
26-
{adminOnly ? <Icon type='lock' /> : ''}
27-
{name}
28-
</span>
29-
)
17+
export default class FeedLabel extends React.Component {
18+
_onConfirmDelete = () => {
19+
this.props.deleteLabel(this.props)
20+
}
21+
22+
_onClickDelete = () => {
23+
this.refs.deleteModal.open()
24+
}
25+
26+
_onClickEdit = () => {
27+
this.refs.editModal.open()
28+
}
29+
30+
render () {
31+
const { name, color, small, adminOnly } = this.props
32+
33+
return (
34+
<div
35+
className={`feedLabel ${small ? 'small' : ''}`}
36+
style={{
37+
backgroundColor: color,
38+
color: getComplementaryColor(color, 40),
39+
borderColor: getComplementaryColor(color, 10)
40+
}}
41+
>
42+
43+
<div className='labelName'>
44+
{adminOnly ? <Icon type='lock' /> : ''}
45+
<span>{name}</span>
46+
</div>
47+
{ small ? ''
48+
: <div className='actionButtons small'>
49+
<ConfirmModal
50+
ref='deleteModal'
51+
title='Delete Label?'
52+
body={`Are you sure you want to delete the label ${name}?`}
53+
onConfirm={this._onConfirmDelete}
54+
/>
55+
<LabelEditorModal ref='editModal' label={{ ...this.props }} />
56+
57+
<button onClick={() => this._onClickEdit()}>Edit</button>
58+
<button onClick={() => this._onClickDelete()}>Delete</button>
59+
</div>
60+
}
61+
</div>
62+
)
63+
}
3064
}

lib/common/containers/FeedLabel.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// @flow
2+
3+
import { connect } from 'react-redux'
4+
5+
import { deleteLabel } from '../../manager/actions/labels'
6+
import FeedLabel from '../components/FeedLabel'
7+
8+
const mapStateToProps = (state, ownProps) => {
9+
return {
10+
label: state.label
11+
}
12+
}
13+
14+
const mapDispatchToProps = {
15+
deleteLabel
16+
}
17+
18+
export default connect(
19+
mapStateToProps,
20+
mapDispatchToProps
21+
)(FeedLabel)

lib/manager/actions/labels.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// @flow
2+
3+
import {secureFetch} from '../../common/actions'
4+
import type {dispatchFn, getStateFn} from '../../types/reducers'
5+
6+
import { fetchProject } from './projects'
7+
import {fetchProjectFeeds} from './feeds'
8+
9+
// Public action used by component or other actions
10+
11+
const LABEL_URL = '/api/manager/secure/label'
12+
13+
/**
14+
* Create new label from provided properties.
15+
*/
16+
export function createLabel (newLabel: Label) {
17+
return function (dispatch: dispatchFn, getState: getStateFn) {
18+
return dispatch(secureFetch(LABEL_URL, 'post', newLabel))
19+
.then((res) => res.json())
20+
.then((createdLabel) => {
21+
// TODO
22+
})
23+
}
24+
}
25+
26+
/**
27+
* Update existing label with provided properties.
28+
*/
29+
export function updateLabel (label: Label, properties: {[string]: any}) {
30+
return function (dispatch: dispatchFn, getState: getStateFn) {
31+
return dispatch(secureFetch(`${LABEL_URL}/${label.id}`, 'put', {...label, ...properties}))
32+
.then((res) => dispatch(fetchProject(label.projectId)))
33+
}
34+
}
35+
36+
/**
37+
* Permanently delete single label */
38+
export function deleteLabel (label: Label) {
39+
return function (dispatch: dispatchFn, getState: getStateFn) {
40+
return dispatch(secureFetch(`${LABEL_URL}/${label.id}`, 'delete'))
41+
.then((res) => dispatch(fetchProject(label.projectId)))
42+
.then(() => dispatch(fetchProjectFeeds(label.projectId)))
43+
}
44+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// @flow
2+
3+
import React from 'react'
4+
import { Modal } from 'react-bootstrap'
5+
6+
type LabelEditorProps = {
7+
label?: Label,
8+
}
9+
10+
type LabelEditorState = {
11+
showModal: boolean,
12+
}
13+
14+
export function LabelEditor ({label}) {
15+
return <div>{label.name}</div>
16+
}
17+
export class LabelEditorModal extends React.Component<LabelEditorProps, LabelEditorState> {
18+
state = {
19+
showModal: false
20+
}
21+
22+
close = () => {
23+
this.setState({
24+
showModal: false
25+
})
26+
}
27+
28+
open = () => {
29+
this.setState({
30+
showModal: true
31+
})
32+
}
33+
34+
ok = () => {
35+
this.close()
36+
}
37+
38+
render () {
39+
const {Body, Header, Title} = Modal
40+
const {label} = this.props
41+
return (
42+
<Modal show={this.state.showModal} onHide={this.close}>
43+
<Header>
44+
<Title>Editing {label.name ? label.name : 'New Label'}</Title>
45+
</Header>
46+
47+
<Body>
48+
<LabelEditor label={label} />
49+
</Body>
50+
</Modal>
51+
)
52+
}
53+
}

lib/manager/components/ProjectViewer.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import WatchButton from '../../common/containers/WatchButton'
2727
import {getComponentMessages, getConfigProperty, isModuleEnabled} from '../../common/util/config'
2828
import DeploymentsPanel from '../containers/DeploymentsPanel'
2929
import FeedSourceTable from '../containers/FeedSourceTable'
30-
import Label from '../../common/components/FeedLabel'
30+
import FeedLabel from '../../common/containers/FeedLabel'
3131
import type {Props as ContainerProps} from '../containers/ActiveProjectViewer'
3232
import type {Feed, Project} from '../../types'
3333
import type {ManagerUserState} from '../../types/reducers'
@@ -314,9 +314,7 @@ const LabelPanel = ({ project }) => {
314314
let labelBody = 'There are no labels in this project.'
315315
if (labels.length > 0) {
316316
labelBody = labels.map((label) => (
317-
// Disable this rule as it is not a form label
318-
// eslint-disable-next-line jsx-a11y/label-has-for
319-
<Label key={label.id} {...label} />
317+
<FeedLabel key={label.id} {...label} />
320318
))
321319
}
322320

lib/style.css

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -162,15 +162,48 @@ td.feed-source-info {
162162

163163
display: flex;
164164
align-items: center;
165-
justify-content: center;
165+
justify-content: space-between;
166166

167167
border-radius: 5px;
168168
border: 1px solid;
169169

170-
padding: 5px;
170+
padding: 5px 0 8px 0;
171171
}
172172
.feedLabel.small {
173-
padding: 2.5px;
173+
/* removing bottom padding seems to be only way to account for the icon baseline fix */
174+
padding: 2px 0 0 0;
175+
align-items: baseline;
176+
justify-content: center;
177+
}
178+
.feedLabel.small .labelName span {
179+
/* The icon adds this, so we have to add it as well */
180+
margin-bottom: 5px;
181+
}
182+
.feedLabel .labelName {
183+
flex: 2;
184+
display: flex;
185+
justify-content: center;
186+
align-items: baseline;
187+
padding: 5px;
188+
}
189+
.feedLabel.small .labelName {
190+
padding: 0;
191+
}
192+
193+
/* Delete Button */
194+
.feedLabel .actionButtons {
195+
display: flex;
196+
align-items: flex-end;
197+
flex-direction: column;
198+
}
199+
.feedLabel .actionButtons button {
200+
background: none;
201+
border: none;
202+
text-decoration: underline;
203+
opacity: 0.7;
204+
}
205+
.feedLabel .actionButtons button:hover {
206+
opacity: 1;
174207
}
175208

176209
/* Feed transformation substitution styling */

lib/types/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,8 @@ export type Label = {
526526
color: string,
527527
description: string,
528528
id: string,
529-
name: string
529+
name: string,
530+
projectId: string
530531
}
531532

532533
// TODO: Remove this eslint rule once https://github.com/babel/babel-eslint/pull/584

0 commit comments

Comments
 (0)