Merge pull request #598 from acelaya-forks/feature/footer-alignment

Feature/footer alignment
This commit is contained in:
Alejandro Celaya 2022-03-11 16:22:00 +01:00 committed by GitHub
commit faa81ea1a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 111 additions and 24 deletions

View file

@ -25,7 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* *Nothing*
### Fixed
* *Nothing*
* [#589](https://github.com/shlinkio/shlink-web-client/pull/589) Fixed alignment of shlink versions footer, by basing the logic on the presence of the sidebar instead of selected server.
## [3.5.1] - 2022-01-08

View file

@ -11,6 +11,11 @@ import NotFound from './NotFound';
import { AsideMenuProps } from './AsideMenu';
import './MenuLayout.scss';
interface MenuLayoutProps {
sidebarPresent: Function;
sidebarNotPresent: Function;
}
const MenuLayout = (
TagsList: FC,
ShortUrlsList: FC,
@ -24,13 +29,19 @@ const MenuLayout = (
Overview: FC,
EditShortUrl: FC,
ManageDomains: FC,
) => withSelectedServer(({ selectedServer }) => {
) => withSelectedServer<MenuLayoutProps>(({ selectedServer, sidebarNotPresent, sidebarPresent }) => {
const location = useLocation();
const [ sidebarVisible, toggleSidebar, showSidebar, hideSidebar ] = useToggle();
const showContent = isReachableServer(selectedServer);
useEffect(() => hideSidebar(), [ location ]);
useEffect(() => {
showContent && sidebarPresent();
if (!isReachableServer(selectedServer)) {
return () => sidebarNotPresent();
}, []);
if (!showContent) {
return <ServerError />;
}

View file

@ -1,13 +1,13 @@
import { pipe } from 'ramda';
import { ExternalLink } from 'react-external-link';
import { versionToPrintable, versionToSemVer } from '../utils/helpers/version';
import { isReachableServer } from '../servers/data';
import { ShlinkVersionsContainerProps } from './ShlinkVersionsContainer';
import { isReachableServer, SelectedServer } from '../servers/data';
const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%';
const normalizeVersion = pipe(versionToSemVer(), versionToPrintable);
export interface ShlinkVersionsProps extends ShlinkVersionsContainerProps {
export interface ShlinkVersionsProps {
selectedServer: SelectedServer;
clientVersion?: string;
}

View file

@ -1,6 +1,6 @@
@import '../utils/base';
.shlink-versions-container--with-server {
.shlink-versions-container--with-sidebar {
margin-left: 0;
@media (min-width: $mdMin) {

View file

@ -1,15 +1,17 @@
import classNames from 'classnames';
import { isReachableServer, SelectedServer } from '../servers/data';
import { SelectedServer } from '../servers/data';
import ShlinkVersions from './ShlinkVersions';
import { Sidebar } from './reducers/sidebar';
import './ShlinkVersionsContainer.scss';
export interface ShlinkVersionsContainerProps {
selectedServer: SelectedServer;
sidebar: Sidebar;
}
const ShlinkVersionsContainer = ({ selectedServer }: ShlinkVersionsContainerProps) => {
const ShlinkVersionsContainer = ({ selectedServer, sidebar }: ShlinkVersionsContainerProps) => {
const classes = classNames('text-center', {
'shlink-versions-container--with-server': isReachableServer(selectedServer),
'shlink-versions-container--with-sidebar': sidebar.sidebarPresent,
});
return (

View 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_PRESENT = 'shlink/common/SIDEBAR_PRESENT';
export const SIDEBAR_NOT_PRESENT = 'shlink/common/SIDEBAR_NOT_PRESENT';
/* eslint-enable padding-line-between-statements */
export interface Sidebar {
sidebarPresent: boolean;
}
type SidebarRenderedAction = Action<string>;
type SidebarNotRenderedAction = Action<string>;
const initialState: Sidebar = {
sidebarPresent: false,
};
export default buildReducer<Sidebar, SidebarRenderedAction & SidebarNotRenderedAction>({
[SIDEBAR_PRESENT]: () => ({ sidebarPresent: true }),
[SIDEBAR_NOT_PRESENT]: () => ({ sidebarPresent: false }),
}, initialState);
export const sidebarPresent = buildActionCreator(SIDEBAR_PRESENT);
export const sidebarNotPresent = buildActionCreator(SIDEBAR_NOT_PRESENT);

View file

@ -9,6 +9,7 @@ import ErrorHandler from '../ErrorHandler';
import ShlinkVersionsContainer from '../ShlinkVersionsContainer';
import { ConnectDecorator } from '../../container/types';
import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServer';
import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar';
import { ImageDownloader } from './ImageDownloader';
const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
@ -44,14 +45,18 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
'EditShortUrl',
'ManageDomains',
);
bottle.decorator('MenuLayout', connect([ 'selectedServer' ], [ 'selectServer' ]));
bottle.decorator('MenuLayout', connect([ 'selectedServer' ], [ 'selectServer', 'sidebarPresent', 'sidebarNotPresent' ]));
bottle.serviceFactory('AsideMenu', AsideMenu, 'DeleteServerButton');
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
bottle.decorator('ShlinkVersionsContainer', connect([ 'selectedServer' ]));
bottle.decorator('ShlinkVersionsContainer', connect([ 'selectedServer', 'sidebar' ]));
bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console');
// Actions
bottle.serviceFactory('sidebarPresent', () => sidebarPresent);
bottle.serviceFactory('sidebarNotPresent', () => sidebarNotPresent);
};
export default provideServices;

View file

@ -14,6 +14,7 @@ import { TagVisits } from '../visits/reducers/tagVisits';
import { DomainsList } from '../domains/reducers/domainsList';
import { VisitsOverview } from '../visits/reducers/visitsOverview';
import { VisitsInfo } from '../visits/types';
import { Sidebar } from '../common/reducers/sidebar';
export interface ShlinkState {
servers: ServersMap;
@ -35,6 +36,7 @@ export interface ShlinkState {
domainsList: DomainsList;
visitsOverview: VisitsOverview;
appUpdated: boolean;
sidebar: Sidebar;
}
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;

View file

@ -18,6 +18,7 @@ import settingsReducer from '../settings/reducers/settings';
import domainsListReducer from '../domains/reducers/domainsList';
import visitsOverviewReducer from '../visits/reducers/visitsOverview';
import appUpdatesReducer from '../app/reducers/appUpdates';
import sidebarReducer from '../common/reducers/sidebar';
import { ShlinkState } from '../container/types';
export default combineReducers<ShlinkState>({
@ -40,4 +41,5 @@ export default combineReducers<ShlinkState>({
domainsList: domainsListReducer,
visitsOverview: visitsOverviewReducer,
appUpdated: appUpdatesReducer,
sidebar: sidebarReducer,
});

View file

@ -30,7 +30,8 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
'useStateFlagTimeout',
'ManageServersRow',
);
bottle.decorator('ManageServers', connect([ 'servers' ]));
bottle.decorator('ManageServers', withoutSelectedServer);
bottle.decorator('ManageServers', connect([ 'selectedServer', 'servers' ], [ 'resetSelectedServer' ]));
bottle.serviceFactory('ManageServersRow', ManageServersRow, 'ManageServersRowDropdown');
@ -42,7 +43,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.decorator('CreateServer', connect([ 'selectedServer', 'servers' ], [ 'createServer', 'resetSelectedServer' ]));
bottle.serviceFactory('EditServer', EditServer, 'ServerError');
bottle.decorator('EditServer', connect([ 'selectedServer' ], [ 'editServer', 'selectServer' ]));
bottle.decorator('EditServer', connect([ 'selectedServer' ], [ 'editServer', 'selectServer', 'resetSelectedServer' ]));
bottle.serviceFactory('ServersDropdown', () => ServersDropdown);
bottle.decorator('ServersDropdown', connect([ 'servers', 'selectedServer' ]));

View file

@ -20,7 +20,14 @@ describe('<MenuLayout />', () => {
const createWrapper = (selectedServer: SelectedServer) => {
(useParams as any).mockReturnValue({ serverId: 'abc123' });
wrapper = shallow(<MenuLayout selectServer={jest.fn()} selectedServer={selectedServer} />);
wrapper = shallow(
<MenuLayout
sidebarNotPresent={jest.fn()}
sidebarPresent={jest.fn()}
selectServer={jest.fn()}
selectedServer={selectedServer}
/>,
);
return wrapper;
};

View file

@ -1,13 +1,14 @@
import { shallow, ShallowWrapper } from 'enzyme';
import { Mock } from 'ts-mockery';
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 />', () => {
let wrapper: ShallowWrapper;
const createWrapper = (selectedServer: SelectedServer) => {
wrapper = shallow(<ShlinkVersionsContainer selectedServer={selectedServer} />);
const createWrapper = (sidebar: Sidebar) => {
wrapper = shallow(<ShlinkVersionsContainer selectedServer={Mock.all<SelectedServer>()} sidebar={sidebar} />);
return wrapper;
};
@ -15,12 +16,10 @@ describe('<ShlinkVersionsContainer />', () => {
afterEach(() => wrapper?.unmount());
it.each([
[ null, 'text-center' ],
[ Mock.of<NotFoundServer>({ serverNotFound: true }), 'text-center' ],
[ Mock.of<NonReachableServer>({ serverNotReachable: true }), 'text-center' ],
[ Mock.of<ReachableServer>({ version: '1.0.0' }), 'text-center shlink-versions-container--with-server' ],
])('renders proper col classes based on type of selected server', (selectedServer, expectedClasses) => {
const wrapper = createWrapper(selectedServer);
[{ sidebarPresent: false }, 'text-center' ],
[{ sidebarPresent: true }, 'text-center shlink-versions-container--with-sidebar' ],
])('renders proper col classes based on sidebar status', (sidebar, expectedClasses) => {
const wrapper = createWrapper(sidebar);
expect(wrapper.find('div').prop('className')).toEqual(`${expectedClasses}`);
});

View file

@ -0,0 +1,31 @@
import { Mock } from 'ts-mockery';
import reducer, {
Sidebar,
SIDEBAR_NOT_PRESENT,
SIDEBAR_PRESENT,
sidebarNotPresent,
sidebarPresent,
} from '../../../src/common/reducers/sidebar';
describe('sidebarReducer', () => {
describe('reducer', () => {
it.each([
[ SIDEBAR_PRESENT, { sidebarPresent: true }],
[ SIDEBAR_NOT_PRESENT, { sidebarPresent: false }],
])('returns expected on %s', (type, expected) => {
expect(reducer(Mock.all<Sidebar>(), { type })).toEqual(expected);
});
});
describe('sidebarPresent', () => {
it('returns expected action', () => {
expect(sidebarPresent()).toEqual({ type: SIDEBAR_PRESENT });
});
});
describe('sidebarNotPresent', () => {
it('returns expected action', () => {
expect(sidebarNotPresent()).toEqual({ type: SIDEBAR_NOT_PRESENT });
});
});
});