mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Removed references to feature checks for version 2.8
This commit is contained in:
parent
bfcdf703e8
commit
815e06809a
8 changed files with 33 additions and 66 deletions
|
@ -12,7 +12,6 @@ import { NavLink, NavLinkProps, useLocation } from 'react-router-dom';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { DeleteServerButtonProps } from '../servers/DeleteServerButton';
|
import { DeleteServerButtonProps } from '../servers/DeleteServerButton';
|
||||||
import { isServerWithId, SelectedServer } from '../servers/data';
|
import { isServerWithId, SelectedServer } from '../servers/data';
|
||||||
import { supportsDomainRedirects } from '../utils/helpers/features';
|
|
||||||
import './AsideMenu.scss';
|
import './AsideMenu.scss';
|
||||||
|
|
||||||
export interface AsideMenuProps {
|
export interface AsideMenuProps {
|
||||||
|
@ -40,7 +39,6 @@ export const AsideMenu = (DeleteServerButton: FC<DeleteServerButtonProps>) => (
|
||||||
const hasId = isServerWithId(selectedServer);
|
const hasId = isServerWithId(selectedServer);
|
||||||
const serverId = hasId ? selectedServer.id : '';
|
const serverId = hasId ? selectedServer.id : '';
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const addManageDomainsLink = supportsDomainRedirects(selectedServer);
|
|
||||||
const asideClass = classNames('aside-menu', {
|
const asideClass = classNames('aside-menu', {
|
||||||
'aside-menu--hidden': !showOnMobile,
|
'aside-menu--hidden': !showOnMobile,
|
||||||
});
|
});
|
||||||
|
@ -68,12 +66,10 @@ export const AsideMenu = (DeleteServerButton: FC<DeleteServerButtonProps>) => (
|
||||||
<FontAwesomeIcon fixedWidth icon={tagsIcon} />
|
<FontAwesomeIcon fixedWidth icon={tagsIcon} />
|
||||||
<span className="aside-menu__item-text">Manage tags</span>
|
<span className="aside-menu__item-text">Manage tags</span>
|
||||||
</AsideMenuItem>
|
</AsideMenuItem>
|
||||||
{addManageDomainsLink && (
|
|
||||||
<AsideMenuItem to={buildPath('/manage-domains')}>
|
<AsideMenuItem to={buildPath('/manage-domains')}>
|
||||||
<FontAwesomeIcon fixedWidth icon={domainsIcon} />
|
<FontAwesomeIcon fixedWidth icon={domainsIcon} />
|
||||||
<span className="aside-menu__item-text">Manage domains</span>
|
<span className="aside-menu__item-text">Manage domains</span>
|
||||||
</AsideMenuItem>
|
</AsideMenuItem>
|
||||||
)}
|
|
||||||
<AsideMenuItem to={buildPath('/edit')} className="aside-menu__item--push">
|
<AsideMenuItem to={buildPath('/edit')} className="aside-menu__item--push">
|
||||||
<FontAwesomeIcon fixedWidth icon={editIcon} />
|
<FontAwesomeIcon fixedWidth icon={editIcon} />
|
||||||
<span className="aside-menu__item-text">Edit this server</span>
|
<span className="aside-menu__item-text">Edit this server</span>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
||||||
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
||||||
import { supportsDomainRedirects, supportsDomainVisits, supportsNonOrphanVisits } from '../utils/helpers/features';
|
import { supportsDomainVisits, supportsNonOrphanVisits } from '../utils/helpers/features';
|
||||||
import { isReachableServer } from '../servers/data';
|
import { isReachableServer } from '../servers/data';
|
||||||
import { NotFound } from './NotFound';
|
import { NotFound } from './NotFound';
|
||||||
import { AsideMenuProps } from './AsideMenu';
|
import { AsideMenuProps } from './AsideMenu';
|
||||||
|
@ -47,7 +47,6 @@ export const MenuLayout = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const addNonOrphanVisitsRoute = supportsNonOrphanVisits(selectedServer);
|
const addNonOrphanVisitsRoute = supportsNonOrphanVisits(selectedServer);
|
||||||
const addManageDomainsRoute = supportsDomainRedirects(selectedServer);
|
|
||||||
const addDomainVisitsRoute = supportsDomainVisits(selectedServer);
|
const addDomainVisitsRoute = supportsDomainVisits(selectedServer);
|
||||||
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
|
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
|
||||||
const swipeableProps = useSwipeable(showSidebar, hideSidebar);
|
const swipeableProps = useSwipeable(showSidebar, hideSidebar);
|
||||||
|
@ -73,7 +72,7 @@ export const MenuLayout = (
|
||||||
<Route path="/orphan-visits/*" element={<OrphanVisits />} />
|
<Route path="/orphan-visits/*" element={<OrphanVisits />} />
|
||||||
{addNonOrphanVisitsRoute && <Route path="/non-orphan-visits/*" element={<NonOrphanVisits />} />}
|
{addNonOrphanVisitsRoute && <Route path="/non-orphan-visits/*" element={<NonOrphanVisits />} />}
|
||||||
<Route path="/manage-tags" element={<TagsList />} />
|
<Route path="/manage-tags" element={<TagsList />} />
|
||||||
{addManageDomainsRoute && <Route path="/manage-domains" element={<ManageDomains />} />}
|
<Route path="/manage-domains" element={<ManageDomains />} />
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
element={<NotFound to={`/server/${selectedServer.id}/list-short-urls/1`}>List short URLs</NotFound>}
|
element={<NotFound to={`/server/${selectedServer.id}/list-short-urls/1`}>List short URLs</NotFound>}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import { ExternalLink } from 'react-external-link';
|
||||||
import { ShortUrlModalProps } from '../data';
|
import { ShortUrlModalProps } from '../data';
|
||||||
import { SelectedServer } from '../../servers/data';
|
import { SelectedServer } from '../../servers/data';
|
||||||
import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon';
|
import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon';
|
||||||
import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes';
|
import { buildQrCodeUrl, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes';
|
||||||
import { supportsNonRestCors, supportsQrErrorCorrection } from '../../utils/helpers/features';
|
import { supportsNonRestCors } from '../../utils/helpers/features';
|
||||||
import { ImageDownloader } from '../../common/services/ImageDownloader';
|
import { ImageDownloader } from '../../common/services/ImageDownloader';
|
||||||
import { QrFormatDropdown } from './qr-codes/QrFormatDropdown';
|
import { QrFormatDropdown } from './qr-codes/QrFormatDropdown';
|
||||||
import { QrErrorCorrectionDropdown } from './qr-codes/QrErrorCorrectionDropdown';
|
import { QrErrorCorrectionDropdown } from './qr-codes/QrErrorCorrectionDropdown';
|
||||||
|
@ -24,14 +24,10 @@ export const QrCodeModal = (imageDownloader: ImageDownloader) => (
|
||||||
const [margin, setMargin] = useState(0);
|
const [margin, setMargin] = useState(0);
|
||||||
const [format, setFormat] = useState<QrCodeFormat>('png');
|
const [format, setFormat] = useState<QrCodeFormat>('png');
|
||||||
const [errorCorrection, setErrorCorrection] = useState<QrErrorCorrection>('L');
|
const [errorCorrection, setErrorCorrection] = useState<QrErrorCorrection>('L');
|
||||||
const capabilities: QrCodeCapabilities = useMemo(() => ({
|
|
||||||
errorCorrectionIsSupported: supportsQrErrorCorrection(selectedServer),
|
|
||||||
}), [selectedServer]);
|
|
||||||
const displayDownloadBtn = supportsNonRestCors(selectedServer);
|
const displayDownloadBtn = supportsNonRestCors(selectedServer);
|
||||||
const willRenderThreeControls = !capabilities.errorCorrectionIsSupported;
|
|
||||||
const qrCodeUrl = useMemo(
|
const qrCodeUrl = useMemo(
|
||||||
() => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }, capabilities),
|
() => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }),
|
||||||
[shortUrl, size, format, margin, errorCorrection, capabilities],
|
[shortUrl, size, format, margin, errorCorrection],
|
||||||
);
|
);
|
||||||
const totalSize = useMemo(() => size + margin, [size, margin]);
|
const totalSize = useMemo(() => size + margin, [size, margin]);
|
||||||
const modalSize = useMemo(() => {
|
const modalSize = useMemo(() => {
|
||||||
|
@ -49,7 +45,7 @@ export const QrCodeModal = (imageDownloader: ImageDownloader) => (
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<Row>
|
<Row>
|
||||||
<FormGroup className={`d-grid ${willRenderThreeControls ? 'col-md-4' : 'col-md-6'}`}>
|
<FormGroup className="d-grid col-md-4">
|
||||||
<label>Size: {size}px</label>
|
<label>Size: {size}px</label>
|
||||||
<input
|
<input
|
||||||
type="range"
|
type="range"
|
||||||
|
@ -61,7 +57,7 @@ export const QrCodeModal = (imageDownloader: ImageDownloader) => (
|
||||||
onChange={(e) => setSize(Number(e.target.value))}
|
onChange={(e) => setSize(Number(e.target.value))}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup className={`d-grid ${willRenderThreeControls ? 'col-md-4' : 'col-md-6'}`}>
|
<FormGroup className="d-grid col-md-4">
|
||||||
<label htmlFor="marginControl">Margin: {margin}px</label>
|
<label htmlFor="marginControl">Margin: {margin}px</label>
|
||||||
<input
|
<input
|
||||||
id="marginControl"
|
id="marginControl"
|
||||||
|
@ -74,14 +70,12 @@ export const QrCodeModal = (imageDownloader: ImageDownloader) => (
|
||||||
onChange={(e) => setMargin(Number(e.target.value))}
|
onChange={(e) => setMargin(Number(e.target.value))}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup className={willRenderThreeControls ? 'col-md-4' : 'col-md-6'}>
|
<FormGroup className="d-grid col-md-4">
|
||||||
<QrFormatDropdown format={format} setFormat={setFormat} />
|
<QrFormatDropdown format={format} setFormat={setFormat} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
{capabilities.errorCorrectionIsSupported && (
|
|
||||||
<FormGroup className="col-md-6">
|
<FormGroup className="col-md-6">
|
||||||
<QrErrorCorrectionDropdown errorCorrection={errorCorrection} setErrorCorrection={setErrorCorrection} />
|
<QrErrorCorrectionDropdown errorCorrection={errorCorrection} setErrorCorrection={setErrorCorrection} />
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)}
|
|
||||||
</Row>
|
</Row>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="mb-3">
|
<div className="mb-3">
|
||||||
|
|
|
@ -6,8 +6,6 @@ const serverMatchesMinVersion = (minVersion: SemVerPattern) => (selectedServer:
|
||||||
|
|
||||||
export const supportsBotVisits = serverMatchesMinVersion('2.7.0');
|
export const supportsBotVisits = serverMatchesMinVersion('2.7.0');
|
||||||
export const supportsCrawlableVisits = supportsBotVisits;
|
export const supportsCrawlableVisits = supportsBotVisits;
|
||||||
export const supportsQrErrorCorrection = serverMatchesMinVersion('2.8.0');
|
|
||||||
export const supportsDomainRedirects = supportsQrErrorCorrection;
|
|
||||||
export const supportsForwardQuery = serverMatchesMinVersion('2.9.0');
|
export const supportsForwardQuery = serverMatchesMinVersion('2.9.0');
|
||||||
export const supportsNonRestCors = supportsForwardQuery;
|
export const supportsNonRestCors = supportsForwardQuery;
|
||||||
export const supportsDefaultDomainRedirectsEdition = serverMatchesMinVersion('2.10.0');
|
export const supportsDefaultDomainRedirectsEdition = serverMatchesMinVersion('2.10.0');
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { isEmpty } from 'ramda';
|
import { isEmpty } from 'ramda';
|
||||||
import { stringifyQuery } from './query';
|
import { stringifyQuery } from './query';
|
||||||
|
|
||||||
export interface QrCodeCapabilities {
|
|
||||||
errorCorrectionIsSupported: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type QrCodeFormat = 'svg' | 'png';
|
export type QrCodeFormat = 'svg' | 'png';
|
||||||
|
|
||||||
export type QrErrorCorrection = 'L' | 'M' | 'Q' | 'H';
|
export type QrErrorCorrection = 'L' | 'M' | 'Q' | 'H';
|
||||||
|
@ -16,17 +12,11 @@ export interface QrCodeOptions {
|
||||||
errorCorrection: QrErrorCorrection;
|
errorCorrection: QrErrorCorrection;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildQrCodeUrl = (
|
export const buildQrCodeUrl = (shortUrl: string, { margin, ...options }: QrCodeOptions): string => {
|
||||||
shortUrl: string,
|
|
||||||
{ size, format, margin, errorCorrection }: QrCodeOptions,
|
|
||||||
{ errorCorrectionIsSupported }: QrCodeCapabilities,
|
|
||||||
): string => {
|
|
||||||
const baseUrl = `${shortUrl}/qr-code`;
|
const baseUrl = `${shortUrl}/qr-code`;
|
||||||
const query = stringifyQuery({
|
const query = stringifyQuery({
|
||||||
size,
|
...options,
|
||||||
format,
|
|
||||||
margin: margin > 0 ? margin : undefined,
|
margin: margin > 0 ? margin : undefined,
|
||||||
errorCorrection: errorCorrectionIsSupported ? errorCorrection : undefined,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return `${baseUrl}${isEmpty(query) ? '' : `?${query}`}`;
|
return `${baseUrl}${isEmpty(query) ? '' : `?${query}`}`;
|
||||||
|
|
|
@ -3,26 +3,22 @@ import { Mock } from 'ts-mockery';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { AsideMenu as createAsideMenu } from '../../src/common/AsideMenu';
|
import { AsideMenu as createAsideMenu } from '../../src/common/AsideMenu';
|
||||||
import { ReachableServer } from '../../src/servers/data';
|
import { ReachableServer } from '../../src/servers/data';
|
||||||
import { SemVer } from '../../src/utils/helpers/version';
|
|
||||||
|
|
||||||
describe('<AsideMenu />', () => {
|
describe('<AsideMenu />', () => {
|
||||||
const AsideMenu = createAsideMenu(() => <>DeleteServerButton</>);
|
const AsideMenu = createAsideMenu(() => <>DeleteServerButton</>);
|
||||||
const setUp = (version: SemVer, id: string | false = 'abc123') => render(
|
const setUp = (id: string | false = 'abc123') => render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<AsideMenu selectedServer={Mock.of<ReachableServer>({ id: id || undefined, version })} />
|
<AsideMenu selectedServer={Mock.of<ReachableServer>({ id: id || undefined, version: '2.8.0' })} />
|
||||||
</MemoryRouter>,
|
</MemoryRouter>,
|
||||||
);
|
);
|
||||||
|
|
||||||
it.each([
|
it('contains links to different sections', () => {
|
||||||
['2.7.0' as SemVer, 5],
|
setUp();
|
||||||
['2.8.0' as SemVer, 6],
|
|
||||||
])('contains links to different sections', (version, expectedAmountOfLinks) => {
|
|
||||||
setUp(version);
|
|
||||||
|
|
||||||
const links = screen.getAllByRole('link');
|
const links = screen.getAllByRole('link');
|
||||||
|
|
||||||
expect.assertions(links.length + 1);
|
expect.assertions(links.length + 1);
|
||||||
expect(links).toHaveLength(expectedAmountOfLinks);
|
expect(links).toHaveLength(6);
|
||||||
links.forEach((link) => expect(link.getAttribute('href')).toContain('abc123'));
|
links.forEach((link) => expect(link.getAttribute('href')).toContain('abc123'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -30,7 +26,7 @@ describe('<AsideMenu />', () => {
|
||||||
['abc', true],
|
['abc', true],
|
||||||
[false, false],
|
[false, false],
|
||||||
])('contains a button to delete server if appropriate', (id, shouldHaveBtn) => {
|
])('contains a button to delete server if appropriate', (id, shouldHaveBtn) => {
|
||||||
setUp('2.8.0', id as string | false);
|
setUp(id as string | false);
|
||||||
|
|
||||||
if (shouldHaveBtn) {
|
if (shouldHaveBtn) {
|
||||||
expect(screen.getByText('DeleteServerButton')).toBeInTheDocument();
|
expect(screen.getByText('DeleteServerButton')).toBeInTheDocument();
|
||||||
|
|
|
@ -77,7 +77,6 @@ describe('<MenuLayout />', () => {
|
||||||
['3.1.0' as SemVer, '/domain/domain.com/visits/foo', 'DomainVisits'],
|
['3.1.0' as SemVer, '/domain/domain.com/visits/foo', 'DomainVisits'],
|
||||||
['2.10.0' as SemVer, '/non-orphan-visits/foo', 'Oops! We could not find requested route.'],
|
['2.10.0' as SemVer, '/non-orphan-visits/foo', 'Oops! We could not find requested route.'],
|
||||||
['3.0.0' as SemVer, '/non-orphan-visits/foo', 'NonOrphanVisits'],
|
['3.0.0' as SemVer, '/non-orphan-visits/foo', 'NonOrphanVisits'],
|
||||||
['2.7.0' as SemVer, '/manage-domains', 'Oops! We could not find requested route.'],
|
|
||||||
['2.8.0' as SemVer, '/manage-domains', 'ManageDomains'],
|
['2.8.0' as SemVer, '/manage-domains', 'ManageDomains'],
|
||||||
])(
|
])(
|
||||||
'renders expected component based on location and server version',
|
'renders expected component based on location and server version',
|
||||||
|
|
|
@ -11,7 +11,7 @@ describe('<QrCodeModal />', () => {
|
||||||
const saveImage = jest.fn().mockReturnValue(Promise.resolve());
|
const saveImage = jest.fn().mockReturnValue(Promise.resolve());
|
||||||
const QrCodeModal = createQrCodeModal(Mock.of<ImageDownloader>({ saveImage }));
|
const QrCodeModal = createQrCodeModal(Mock.of<ImageDownloader>({ saveImage }));
|
||||||
const shortUrl = 'https://doma.in/abc123';
|
const shortUrl = 'https://doma.in/abc123';
|
||||||
const setUp = (version: SemVer = '2.6.0') => renderWithEvents(
|
const setUp = (version: SemVer = '2.8.0') => renderWithEvents(
|
||||||
<QrCodeModal
|
<QrCodeModal
|
||||||
isOpen
|
isOpen
|
||||||
shortUrl={Mock.of<ShortUrl>({ shortUrl })}
|
shortUrl={Mock.of<ShortUrl>({ shortUrl })}
|
||||||
|
@ -32,12 +32,10 @@ describe('<QrCodeModal />', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
['2.5.0' as SemVer, 0, '/qr-code?size=300&format=png'],
|
[10, '/qr-code?size=300&format=png&errorCorrection=L&margin=10'],
|
||||||
['2.6.0' as SemVer, 0, '/qr-code?size=300&format=png'],
|
[0, '/qr-code?size=300&format=png&errorCorrection=L'],
|
||||||
['2.6.0' as SemVer, 10, '/qr-code?size=300&format=png&margin=10'],
|
])('displays an image with the QR code of the URL', async (margin, expectedUrl) => {
|
||||||
['2.8.0' as SemVer, 0, '/qr-code?size=300&format=png&errorCorrection=L'],
|
const { container } = setUp();
|
||||||
])('displays an image with the QR code of the URL', async (version, margin, expectedUrl) => {
|
|
||||||
const { container } = setUp(version);
|
|
||||||
const marginControl = container.parentNode?.querySelectorAll('.form-control-range').item(1);
|
const marginControl = container.parentNode?.querySelectorAll('.form-control-range').item(1);
|
||||||
|
|
||||||
if (marginControl) {
|
if (marginControl) {
|
||||||
|
@ -69,16 +67,13 @@ describe('<QrCodeModal />', () => {
|
||||||
modalSize && expect(screen.getByRole('document')).toHaveClass(`modal-${modalSize}`);
|
modalSize && expect(screen.getByRole('document')).toHaveClass(`modal-${modalSize}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each([
|
it('shows expected components based on server version', () => {
|
||||||
['2.6.0' as SemVer, 1, 'col-md-4'],
|
const { container } = setUp();
|
||||||
['2.8.0' as SemVer, 2, 'col-md-6'],
|
|
||||||
])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => {
|
|
||||||
const { container } = setUp(version);
|
|
||||||
const dropdowns = screen.getAllByRole('button');
|
const dropdowns = screen.getAllByRole('button');
|
||||||
const firstCol = container.parentNode?.querySelectorAll('.d-grid').item(0);
|
const firstCol = container.parentNode?.querySelectorAll('.d-grid').item(0);
|
||||||
|
|
||||||
expect(dropdowns).toHaveLength(expectedAmountOfDropdowns + 1); // Add one because of the close button
|
expect(dropdowns).toHaveLength(2 + 1); // Add one because of the close button
|
||||||
expect(firstCol).toHaveClass(expectedRangeClass);
|
expect(firstCol).toHaveClass('col-md-4');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('saves the QR code image when clicking the Download button', async () => {
|
it('saves the QR code image when clicking the Download button', async () => {
|
||||||
|
|
Loading…
Reference in a new issue