mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2026-04-19 21:16:18 +00:00
Implemented edition of tags
This commit is contained in:
@@ -59,6 +59,11 @@ export class ShlinkApiClient {
|
||||
.then(() => ({ tags }))
|
||||
.catch(e => this._handleAuthError(e, this.deleteTag, []));
|
||||
|
||||
editTag = (oldName, newName) =>
|
||||
this._performRequest('/tags', 'PUT', {}, { oldName, newName })
|
||||
.then(() => ({ oldName, newName }))
|
||||
.catch(e => this._handleAuthError(e, this.editTag, [oldName, newName]));
|
||||
|
||||
_performRequest = async (url, method = 'GET', query = {}, body = {}) => {
|
||||
if (isEmpty(this._token)) {
|
||||
this._token = await this._authenticate();
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function AsideMenu({ selectedServer, className, showOnMobile }) {
|
||||
to={`/server/${serverId}/tags`}
|
||||
>
|
||||
<FontAwesomeIcon icon={tagsIcon} />
|
||||
<span className="aside-menu__item-text">List tags</span>
|
||||
<span className="aside-menu__item-text">Manage tags</span>
|
||||
</NavLink>
|
||||
|
||||
<DeleteServerButton
|
||||
|
||||
@@ -9,6 +9,7 @@ import shortUrlVisitsReducer from '../short-urls/reducers/shortUrlVisits';
|
||||
import shortUrlTagsReducer from '../short-urls/reducers/shortUrlTags';
|
||||
import tagsListReducer from '../tags/reducers/tagsList';
|
||||
import tagDeleteReducer from '../tags/reducers/tagDelete';
|
||||
import tagEditReducer from '../tags/reducers/tagEdit';
|
||||
|
||||
export default combineReducers({
|
||||
servers: serversReducer,
|
||||
@@ -20,4 +21,5 @@ export default combineReducers({
|
||||
shortUrlTags: shortUrlTagsReducer,
|
||||
tagsList: tagsListReducer,
|
||||
tagDelete: tagDeleteReducer,
|
||||
tagEdit: tagEditReducer,
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ import React from 'react';
|
||||
import ColorGenerator, { colorGeneratorType } from '../utils/ColorGenerator';
|
||||
import './TagCard.scss';
|
||||
import { Link } from 'react-router-dom';
|
||||
import EditTagModal from './helpers/EditTagModal';
|
||||
|
||||
const propTypes = {
|
||||
tag: PropTypes.string,
|
||||
@@ -24,17 +25,22 @@ export default class TagCard extends React.Component {
|
||||
const { tag, colorGenerator, currentServerId } = this.props;
|
||||
const toggleDelete = () =>
|
||||
this.setState({ isDeleteModalOpen: !this.state.isDeleteModalOpen });
|
||||
const toggleEdit = () =>
|
||||
this.setState({ isEditModalOpen: !this.state.isEditModalOpen });
|
||||
|
||||
return (
|
||||
<Card className="tag-card">
|
||||
<CardBody className="tag-card__body">
|
||||
<button
|
||||
className="btn btn-light btn-sm tag-card__btn"
|
||||
className="btn btn-light btn-sm tag-card__btn tag-card__btn--last"
|
||||
onClick={toggleDelete}
|
||||
>
|
||||
<FontAwesomeIcon icon={deleteIcon}/>
|
||||
</button>
|
||||
<button className="btn btn-light btn-sm tag-card__btn">
|
||||
<button
|
||||
className="btn btn-light btn-sm tag-card__btn"
|
||||
onClick={toggleEdit}
|
||||
>
|
||||
<FontAwesomeIcon icon={editIcon}/>
|
||||
</button>
|
||||
<h5 className="tag-card__tag-title">
|
||||
@@ -53,6 +59,11 @@ export default class TagCard extends React.Component {
|
||||
toggle={toggleDelete}
|
||||
isOpen={this.state.isDeleteModalOpen}
|
||||
/>
|
||||
<EditTagModal
|
||||
tag={tag}
|
||||
toggle={toggleEdit}
|
||||
isOpen={this.state.isEditModalOpen}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,6 @@
|
||||
.tag-card__btn {
|
||||
float: right;
|
||||
}
|
||||
.tag-card__btn:not(:last-child) {
|
||||
margin-left: 2px;
|
||||
.tag-card__btn--last {
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
75
src/tags/helpers/EditTagModal.js
Normal file
75
src/tags/helpers/EditTagModal.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
|
||||
import { pick } from 'ramda';
|
||||
import { editTag, tagEdited } from '../reducers/tagEdit';
|
||||
|
||||
export class EditTagModal extends React.Component {
|
||||
saveTag = e => {
|
||||
e.preventDefault();
|
||||
const { tag: oldName, editTag, toggle } = this.props;
|
||||
const { tag: newName } = this.state;
|
||||
|
||||
editTag(oldName, newName)
|
||||
.then(() => {
|
||||
this.tagWasEdited = true;
|
||||
toggle();
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
onClosed = () => {
|
||||
if (!this.tagWasEdited) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { tag: oldName, tagEdited } = this.props;
|
||||
const { tag: newName } = this.state;
|
||||
tagEdited(oldName, newName);
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
tag: props.tag,
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.tagWasEdited = false;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isOpen, toggle, tagEdit } = this.props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} toggle={toggle} centered onClosed={this.onClosed}>
|
||||
<form onSubmit={this.saveTag}>
|
||||
<ModalHeader toggle={toggle}>Edit tag</ModalHeader>
|
||||
<ModalBody>
|
||||
<input
|
||||
type="text"
|
||||
value={this.state.tag}
|
||||
onChange={e => this.setState({ tag: e.target.value })}
|
||||
placeholder="Tag"
|
||||
required
|
||||
className="form-control"
|
||||
/>
|
||||
{tagEdit.error && (
|
||||
<div className="p-2 mt-2 bg-danger text-white text-center">
|
||||
Something went wrong while editing the tag :(
|
||||
</div>
|
||||
)}
|
||||
</ModalBody>
|
||||
<ModalFooter>
|
||||
<button type="button" className="btn btn-link" onClick={toggle}>Cancel</button>
|
||||
<button type="submit" className="btn btn-primary" disabled={tagEdit.editing}>
|
||||
{tagEdit.editing ? 'Saving...' : 'Save'}
|
||||
</button>
|
||||
</ModalFooter>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(pick(['tagEdit']), { editTag, tagEdited })(EditTagModal);
|
||||
65
src/tags/reducers/tagEdit.js
Normal file
65
src/tags/reducers/tagEdit.js
Normal file
@@ -0,0 +1,65 @@
|
||||
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import ColorGenerator from '../../utils/ColorGenerator';
|
||||
import { curry } from 'ramda';
|
||||
|
||||
const EDIT_TAG_START = 'shlink/editTag/EDIT_TAG_START';
|
||||
const EDIT_TAG_ERROR = 'shlink/editTag/EDIT_TAG_ERROR';
|
||||
const EDIT_TAG = 'shlink/editTag/EDIT_TAG';
|
||||
export const TAG_EDITED = 'shlink/editTag/TAG_EDITED';
|
||||
|
||||
const defaultState = {
|
||||
oldName: '',
|
||||
newName: '',
|
||||
editing: false,
|
||||
error: false,
|
||||
};
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
switch (action.type) {
|
||||
case EDIT_TAG_START:
|
||||
return {
|
||||
...state,
|
||||
editing: true,
|
||||
error: false,
|
||||
};
|
||||
case EDIT_TAG_ERROR:
|
||||
return {
|
||||
...state,
|
||||
editing: false,
|
||||
error: true,
|
||||
};
|
||||
case EDIT_TAG:
|
||||
return {
|
||||
oldName: action.oldName,
|
||||
newName: action.newName,
|
||||
editing: false,
|
||||
error: false,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
export const _editTag = (ShlinkApiClient, ColorGenerator, oldName, newName) => async dispatch => {
|
||||
dispatch({ type: EDIT_TAG_START });
|
||||
|
||||
try {
|
||||
await ShlinkApiClient.editTag(oldName, newName);
|
||||
|
||||
// Make new tag name use the same color as the old one
|
||||
const color = ColorGenerator.getColorForKey(oldName);
|
||||
ColorGenerator.setColorForKey(newName, color);
|
||||
|
||||
dispatch({ type: EDIT_TAG, oldName, newName });
|
||||
} catch (e) {
|
||||
dispatch({ type: EDIT_TAG_ERROR });
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
export const editTag = curry(_editTag)(ShlinkApiClient, ColorGenerator);
|
||||
|
||||
export const tagEdited = (oldName, newName) => ({
|
||||
type: TAG_EDITED,
|
||||
oldName,
|
||||
newName,
|
||||
});
|
||||
@@ -1,6 +1,7 @@
|
||||
import ShlinkApiClient from '../../api/ShlinkApiClient';
|
||||
import { TAG_DELETED } from './tagDelete';
|
||||
import { reject } from 'ramda';
|
||||
import { TAG_EDITED } from './tagEdit';
|
||||
|
||||
const LIST_TAGS_START = 'shlink/tagsList/LIST_TAGS_START';
|
||||
const LIST_TAGS_ERROR = 'shlink/tagsList/LIST_TAGS_ERROR';
|
||||
@@ -37,6 +38,13 @@ export default function reducer(state = defaultState, action) {
|
||||
...state,
|
||||
tags: reject(tag => tag === action.tag, state.tags),
|
||||
};
|
||||
case TAG_EDITED:
|
||||
return {
|
||||
...state,
|
||||
tags: state.tags.map(
|
||||
tag => tag === action.oldName ? action.newName : tag
|
||||
),
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user