mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Moved copy-to-clipboard control next to short URL
This commit is contained in:
parent
96d538db15
commit
fba156b271
8 changed files with 50 additions and 54 deletions
|
@ -10,10 +10,6 @@ body,
|
|||
outline: none !important;
|
||||
}
|
||||
|
||||
.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bg-main {
|
||||
background-color: $mainColor !important;
|
||||
}
|
||||
|
|
|
@ -161,7 +161,7 @@ const ShortUrlsList = (ShortUrlsRow) => class ShortUrlsList extends React.Compon
|
|||
className="short-urls-list__header-cell short-urls-list__header-cell--with-action"
|
||||
onClick={this.orderByColumn('visits')}
|
||||
>
|
||||
<span className="nowrap">{this.renderOrderIcon('visits')} Visits</span>
|
||||
<span className="indivisible">{this.renderOrderIcon('visits')} Visits</span>
|
||||
</th>
|
||||
<th className="short-urls-list__header-cell"> </th>
|
||||
</tr>
|
||||
|
|
|
@ -3,6 +3,9 @@ import React from 'react';
|
|||
import Moment from 'react-moment';
|
||||
import PropTypes from 'prop-types';
|
||||
import { ExternalLink } from 'react-external-link';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faCopy as copyIcon } from '@fortawesome/free-regular-svg-icons';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { shortUrlsListParamsType } from '../reducers/shortUrlsListParams';
|
||||
import { serverType } from '../../servers/prop-types';
|
||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||
|
@ -26,7 +29,7 @@ const ShortUrlsRow = (
|
|||
|
||||
renderTags(tags) {
|
||||
if (isEmpty(tags)) {
|
||||
return <i className="nowrap"><small>No tags</small></i>;
|
||||
return <i className="indivisible"><small>No tags</small></i>;
|
||||
}
|
||||
|
||||
const { refreshList, shortUrlsListParams } = this.props;
|
||||
|
@ -47,11 +50,25 @@ const ShortUrlsRow = (
|
|||
|
||||
return (
|
||||
<tr className="short-urls-row">
|
||||
<td className="nowrap short-urls-row__cell" data-th="Created at: ">
|
||||
<td className="indivisible short-urls-row__cell" data-th="Created at: ">
|
||||
<Moment format="YYYY-MM-DD HH:mm">{shortUrl.dateCreated}</Moment>
|
||||
</td>
|
||||
<td className="short-urls-row__cell" data-th="Short URL: ">
|
||||
<ExternalLink href={shortUrl.shortUrl} />
|
||||
<span className="indivisible short-urls-row__cell--relative">
|
||||
<ExternalLink href={shortUrl.shortUrl} />
|
||||
<CopyToClipboard
|
||||
text={shortUrl.shortUrl}
|
||||
onCopy={() => stateFlagTimeout(this.setState.bind(this), 'copiedToClipboard')}
|
||||
>
|
||||
<FontAwesomeIcon icon={copyIcon} className="ml-2 short-urls-row__copy-btn" />
|
||||
</CopyToClipboard>
|
||||
<span
|
||||
className="badge badge-warning short-urls-row__copy-hint"
|
||||
hidden={!this.state.copiedToClipboard}
|
||||
>
|
||||
Copied short URL!
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td className="short-urls-row__cell short-urls-row__cell--break" data-th="Long URL: ">
|
||||
<ExternalLink href={shortUrl.longUrl} />
|
||||
|
@ -64,18 +81,8 @@ const ShortUrlsRow = (
|
|||
selectedServer={selectedServer}
|
||||
/>
|
||||
</td>
|
||||
<td className="short-urls-row__cell short-urls-row__cell--relative">
|
||||
<small
|
||||
className="badge badge-warning short-urls-row__copy-hint"
|
||||
hidden={!this.state.copiedToClipboard}
|
||||
>
|
||||
Copied short URL!
|
||||
</small>
|
||||
<ShortUrlsRowMenu
|
||||
selectedServer={selectedServer}
|
||||
shortUrl={shortUrl}
|
||||
onCopyToClipboard={() => stateFlagTimeout(this.setState.bind(this), 'copiedToClipboard')}
|
||||
/>
|
||||
<td className="short-urls-row__cell">
|
||||
<ShortUrlsRowMenu selectedServer={selectedServer} shortUrl={shortUrl} />
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -43,11 +43,16 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.short-urls-row__copy-btn {
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.short-urls-row__copy-hint {
|
||||
@include vertical-align();
|
||||
right: 100%;
|
||||
@include vertical-align(translateX(10px));
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, .25);
|
||||
|
||||
@media (max-width: $smMax) {
|
||||
right: calc(100% + 10px);
|
||||
@include vertical-align(translateX(calc(-100% - 20px)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { faCopy as copyIcon, faImage as pictureIcon } from '@fortawesome/free-regular-svg-icons';
|
||||
import { faImage as pictureIcon } from '@fortawesome/free-regular-svg-icons';
|
||||
import {
|
||||
faTags as tagsIcon,
|
||||
faChartPie as pieChartIcon,
|
||||
|
@ -9,9 +9,7 @@ import {
|
|||
} from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import React from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { ButtonDropdown, DropdownItem, DropdownMenu, DropdownToggle } from 'reactstrap';
|
||||
import PropTypes from 'prop-types';
|
||||
import { serverType } from '../../servers/prop-types';
|
||||
import { shortUrlType } from '../reducers/shortUrlsList';
|
||||
import PreviewModal from './PreviewModal';
|
||||
|
@ -26,7 +24,6 @@ const ShortUrlsRowMenu = (
|
|||
ForServerVersion
|
||||
) => class ShortUrlsRowMenu extends React.Component {
|
||||
static propTypes = {
|
||||
onCopyToClipboard: PropTypes.func,
|
||||
selectedServer: serverType,
|
||||
shortUrl: shortUrlType,
|
||||
};
|
||||
|
@ -42,7 +39,7 @@ const ShortUrlsRowMenu = (
|
|||
toggle = () => this.setState(({ isOpen }) => ({ isOpen: !isOpen }));
|
||||
|
||||
render() {
|
||||
const { onCopyToClipboard, shortUrl, selectedServer } = this.props;
|
||||
const { shortUrl, selectedServer } = this.props;
|
||||
const completeShortUrl = shortUrl && shortUrl.shortUrl ? shortUrl.shortUrl : '';
|
||||
const toggleModal = (prop) => () => this.setState((prevState) => ({ [prop]: !prevState[prop] }));
|
||||
const toggleQrCode = toggleModal('isQrModalOpen');
|
||||
|
@ -73,12 +70,10 @@ const ShortUrlsRowMenu = (
|
|||
<EditMetaModal shortUrl={shortUrl} isOpen={this.state.isMetaModalOpen} toggle={toggleMeta} />
|
||||
</ForServerVersion>
|
||||
|
||||
<DropdownItem className="short-urls-row-menu__dropdown-item--danger" onClick={toggleDelete}>
|
||||
<FontAwesomeIcon icon={deleteIcon} fixedWidth /> Delete short URL
|
||||
<DropdownItem onClick={toggleQrCode}>
|
||||
<FontAwesomeIcon icon={qrIcon} fixedWidth /> QR code
|
||||
</DropdownItem>
|
||||
<DeleteShortUrlModal shortUrl={shortUrl} isOpen={this.state.isDeleteModalOpen} toggle={toggleDelete} />
|
||||
|
||||
<DropdownItem divider />
|
||||
<QrCodeModal url={completeShortUrl} isOpen={this.state.isQrModalOpen} toggle={toggleQrCode} />
|
||||
|
||||
<ForServerVersion maxVersion="1.x">
|
||||
<DropdownItem onClick={togglePreview}>
|
||||
|
@ -87,20 +82,12 @@ const ShortUrlsRowMenu = (
|
|||
<PreviewModal url={completeShortUrl} isOpen={this.state.isPreviewModalOpen} toggle={togglePreview} />
|
||||
</ForServerVersion>
|
||||
|
||||
<DropdownItem onClick={toggleQrCode}>
|
||||
<FontAwesomeIcon icon={qrIcon} fixedWidth /> QR code
|
||||
<DropdownItem divider />
|
||||
|
||||
<DropdownItem className="short-urls-row-menu__dropdown-item--danger" onClick={toggleDelete}>
|
||||
<FontAwesomeIcon icon={deleteIcon} fixedWidth /> Delete short URL
|
||||
</DropdownItem>
|
||||
<QrCodeModal url={completeShortUrl} isOpen={this.state.isQrModalOpen} toggle={toggleQrCode} />
|
||||
|
||||
<ForServerVersion maxVersion="1.x">
|
||||
<DropdownItem divider />
|
||||
</ForServerVersion>
|
||||
|
||||
<CopyToClipboard text={completeShortUrl} onCopy={onCopyToClipboard}>
|
||||
<DropdownItem>
|
||||
<FontAwesomeIcon icon={copyIcon} fixedWidth /> Copy to clipboard
|
||||
</DropdownItem>
|
||||
</CopyToClipboard>
|
||||
<DeleteShortUrlModal shortUrl={shortUrl} isOpen={this.state.isDeleteModalOpen} toggle={toggleDelete} />
|
||||
</DropdownMenu>
|
||||
</ButtonDropdown>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@mixin vertical-align {
|
||||
@mixin vertical-align($extraTransforms: '') {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
transform: translateY(-50%) $extraTransforms;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import moment from 'moment';
|
|||
import Moment from 'react-moment';
|
||||
import { assoc, toString } from 'ramda';
|
||||
import { ExternalLink } from 'react-external-link';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import createShortUrlsRow from '../../../src/short-urls/helpers/ShortUrlsRow';
|
||||
import Tag from '../../../src/tags/helpers/Tag';
|
||||
|
||||
|
@ -87,17 +88,17 @@ describe('<ShortUrlsRow />', () => {
|
|||
});
|
||||
|
||||
it('updates state when copied to clipboard', () => {
|
||||
const col = wrapper.find('td').at(5);
|
||||
const menu = col.find(ShortUrlsRowMenu);
|
||||
const col = wrapper.find('td').at(1);
|
||||
const menu = col.find(CopyToClipboard);
|
||||
|
||||
expect(menu).toHaveLength(1);
|
||||
expect(stateFlagTimeout).not.toHaveBeenCalled();
|
||||
menu.simulate('copyToClipboard');
|
||||
menu.simulate('copy');
|
||||
expect(stateFlagTimeout).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows copy hint when state prop is true', () => {
|
||||
const isHidden = () => wrapper.find('td').at(5).find('.short-urls-row__copy-hint').prop('hidden');
|
||||
const isHidden = () => wrapper.find('td').at(1).find('.short-urls-row__copy-hint').prop('hidden');
|
||||
|
||||
expect(isHidden()).toEqual(true);
|
||||
wrapper.setState({ copiedToClipboard: true });
|
||||
|
|
|
@ -49,8 +49,8 @@ describe('<ShortUrlsRowMenu />', () => {
|
|||
const wrapper = createWrapper();
|
||||
const items = wrapper.find(DropdownItem);
|
||||
|
||||
expect(items).toHaveLength(9);
|
||||
expect(items.find('[divider]')).toHaveLength(2);
|
||||
expect(items).toHaveLength(7);
|
||||
expect(items.find('[divider]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('toggles state when toggling modal windows', () => {
|
||||
|
|
Loading…
Reference in a new issue