mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Ensured versions footer has proper classes based on sidebar status, not selected server
This commit is contained in:
parent
4f731d9de8
commit
73d4707420
11 changed files with 76 additions and 22 deletions
|
@ -11,6 +11,11 @@ import NotFound from './NotFound';
|
||||||
import { AsideMenuProps } from './AsideMenu';
|
import { AsideMenuProps } from './AsideMenu';
|
||||||
import './MenuLayout.scss';
|
import './MenuLayout.scss';
|
||||||
|
|
||||||
|
interface MenuLayoutProps {
|
||||||
|
sidebarRendered: Function;
|
||||||
|
sidebarNotRendered: Function;
|
||||||
|
}
|
||||||
|
|
||||||
const MenuLayout = (
|
const MenuLayout = (
|
||||||
TagsList: FC,
|
TagsList: FC,
|
||||||
ShortUrlsList: FC,
|
ShortUrlsList: FC,
|
||||||
|
@ -24,13 +29,19 @@ const MenuLayout = (
|
||||||
Overview: FC,
|
Overview: FC,
|
||||||
EditShortUrl: FC,
|
EditShortUrl: FC,
|
||||||
ManageDomains: FC,
|
ManageDomains: FC,
|
||||||
) => withSelectedServer(({ selectedServer }) => {
|
) => withSelectedServer<MenuLayoutProps>(({ selectedServer, sidebarNotRendered, sidebarRendered }) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
|
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
|
||||||
|
const showContent = isReachableServer(selectedServer);
|
||||||
|
|
||||||
useEffect(() => hideSidebar(), [ location ]);
|
useEffect(() => hideSidebar(), [ location ]);
|
||||||
|
useEffect(() => {
|
||||||
|
showContent && sidebarRendered();
|
||||||
|
|
||||||
if (!isReachableServer(selectedServer)) {
|
return () => sidebarNotRendered();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!showContent) {
|
||||||
return <ServerError />;
|
return <ServerError />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { pipe } from 'ramda';
|
import { pipe } from 'ramda';
|
||||||
import { ExternalLink } from 'react-external-link';
|
import { ExternalLink } from 'react-external-link';
|
||||||
import { versionToPrintable, versionToSemVer } from '../utils/helpers/version';
|
import { versionToPrintable, versionToSemVer } from '../utils/helpers/version';
|
||||||
import { isReachableServer } from '../servers/data';
|
import { isReachableServer, SelectedServer } from '../servers/data';
|
||||||
import { ShlinkVersionsContainerProps } from './ShlinkVersionsContainer';
|
|
||||||
|
|
||||||
const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%';
|
const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%';
|
||||||
const normalizeVersion = pipe(versionToSemVer(), versionToPrintable);
|
const normalizeVersion = pipe(versionToSemVer(), versionToPrintable);
|
||||||
|
|
||||||
export interface ShlinkVersionsProps extends ShlinkVersionsContainerProps {
|
export interface ShlinkVersionsProps {
|
||||||
|
selectedServer: SelectedServer;
|
||||||
clientVersion?: string;
|
clientVersion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
@import '../utils/base';
|
@import '../utils/base';
|
||||||
|
|
||||||
.shlink-versions-container--with-server {
|
.shlink-versions-container--with-sidebar {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
|
||||||
@media (min-width: $mdMin) {
|
@media (min-width: $mdMin) {
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isReachableServer, SelectedServer } from '../servers/data';
|
import { SelectedServer } from '../servers/data';
|
||||||
import ShlinkVersions from './ShlinkVersions';
|
import ShlinkVersions from './ShlinkVersions';
|
||||||
|
import { Sidebar } from './reducers/sidebar';
|
||||||
import './ShlinkVersionsContainer.scss';
|
import './ShlinkVersionsContainer.scss';
|
||||||
|
|
||||||
export interface ShlinkVersionsContainerProps {
|
export interface ShlinkVersionsContainerProps {
|
||||||
selectedServer: SelectedServer;
|
selectedServer: SelectedServer;
|
||||||
|
sidebar: Sidebar;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ShlinkVersionsContainer = ({ selectedServer }: ShlinkVersionsContainerProps) => {
|
const ShlinkVersionsContainer = ({ selectedServer, sidebar }: ShlinkVersionsContainerProps) => {
|
||||||
const classes = classNames('text-center', {
|
const classes = classNames('text-center', {
|
||||||
'shlink-versions-container--with-server': isReachableServer(selectedServer),
|
'shlink-versions-container--with-sidebar': sidebar.hasSidebar,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
27
src/common/reducers/sidebar.ts
Normal file
27
src/common/reducers/sidebar.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Action } from 'redux';
|
||||||
|
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
|
||||||
|
|
||||||
|
/* eslint-disable padding-line-between-statements */
|
||||||
|
export const SIDEBAR_RENDERED = 'shlink/common/SIDEBAR_RENDERED';
|
||||||
|
export const SIDEBAR_NOT_RENDERED = 'shlink/common/SIDEBAR_NOT_RENDERED';
|
||||||
|
/* eslint-enable padding-line-between-statements */
|
||||||
|
|
||||||
|
export interface Sidebar {
|
||||||
|
hasSidebar: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SidebarRenderedAction = Action<string>;
|
||||||
|
type SidebarNotRenderedAction = Action<string>;
|
||||||
|
|
||||||
|
const initialState: Sidebar = {
|
||||||
|
hasSidebar: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default buildReducer<Sidebar, SidebarRenderedAction & SidebarNotRenderedAction>({
|
||||||
|
[SIDEBAR_RENDERED]: () => ({ hasSidebar: true }),
|
||||||
|
[SIDEBAR_NOT_RENDERED]: () => ({ hasSidebar: false }),
|
||||||
|
}, initialState);
|
||||||
|
|
||||||
|
export const sidebarRendered = buildActionCreator(SIDEBAR_RENDERED);
|
||||||
|
|
||||||
|
export const sidebarNotRendered = buildActionCreator(SIDEBAR_NOT_RENDERED);
|
|
@ -9,6 +9,7 @@ import ErrorHandler from '../ErrorHandler';
|
||||||
import ShlinkVersionsContainer from '../ShlinkVersionsContainer';
|
import ShlinkVersionsContainer from '../ShlinkVersionsContainer';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
|
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
|
||||||
|
import { sidebarNotRendered, sidebarRendered } from '../reducers/sidebar';
|
||||||
import { ImageDownloader } from './ImageDownloader';
|
import { ImageDownloader } from './ImageDownloader';
|
||||||
|
|
||||||
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
|
@ -44,14 +45,18 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
'EditShortUrl',
|
'EditShortUrl',
|
||||||
'ManageDomains',
|
'ManageDomains',
|
||||||
);
|
);
|
||||||
bottle.decorator('MenuLayout', connect([ 'selectedServer' ], [ 'selectServer' ]));
|
bottle.decorator('MenuLayout', connect([ 'selectedServer' ], [ 'selectServer', 'sidebarRendered', 'sidebarNotRendered' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton');
|
bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton');
|
||||||
|
|
||||||
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
|
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
|
||||||
bottle.decorator('ShlinkVersionsContainer', connect([ 'selectedServer' ]));
|
bottle.decorator('ShlinkVersionsContainer', connect([ 'selectedServer', 'sidebar' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console');
|
bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console');
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
bottle.serviceFactory('sidebarRendered', () => sidebarRendered);
|
||||||
|
bottle.serviceFactory('sidebarNotRendered', () => sidebarNotRendered);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default provideServices;
|
export default provideServices;
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { TagVisits } from '../visits/reducers/tagVisits';
|
||||||
import { DomainsList } from '../domains/reducers/domainsList';
|
import { DomainsList } from '../domains/reducers/domainsList';
|
||||||
import { VisitsOverview } from '../visits/reducers/visitsOverview';
|
import { VisitsOverview } from '../visits/reducers/visitsOverview';
|
||||||
import { VisitsInfo } from '../visits/types';
|
import { VisitsInfo } from '../visits/types';
|
||||||
|
import { Sidebar } from '../common/reducers/sidebar';
|
||||||
|
|
||||||
export interface ShlinkState {
|
export interface ShlinkState {
|
||||||
servers: ServersMap;
|
servers: ServersMap;
|
||||||
|
@ -35,6 +36,7 @@ export interface ShlinkState {
|
||||||
domainsList: DomainsList;
|
domainsList: DomainsList;
|
||||||
visitsOverview: VisitsOverview;
|
visitsOverview: VisitsOverview;
|
||||||
appUpdated: boolean;
|
appUpdated: boolean;
|
||||||
|
sidebar: Sidebar;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;
|
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;
|
||||||
|
|
|
@ -18,6 +18,7 @@ import settingsReducer from '../settings/reducers/settings';
|
||||||
import domainsListReducer from '../domains/reducers/domainsList';
|
import domainsListReducer from '../domains/reducers/domainsList';
|
||||||
import visitsOverviewReducer from '../visits/reducers/visitsOverview';
|
import visitsOverviewReducer from '../visits/reducers/visitsOverview';
|
||||||
import appUpdatesReducer from '../app/reducers/appUpdates';
|
import appUpdatesReducer from '../app/reducers/appUpdates';
|
||||||
|
import sidebarReducer from '../common/reducers/sidebar';
|
||||||
import { ShlinkState } from '../container/types';
|
import { ShlinkState } from '../container/types';
|
||||||
|
|
||||||
export default combineReducers<ShlinkState>({
|
export default combineReducers<ShlinkState>({
|
||||||
|
@ -40,4 +41,5 @@ export default combineReducers<ShlinkState>({
|
||||||
domainsList: domainsListReducer,
|
domainsList: domainsListReducer,
|
||||||
visitsOverview: visitsOverviewReducer,
|
visitsOverview: visitsOverviewReducer,
|
||||||
appUpdated: appUpdatesReducer,
|
appUpdated: appUpdatesReducer,
|
||||||
|
sidebar: sidebarReducer,
|
||||||
});
|
});
|
||||||
|
|
|
@ -43,7 +43,6 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
bottle.decorator('CreateServer', connect([ 'selectedServer', 'servers' ], [ 'createServer', 'resetSelectedServer' ]));
|
bottle.decorator('CreateServer', connect([ 'selectedServer', 'servers' ], [ 'createServer', 'resetSelectedServer' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('EditServer', EditServer, 'ServerError');
|
bottle.serviceFactory('EditServer', EditServer, 'ServerError');
|
||||||
bottle.decorator('EditServer', withoutSelectedServer);
|
|
||||||
bottle.decorator('EditServer', connect([ 'selectedServer' ], [ 'editServer', 'selectServer', 'resetSelectedServer' ]));
|
bottle.decorator('EditServer', connect([ 'selectedServer' ], [ 'editServer', 'selectServer', 'resetSelectedServer' ]));
|
||||||
|
|
||||||
bottle.serviceFactory('ServersDropdown', () => ServersDropdown);
|
bottle.serviceFactory('ServersDropdown', () => ServersDropdown);
|
||||||
|
|
|
@ -20,7 +20,14 @@ describe('<MenuLayout />', () => {
|
||||||
const createWrapper = (selectedServer: SelectedServer) => {
|
const createWrapper = (selectedServer: SelectedServer) => {
|
||||||
(useParams as any).mockReturnValue({ serverId: 'abc123' });
|
(useParams as any).mockReturnValue({ serverId: 'abc123' });
|
||||||
|
|
||||||
wrapper = shallow(<MenuLayout selectServer={jest.fn()} selectedServer={selectedServer} />);
|
wrapper = shallow(
|
||||||
|
<MenuLayout
|
||||||
|
sidebarNotRendered={jest.fn()}
|
||||||
|
sidebarRendered={jest.fn}
|
||||||
|
selectServer={jest.fn()}
|
||||||
|
selectedServer={selectedServer}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import { shallow, ShallowWrapper } from 'enzyme';
|
import { shallow, ShallowWrapper } from 'enzyme';
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import ShlinkVersionsContainer from '../../src/common/ShlinkVersionsContainer';
|
import ShlinkVersionsContainer from '../../src/common/ShlinkVersionsContainer';
|
||||||
import { NonReachableServer, NotFoundServer, ReachableServer, SelectedServer } from '../../src/servers/data';
|
import { SelectedServer } from '../../src/servers/data';
|
||||||
|
import { Sidebar } from '../../src/common/reducers/sidebar';
|
||||||
|
|
||||||
describe('<ShlinkVersionsContainer />', () => {
|
describe('<ShlinkVersionsContainer />', () => {
|
||||||
let wrapper: ShallowWrapper;
|
let wrapper: ShallowWrapper;
|
||||||
|
|
||||||
const createWrapper = (selectedServer: SelectedServer) => {
|
const createWrapper = (sidebar: Sidebar) => {
|
||||||
wrapper = shallow(<ShlinkVersionsContainer selectedServer={selectedServer} />);
|
wrapper = shallow(<ShlinkVersionsContainer selectedServer={Mock.all<SelectedServer>()} sidebar={sidebar} />);
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
};
|
};
|
||||||
|
@ -15,12 +16,10 @@ describe('<ShlinkVersionsContainer />', () => {
|
||||||
afterEach(() => wrapper?.unmount());
|
afterEach(() => wrapper?.unmount());
|
||||||
|
|
||||||
it.each([
|
it.each([
|
||||||
[ null, 'text-center' ],
|
[{ hasSidebar: false }, 'text-center' ],
|
||||||
[ Mock.of<NotFoundServer>({ serverNotFound: true }), 'text-center' ],
|
[{ hasSidebar: true }, 'text-center shlink-versions-container--with-sidebar' ],
|
||||||
[ Mock.of<NonReachableServer>({ serverNotReachable: true }), 'text-center' ],
|
])('renders proper col classes based on sidebar status', (sidebar, expectedClasses) => {
|
||||||
[ Mock.of<ReachableServer>({ version: '1.0.0' }), 'text-center shlink-versions-container--with-server' ],
|
const wrapper = createWrapper(sidebar);
|
||||||
])('renders proper col classes based on type of selected server', (selectedServer, expectedClasses) => {
|
|
||||||
const wrapper = createWrapper(selectedServer);
|
|
||||||
|
|
||||||
expect(wrapper.find('div').prop('className')).toEqual(`${expectedClasses}`);
|
expect(wrapper.find('div').prop('className')).toEqual(`${expectedClasses}`);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue