Created common component to handle tags and modal to edit tags

This commit is contained in:
Alejandro Celaya 2018-08-15 11:14:44 +02:00
parent 707c097ed9
commit 854851fefc
9 changed files with 115 additions and 30 deletions

View file

@ -3,7 +3,7 @@
border: 1px solid #ccc;
border-radius: .25rem;
overflow: hidden;
min-height: calc(2.6rem + 2px);
min-height: 2.6rem;
padding: 6px 0 0 6px;
}
@ -21,16 +21,13 @@
.react-tagsinput-tag {
font-size: 1rem;
background-color: #f1f1f1;
border-radius: 2px;
border: 1px solid #d1d1d1;
border-radius: 4px;
display: inline-block;
font-weight: 400;
margin: 0 5px 6px 0;
padding: 6px 8px;
line-height: 1;
}
.react-tagsinput-tag:hover {
border-color: #b1b1b1;
color: #fff;
}
.react-tagsinput-remove {
@ -41,7 +38,7 @@
.react-tagsinput-tag a::before {
content: "\2715";
color: #aaa;
color: #fff;
}
.react-tagsinput-input {

View file

@ -4,11 +4,11 @@ import FontAwesomeIcon from '@fortawesome/react-fontawesome';
import { assoc, dissoc, isNil, pick, pipe, replace, trim } from 'ramda';
import React from 'react';
import { connect } from 'react-redux';
import TagsInput from 'react-tagsinput'
import { Collapse } from 'reactstrap';
import DateInput from '../common/DateInput';
import CreateShortUrlResult from './helpers/CreateShortUrlResult';
import { createShortUrl, resetCreateShortUrl } from './reducers/shortUrlCreationResult';
import TagsSelector from '../utils/TagsSelector';
export class CreateShortUrl extends React.Component {
state = {
@ -68,13 +68,7 @@ export class CreateShortUrl extends React.Component {
<Collapse isOpen={this.state.moreOptionsVisible}>
<div className="form-group">
<TagsInput
value={this.state.tags}
onChange={changeTags}
inputProps={{ placeholder: 'Add tags to the URL' }}
onlyUnique
addOnBlur // FIXME Workaround to be able to add tags on Android
/>
<TagsSelector tags={this.state.tags} onChange={changeTags} />
</div>
<div className="row">

View file

@ -1,7 +1,7 @@
import caretDownIcon from '@fortawesome/fontawesome-free-solid/faCaretDown'
import caretUpIcon from '@fortawesome/fontawesome-free-solid/faCaretUp'
import FontAwesomeIcon from '@fortawesome/react-fontawesome'
import { head, isEmpty, pick, toPairs } from 'ramda'
import { head, isEmpty, pick, toPairs, keys, values } from 'ramda'
import React from 'react'
import { connect } from 'react-redux'
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap'
@ -34,7 +34,7 @@ export class ShortUrlsList extends React.Component {
'DESC': undefined,
};
return this.state.orderDir ? newOrderMap[this.state.orderDir] : 'ASC';
}
};
orderBy = field => {
const newOrderDir = this.determineOrderDir(field);
this.setState({ orderField: newOrderDir !== undefined ? field : undefined, orderDir: newOrderDir });
@ -58,8 +58,8 @@ export class ShortUrlsList extends React.Component {
const { orderBy } = props.shortUrlsListParams;
this.state = {
orderField: orderBy ? head(Object.keys(orderBy)) : undefined,
orderDir: orderBy ? head(Object.values(orderBy)) : undefined,
orderField: orderBy ? head(keys(orderBy)) : undefined,
orderDir: orderBy ? head(values(orderBy)) : undefined,
}
}
@ -71,7 +71,11 @@ export class ShortUrlsList extends React.Component {
renderShortUrls() {
const { shortUrlsList, selectedServer, loading, error, shortUrlsListParams } = this.props;
if (error) {
return <tr><td colSpan="6" className="text-center table-danger">Something went wrong while loading short URLs :(</td></tr>;
return (
<tr>
<td colSpan="6" className="text-center table-danger">Something went wrong while loading short URLs :(</td>
</tr>
);
}
if (loading) {
@ -102,7 +106,7 @@ export class ShortUrlsList extends React.Component {
</DropdownToggle>
<DropdownMenu className="short-urls-list__order-dropdown">
{toPairs(SORTABLE_FIELDS).map(([key, value]) =>
<DropdownItem active={this.state.orderField === key} onClick={() => this.orderBy(key)}>
<DropdownItem key={key} active={this.state.orderField === key} onClick={() => this.orderBy(key)}>
{value}
{this.renderOrderIcon(key, 'short-urls-list__header-icon--mobile')}
</DropdownItem>)}

View file

@ -0,0 +1,27 @@
import React from 'react';
import { Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import TagsSelector from '../../utils/TagsSelector';
export default class EditTagsModal extends React.Component {
constructor(props) {
super(props);
this.state = { tags: props.shortUrl.tags };
}
render() {
const { isOpen, toggle, url } = this.props;
const changeTags = tags => this.setState({ tags });
return (
<Modal isOpen={isOpen} toggle={toggle} centered>
<ModalHeader toggle={toggle}>Edit tags for <a target="_blank" href={url}>{url}</a></ModalHeader>
<ModalBody>
<TagsSelector tags={this.state.tags} onChange={changeTags} />
</ModalBody>
<ModalFooter>
<button className="btn btn-outline-primary" type="button">Save</button>
</ModalFooter>
</Modal>
);
}
}

View file

@ -4,7 +4,7 @@ import './QrCodeModal.scss';
export default function QrCodeModal ({ url, toggle, isOpen }) {
return (
<Modal isOpen={isOpen} toggle={toggle} centered={true}>
<Modal isOpen={isOpen} toggle={toggle} centered>
<ModalHeader toggle={toggle}>QR code for <a target="_blank" href={url}>{url}</a></ModalHeader>
<ModalBody>
<div className="text-center">

View file

@ -45,9 +45,9 @@ export class ShortUrlsRow extends React.Component {
Copied short URL!
</small>
<ShortUrlsRowMenu
shortUrl={completeShortUrl}
completeShortUrl={completeShortUrl}
selectedServer={selectedServer}
shortCode={shortUrl.shortCode}
shortUrl={shortUrl}
onCopyToClipboard={() => {
this.setState({ copiedToClipboard: true });
setTimeout(() => this.setState({ copiedToClipboard: false }), 2000);

View file

@ -1,5 +1,6 @@
import copyIcon from '@fortawesome/fontawesome-free-regular/faCopy';
import pictureIcon from '@fortawesome/fontawesome-free-regular/faImage';
import tagsIcon from '@fortawesome/fontawesome-free-solid/faTags';
import pieChartIcon from '@fortawesome/fontawesome-free-solid/faChartPie';
import menuIcon from '@fortawesome/fontawesome-free-solid/faEllipsisV';
import qrIcon from '@fortawesome/fontawesome-free-solid/faQrcode';
@ -11,16 +12,23 @@ import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reac
import PreviewModal from './PreviewModal';
import QrCodeModal from './QrCodeModal';
import './ShortUrlsRowMenu.scss';
import EditTagsModal from './EditTagsModal';
export class ShortUrlsRowMenu extends React.Component {
state = { isOpen: false, isQrModalOpen: false, isPreviewOpen: false };
state = {
isOpen: false,
isQrModalOpen: false,
isPreviewOpen: false,
isTagsModalOpen: false,
};
toggle = () => this.setState({ isOpen: !this.state.isOpen });
render() {
const { shortUrl, onCopyToClipboard, selectedServer, shortCode } = this.props;
const { completeShortUrl, onCopyToClipboard, selectedServer, shortUrl } = this.props;
const serverId = selectedServer ? selectedServer.id : '';
const toggleQrCode = () => this.setState({isQrModalOpen: !this.state.isQrModalOpen});
const togglePreview = () => this.setState({isPreviewOpen: !this.state.isPreviewOpen});
const toggleTags = () => this.setState({isTagsModalOpen: !this.state.isTagsModalOpen});
return (
<ButtonDropdown toggle={this.toggle} isOpen={this.state.isOpen} direction="left">
@ -28,9 +36,18 @@ export class ShortUrlsRowMenu extends React.Component {
&nbsp;<FontAwesomeIcon icon={menuIcon}/>&nbsp;
</DropdownToggle>
<DropdownMenu>
<DropdownItem tag={Link} to={`/server/${serverId}/short-code/${shortCode}/visits`}>
<DropdownItem tag={Link} to={`/server/${serverId}/short-code/${shortUrl.shortCode}/visits`}>
<FontAwesomeIcon icon={pieChartIcon}/> &nbsp;Visit Stats
</DropdownItem>
<DropdownItem onClick={toggleTags}>
<FontAwesomeIcon icon={tagsIcon}/> &nbsp;Edit tags
</DropdownItem>
<EditTagsModal
url={completeShortUrl}
shortUrl={shortUrl}
isOpen={this.state.isTagsModalOpen}
toggle={toggleTags}
/>
<DropdownItem divider/>
@ -38,7 +55,7 @@ export class ShortUrlsRowMenu extends React.Component {
<FontAwesomeIcon icon={pictureIcon}/> &nbsp;Preview
</DropdownItem>
<PreviewModal
url={shortUrl}
url={completeShortUrl}
isOpen={this.state.isPreviewOpen}
toggle={togglePreview}
/>
@ -47,14 +64,14 @@ export class ShortUrlsRowMenu extends React.Component {
<FontAwesomeIcon icon={qrIcon}/> &nbsp;QR code
</DropdownItem>
<QrCodeModal
url={shortUrl}
url={completeShortUrl}
isOpen={this.state.isQrModalOpen}
toggle={toggleQrCode}
/>
<DropdownItem divider/>
<CopyToClipboard text={shortUrl} onCopy={onCopyToClipboard}>
<CopyToClipboard text={completeShortUrl} onCopy={onCopyToClipboard}>
<DropdownItem>
<FontAwesomeIcon icon={copyIcon}/> &nbsp;Copy to clipboard
</DropdownItem>

View file

@ -1,4 +1,5 @@
import Storage from './Storage';
import PropTypes from 'prop-types';
const buildRandomColor = () => {
const letters = '0123456789ABCDEF';
@ -29,4 +30,8 @@ export class ColorGenerator {
};
}
export const colorGeneratorType = PropTypes.shape({
getColorForKey: PropTypes.func,
});
export default new ColorGenerator(Storage);

41
src/utils/TagsSelector.js Normal file
View file

@ -0,0 +1,41 @@
import React from 'react';
import TagsInput from 'react-tagsinput';
import ColorGenerator, { colorGeneratorType } from './ColorGenerator';
import PropTypes from 'prop-types';
const defaultProps = {
colorGenerator: ColorGenerator,
placeholder: 'Add tags to the URL',
};
const propTypes = {
tags: PropTypes.arrayOf(PropTypes.string).isRequired,
onChange: PropTypes.func.isRequired,
placeholder: PropTypes.string,
colorGenerator: colorGeneratorType
};
export default function TagsSelector({ tags, onChange, placeholder, colorGenerator }) {
const renderTag = (props) => {
const { tag, key, disabled, onRemove, classNameRemove, getTagDisplayValue, ...other } = props;
return (
<span key={key} style={{ backgroundColor: colorGenerator.getColorForKey(tag) }} {...other}>
{getTagDisplayValue(tag)}
{!disabled && <a className={classNameRemove} onClick={() => onRemove(key)} />}
</span>
)
};
return (
<TagsInput
value={tags}
onChange={onChange}
inputProps={{ placeholder }}
onlyUnique
addOnBlur // FIXME Workaround to be able to add tags on Android
renderTag={renderTag}
/>
);
}
TagsSelector.defaultProps = defaultProps;
TagsSelector.propTypes = propTypes;