mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Added margin option to QR code component
This commit is contained in:
parent
f9da22c5a1
commit
c95cb144a8
4 changed files with 66 additions and 19 deletions
|
@ -1,6 +1,7 @@
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { DropdownItem, FormGroup, Modal, ModalBody, ModalHeader, Row } from 'reactstrap';
|
import { DropdownItem, FormGroup, Modal, ModalBody, ModalHeader, Row } from 'reactstrap';
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { ShortUrlModalProps } from '../data';
|
import { ShortUrlModalProps } from '../data';
|
||||||
import { ReachableServer } from '../../servers/data';
|
import { ReachableServer } from '../../servers/data';
|
||||||
import { versionMatch } from '../../utils/helpers/version';
|
import { versionMatch } from '../../utils/helpers/version';
|
||||||
|
@ -15,7 +16,7 @@ interface QrCodeModalConnectProps extends ShortUrlModalProps {
|
||||||
|
|
||||||
const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen, selectedServer }: QrCodeModalConnectProps) => {
|
const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen, selectedServer }: QrCodeModalConnectProps) => {
|
||||||
const [ size, setSize ] = useState(300);
|
const [ size, setSize ] = useState(300);
|
||||||
const [ margin ] = useState(0);
|
const [ margin, setMargin ] = useState(0);
|
||||||
const [ format, setFormat ] = useState<QrCodeFormat>('png');
|
const [ format, setFormat ] = useState<QrCodeFormat>('png');
|
||||||
const capabilities: QrCodeCapabilities = useMemo(() => ({
|
const capabilities: QrCodeCapabilities = useMemo(() => ({
|
||||||
useSizeInPath: !versionMatch(selectedServer.version, { minVersion: '2.5.0' }),
|
useSizeInPath: !versionMatch(selectedServer.version, { minVersion: '2.5.0' }),
|
||||||
|
@ -24,15 +25,16 @@ const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen, selectedServer }:
|
||||||
}), [ selectedServer ]);
|
}), [ selectedServer ]);
|
||||||
const qrCodeUrl = useMemo(
|
const qrCodeUrl = useMemo(
|
||||||
() => buildQrCodeUrl(shortUrl, { size, format, margin }, capabilities),
|
() => buildQrCodeUrl(shortUrl, { size, format, margin }, capabilities),
|
||||||
[ shortUrl, size, format, capabilities ],
|
[ shortUrl, size, format, margin, capabilities ],
|
||||||
);
|
);
|
||||||
|
const totalSize = useMemo(() => size + margin, [ size, margin ]);
|
||||||
const modalSize = useMemo(() => {
|
const modalSize = useMemo(() => {
|
||||||
if (size < 500) {
|
if (totalSize < 500) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return size < 800 ? 'lg' : 'xl';
|
return totalSize < 800 ? 'lg' : 'xl';
|
||||||
}, [ size ]);
|
}, [ totalSize ]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal isOpen={isOpen} toggle={toggle} centered size={modalSize}>
|
<Modal isOpen={isOpen} toggle={toggle} centered size={modalSize}>
|
||||||
|
@ -41,7 +43,13 @@ const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen, selectedServer }:
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Row className="mb-2">
|
<Row className="mb-2">
|
||||||
<div className={capabilities.svgIsSupported ? 'col-md-6' : 'col-12'}>
|
<div
|
||||||
|
className={classNames({
|
||||||
|
'col-md-4': capabilities.marginIsSupported && capabilities.svgIsSupported,
|
||||||
|
'col-md-6': (!capabilities.marginIsSupported && capabilities.svgIsSupported) || (capabilities.marginIsSupported && !capabilities.svgIsSupported),
|
||||||
|
'col-12': !capabilities.marginIsSupported && !capabilities.svgIsSupported,
|
||||||
|
})}
|
||||||
|
>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
<label className="mb-0">Size: {size}px</label>
|
<label className="mb-0">Size: {size}px</label>
|
||||||
<input
|
<input
|
||||||
|
@ -55,8 +63,24 @@ const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen, selectedServer }:
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
|
{capabilities.marginIsSupported && (
|
||||||
|
<div className={capabilities.svgIsSupported ? 'col-md-4' : 'col-md-6'}>
|
||||||
|
<FormGroup>
|
||||||
|
<label className="mb-0">Margin: {margin}px</label>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
className="form-control-range"
|
||||||
|
value={margin}
|
||||||
|
step={1}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
onChange={(e) => setMargin(Number(e.target.value))}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{capabilities.svgIsSupported && (
|
{capabilities.svgIsSupported && (
|
||||||
<div className="col-md-6">
|
<div className={capabilities.marginIsSupported ? 'col-md-4' : 'col-md-6'}>
|
||||||
<DropdownBtn text={`Format (${format})`}>
|
<DropdownBtn text={`Format (${format})`}>
|
||||||
<DropdownItem active={format === 'png'} onClick={() => setFormat('png')}>PNG</DropdownItem>
|
<DropdownItem active={format === 'png'} onClick={() => setFormat('png')}>PNG</DropdownItem>
|
||||||
<DropdownItem active={format === 'svg'} onClick={() => setFormat('svg')}>SVG</DropdownItem>
|
<DropdownItem active={format === 'svg'} onClick={() => setFormat('svg')}>SVG</DropdownItem>
|
||||||
|
|
|
@ -24,7 +24,7 @@ export const buildQrCodeUrl = (
|
||||||
const query = stringifyQuery({
|
const query = stringifyQuery({
|
||||||
size: useSizeInPath ? undefined : size,
|
size: useSizeInPath ? undefined : size,
|
||||||
format: svgIsSupported ? format : undefined,
|
format: svgIsSupported ? format : undefined,
|
||||||
margin: marginIsSupported ? margin : undefined,
|
margin: marginIsSupported && margin > 0 ? margin : undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
return `${baseUrl}${isEmpty(query) ? '' : `?${query}`}`;
|
return `${baseUrl}${isEmpty(query) ? '' : `?${query}`}`;
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { DropdownBtn } from '../../../src/utils/DropdownBtn';
|
||||||
describe('<QrCodeModal />', () => {
|
describe('<QrCodeModal />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
const shortUrl = 'https://doma.in/abc123';
|
const shortUrl = 'https://doma.in/abc123';
|
||||||
const createWrapper = (version = '2.5.0') => {
|
const createWrapper = (version = '2.6.0') => {
|
||||||
const selectedServer = Mock.of<ReachableServer>({ version });
|
const selectedServer = Mock.of<ReachableServer>({ version });
|
||||||
|
|
||||||
wrapper = shallow(
|
wrapper = shallow(
|
||||||
|
@ -37,11 +37,20 @@ describe('<QrCodeModal />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[ '2.3.0', '/qr-code/300' ],
|
[ '2.3.0', 0, '/qr-code/300' ],
|
||||||
[ '2.4.0', '/qr-code/300?format=png' ],
|
[ '2.4.0', 0, '/qr-code/300?format=png' ],
|
||||||
[ '2.5.0', '/qr-code?size=300&format=png' ],
|
[ '2.4.0', 10, '/qr-code/300?format=png' ],
|
||||||
])('displays an image with the QR code of the URL', (version, expectedUrl) => {
|
[ '2.5.0', 0, '/qr-code?size=300&format=png' ],
|
||||||
|
[ '2.6.0', 0, '/qr-code?size=300&format=png' ],
|
||||||
|
[ '2.6.0', 10, '/qr-code?size=300&format=png&margin=10' ],
|
||||||
|
])('displays an image with the QR code of the URL', (version, margin, expectedUrl) => {
|
||||||
const wrapper = createWrapper(version);
|
const wrapper = createWrapper(version);
|
||||||
|
const formControls = wrapper.find('.form-control-range');
|
||||||
|
|
||||||
|
if (formControls.length > 1) {
|
||||||
|
formControls.at(1).simulate('change', { target: { value: `${margin}` } });
|
||||||
|
}
|
||||||
|
|
||||||
const modalBody = wrapper.find(ModalBody);
|
const modalBody = wrapper.find(ModalBody);
|
||||||
const img = modalBody.find('img');
|
const img = modalBody.find('img');
|
||||||
const linkInBody = modalBody.find(ExternalLink);
|
const linkInBody = modalBody.find(ExternalLink);
|
||||||
|
@ -53,23 +62,31 @@ describe('<QrCodeModal />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[ 530, 'lg' ],
|
[ 530, 0, 'lg' ],
|
||||||
[ 200, undefined ],
|
[ 200, 0, undefined ],
|
||||||
[ 830, 'xl' ],
|
[ 830, 0, 'xl' ],
|
||||||
])('renders expected size', (size, modalSize) => {
|
[ 430, 80, 'lg' ],
|
||||||
|
[ 200, 50, undefined ],
|
||||||
|
[ 720, 100, 'xl' ],
|
||||||
|
])('renders expected size', (size, margin, modalSize) => {
|
||||||
const wrapper = createWrapper();
|
const wrapper = createWrapper();
|
||||||
const sizeInput = wrapper.find('.form-control-range');
|
const formControls = wrapper.find('.form-control-range');
|
||||||
|
const sizeInput = formControls.at(0);
|
||||||
|
const marginInput = formControls.at(1);
|
||||||
|
|
||||||
sizeInput.simulate('change', { target: { value: `${size}` } });
|
sizeInput.simulate('change', { target: { value: `${size}` } });
|
||||||
|
marginInput.simulate('change', { target: { value: `${margin}` } });
|
||||||
|
|
||||||
expect(wrapper.find('.mt-2').text()).toEqual(`${size}x${size}`);
|
expect(wrapper.find('.mt-2').text()).toEqual(`${size}x${size}`);
|
||||||
expect(wrapper.find('label').text()).toEqual(`Size: ${size}px`);
|
expect(wrapper.find('label').at(0).text()).toEqual(`Size: ${size}px`);
|
||||||
|
expect(wrapper.find('label').at(1).text()).toEqual(`Margin: ${margin}px`);
|
||||||
expect(wrapper.find(Modal).prop('size')).toEqual(modalSize);
|
expect(wrapper.find(Modal).prop('size')).toEqual(modalSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[ '2.3.0', 0, 'col-12' ],
|
[ '2.3.0', 0, 'col-12' ],
|
||||||
[ '2.4.0', 1, 'col-md-6' ],
|
[ '2.4.0', 1, 'col-md-6' ],
|
||||||
|
[ '2.6.0', 1, 'col-md-4' ],
|
||||||
])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => {
|
])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => {
|
||||||
const wrapper = createWrapper(version);
|
const wrapper = createWrapper(version);
|
||||||
const dropdown = wrapper.find(DropdownBtn);
|
const dropdown = wrapper.find(DropdownBtn);
|
||||||
|
|
|
@ -57,6 +57,12 @@ describe('qrCodes', () => {
|
||||||
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: true },
|
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: true },
|
||||||
'shlink.io/qr-code/456?format=png&margin=10',
|
'shlink.io/qr-code/456?format=png&margin=10',
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
'shlink.io',
|
||||||
|
{ size: 456, format: 'png' as QrCodeFormat, margin: 0 },
|
||||||
|
{ useSizeInPath: true, svgIsSupported: true, marginIsSupported: true },
|
||||||
|
'shlink.io/qr-code/456?format=png',
|
||||||
|
],
|
||||||
])('builds expected URL based in params', (shortUrl, options, capabilities, expectedUrl) => {
|
])('builds expected URL based in params', (shortUrl, options, capabilities, expectedUrl) => {
|
||||||
expect(buildQrCodeUrl(shortUrl, options, capabilities)).toEqual(expectedUrl);
|
expect(buildQrCodeUrl(shortUrl, options, capabilities)).toEqual(expectedUrl);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue