Remove sidebar reducer, which couple web-client with web-component

This commit is contained in:
Alejandro Celaya 2023-08-06 18:07:03 +02:00
parent c3b6ce34ba
commit 5a9640bd57
10 changed files with 38 additions and 87 deletions

View file

@ -3,8 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useToggle } from '@shlinkio/shlink-frontend-kit'; import { useToggle } from '@shlinkio/shlink-frontend-kit';
import classNames from 'classnames'; import classNames from 'classnames';
import type { FC, ReactNode } from 'react'; import type { FC, ReactNode } from 'react';
import { useEffect } from 'react'; import { Fragment, useEffect, useMemo } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import { BrowserRouter, Navigate, Route, Routes, useInRouterContext, useLocation } from 'react-router-dom';
import { AsideMenu } from './common/AsideMenu'; import { AsideMenu } from './common/AsideMenu';
import { useFeature } from './utils/features'; import { useFeature } from './utils/features';
import { useSwipeable } from './utils/helpers/hooks'; import { useSwipeable } from './utils/helpers/hooks';
@ -30,6 +30,9 @@ export const Main = (
): FC<MainProps> => ({ createNotFound }) => { ): FC<MainProps> => ({ createNotFound }) => {
const location = useLocation(); const location = useLocation();
const routesPrefix = useRoutesPrefix(); const routesPrefix = useRoutesPrefix();
const inRouterContext = useInRouterContext();
const Wrapper = useMemo(() => (inRouterContext ? Fragment : BrowserRouter), [inRouterContext]);
const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle(); const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle();
useEffect(() => hideSidebar(), [location]); useEffect(() => hideSidebar(), [location]);
@ -37,10 +40,10 @@ export const Main = (
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);
// FIXME Check if this is already wrapped by a router, and wrap otherwise // FIXME Check if this works when not currently wrapped in a router
return ( return (
<> <Wrapper basename={routesPrefix}>
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} /> <FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
<div {...swipeableProps} className="menu-layout__swipeable"> <div {...swipeableProps} className="menu-layout__swipeable">
@ -67,6 +70,6 @@ export const Main = (
</div> </div>
</div> </div>
</div> </div>
</> </Wrapper>
); );
}; };

View file

@ -1,17 +1,22 @@
import classNames from 'classnames'; import classNames from 'classnames';
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import type { SelectedServer } from '../servers/data'; import type { SelectedServer } from '../servers/data';
import type { Sidebar } from './reducers/sidebar';
import { ShlinkVersions } from './ShlinkVersions'; import { ShlinkVersions } from './ShlinkVersions';
import './ShlinkVersionsContainer.scss'; import './ShlinkVersionsContainer.scss';
export interface ShlinkVersionsContainerProps { export type ShlinkVersionsContainerProps = {
selectedServer: SelectedServer; selectedServer: SelectedServer;
sidebar: Sidebar; };
}
const SHLINK_CONTAINER_PATH_PATTERN = /^\/server\/[a-zA-Z0-9-]*\/(?!edit)/;
export const ShlinkVersionsContainer = ({ selectedServer }: ShlinkVersionsContainerProps) => {
const { pathname } = useLocation();
const withPadding = useMemo(() => SHLINK_CONTAINER_PATH_PATTERN.test(pathname), [pathname]);
export const ShlinkVersionsContainer = ({ selectedServer, sidebar }: ShlinkVersionsContainerProps) => {
const classes = classNames('text-center', { const classes = classNames('text-center', {
'shlink-versions-container--with-sidebar': sidebar.sidebarPresent, 'shlink-versions-container--with-sidebar': withPadding,
}); });
return ( return (

View file

@ -1,6 +1,5 @@
import type { Settings, ShlinkWebComponentType, TagColorsStorage } from '@shlinkio/shlink-web-component'; import type { Settings, ShlinkWebComponentType, TagColorsStorage } from '@shlinkio/shlink-web-component';
import type { FC } from 'react'; import type { FC } from 'react';
import { useEffect } from 'react';
import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder'; import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder';
import { isReachableServer } from '../servers/data'; import { isReachableServer } from '../servers/data';
import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { withSelectedServer } from '../servers/helpers/withSelectedServer';
@ -8,8 +7,6 @@ import { NotFound } from './NotFound';
import './ShlinkWebComponentContainer.scss'; import './ShlinkWebComponentContainer.scss';
interface ShlinkWebComponentContainerProps { interface ShlinkWebComponentContainerProps {
sidebarPresent: Function;
sidebarNotPresent: Function;
settings: Settings; settings: Settings;
} }
@ -18,17 +15,10 @@ export const ShlinkWebComponentContainer = (
tagColorsStorage: TagColorsStorage, tagColorsStorage: TagColorsStorage,
ShlinkWebComponent: ShlinkWebComponentType, ShlinkWebComponent: ShlinkWebComponentType,
ServerError: FC, ServerError: FC,
) => withSelectedServer<ShlinkWebComponentContainerProps>(( ) => withSelectedServer<ShlinkWebComponentContainerProps>(({ selectedServer, settings }) => {
{ selectedServer, sidebarNotPresent, sidebarPresent, settings },
) => {
const selectedServerIsReachable = isReachableServer(selectedServer); const selectedServerIsReachable = isReachableServer(selectedServer);
const routesPrefix = selectedServerIsReachable ? `/server/${selectedServer.id}` : ''; const routesPrefix = selectedServerIsReachable ? `/server/${selectedServer.id}` : '';
useEffect(() => {
selectedServerIsReachable && sidebarPresent();
return () => sidebarNotPresent();
}, []);
if (!selectedServerIsReachable) { if (!selectedServerIsReachable) {
return <ServerError />; return <ServerError />;
} }

View file

@ -1,26 +0,0 @@
import { createSlice } from '@reduxjs/toolkit';
// FIXME This is only used for some components to have extra paddings/styles if existing section has a side menu
// Now that's basically the route which renders ShlinkWebComponent, so maybe there's some way to re-think this
// logic, and perhaps get rid of a reducer just for that
export interface Sidebar {
sidebarPresent: boolean;
}
const initialState: Sidebar = {
sidebarPresent: false,
};
const { actions, reducer } = createSlice({
name: 'shlink/sidebar',
initialState,
reducers: {
sidebarPresent: () => ({ sidebarPresent: true }),
sidebarNotPresent: () => ({ sidebarPresent: false }),
},
});
export const { sidebarPresent, sidebarNotPresent } = actions;
export const sidebarReducer = reducer;

View file

@ -5,7 +5,6 @@ import { withoutSelectedServer } from '../../servers/helpers/withoutSelectedServ
import { ErrorHandler } from '../ErrorHandler'; import { ErrorHandler } from '../ErrorHandler';
import { Home } from '../Home'; import { Home } from '../Home';
import { MainHeader } from '../MainHeader'; import { MainHeader } from '../MainHeader';
import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar';
import { ScrollToTop } from '../ScrollToTop'; import { ScrollToTop } from '../ScrollToTop';
import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer'; import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer';
import { ShlinkWebComponentContainer } from '../ShlinkWebComponentContainer'; import { ShlinkWebComponentContainer } from '../ShlinkWebComponentContainer';
@ -36,17 +35,10 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
'ShlinkWebComponent', 'ShlinkWebComponent',
'ServerError', 'ServerError',
); );
bottle.decorator('ShlinkWebComponentContainer', connect( bottle.decorator('ShlinkWebComponentContainer', connect(['selectedServer', 'settings'], ['selectServer']));
['selectedServer', 'settings'],
['selectServer', 'sidebarPresent', 'sidebarNotPresent'],
));
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer); bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
bottle.decorator('ShlinkVersionsContainer', connect(['selectedServer', 'sidebar'])); bottle.decorator('ShlinkVersionsContainer', connect(['selectedServer']));
bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console'); bottle.serviceFactory('ErrorHandler', ErrorHandler, 'window', 'console');
// Actions
bottle.serviceFactory('sidebarPresent', () => sidebarPresent);
bottle.serviceFactory('sidebarNotPresent', () => sidebarNotPresent);
}; };

View file

@ -1,5 +1,4 @@
import type { Settings } from '@shlinkio/shlink-web-component'; import type { Settings } from '@shlinkio/shlink-web-component';
import type { Sidebar } from '../common/reducers/sidebar';
import type { SelectedServer, ServersMap } from '../servers/data'; import type { SelectedServer, ServersMap } from '../servers/data';
export interface ShlinkState { export interface ShlinkState {
@ -7,7 +6,6 @@ export interface ShlinkState {
selectedServer: SelectedServer; selectedServer: SelectedServer;
settings: Settings; settings: Settings;
appUpdated: boolean; appUpdated: boolean;
sidebar: Sidebar;
} }
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any; export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;

View file

@ -1,7 +1,6 @@
import { combineReducers } from '@reduxjs/toolkit'; import { combineReducers } from '@reduxjs/toolkit';
import type { IContainer } from 'bottlejs'; import type { IContainer } from 'bottlejs';
import { appUpdatesReducer } from '../app/reducers/appUpdates'; import { appUpdatesReducer } from '../app/reducers/appUpdates';
import { sidebarReducer } from '../common/reducers/sidebar';
import type { ShlinkState } from '../container/types'; import type { ShlinkState } from '../container/types';
import { serversReducer } from '../servers/reducers/servers'; import { serversReducer } from '../servers/reducers/servers';
import { settingsReducer } from '../settings/reducers/settings'; import { settingsReducer } from '../settings/reducers/settings';
@ -11,5 +10,4 @@ export const initReducers = (container: IContainer) => combineReducers<ShlinkSta
servers: serversReducer, servers: serversReducer,
selectedServer: container.selectedServerReducer, selectedServer: container.selectedServerReducer,
settings: settingsReducer, settings: settingsReducer,
sidebar: sidebarReducer,
}); });

View file

@ -1,16 +1,25 @@
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import { fromPartial } from '@total-typescript/shoehorn'; import { fromPartial } from '@total-typescript/shoehorn';
import type { Sidebar } from '../../src/common/reducers/sidebar'; import { createMemoryHistory } from 'history';
import { Router } from 'react-router-dom';
import { ShlinkVersionsContainer } from '../../src/common/ShlinkVersionsContainer'; import { ShlinkVersionsContainer } from '../../src/common/ShlinkVersionsContainer';
describe('<ShlinkVersionsContainer />', () => { describe('<ShlinkVersionsContainer />', () => {
const setUp = (sidebar: Sidebar) => render( const setUp = (activeRoute: string) => {
<ShlinkVersionsContainer selectedServer={fromPartial({})} sidebar={sidebar} />, const history = createMemoryHistory();
); history.push(activeRoute);
return render(
<Router location={history.location} navigator={history}>
<ShlinkVersionsContainer selectedServer={fromPartial({})} />
</Router>,
);
};
it.each([ it.each([
[{ sidebarPresent: false }, 'text-center'], ['/something', 'text-center'],
[{ sidebarPresent: true }, 'text-center shlink-versions-container--with-sidebar'], ['/server/foo/edit', 'text-center'],
['/server/foo/bar', 'text-center shlink-versions-container--with-sidebar'],
])('renders proper col classes based on sidebar status', (sidebar, expectedClasses) => { ])('renders proper col classes based on sidebar status', (sidebar, expectedClasses) => {
const { container } = setUp(sidebar); const { container } = setUp(sidebar);
expect(container.firstChild).toHaveAttribute('class', `${expectedClasses}`); expect(container.firstChild).toHaveAttribute('class', `${expectedClasses}`);

View file

@ -17,13 +17,7 @@ describe('<ShlinkWebComponentContainer />', () => {
() => <>ServerError</>, () => <>ServerError</>,
); );
const setUp = (selectedServer: SelectedServer) => render( const setUp = (selectedServer: SelectedServer) => render(
<ShlinkWebComponentContainer <ShlinkWebComponentContainer selectServer={vi.fn()} selectedServer={selectedServer} settings={{}} />,
sidebarNotPresent={vi.fn()}
sidebarPresent={vi.fn()}
selectServer={vi.fn()}
selectedServer={selectedServer}
settings={{}}
/>,
); );
beforeEach(() => { beforeEach(() => {

View file

@ -1,12 +0,0 @@
import { sidebarNotPresent, sidebarPresent, sidebarReducer } from '../../../src/common/reducers/sidebar';
describe('sidebarReducer', () => {
describe('reducer', () => {
it.each([
[sidebarPresent, { sidebarPresent: true }],
[sidebarNotPresent, { sidebarPresent: false }],
])('returns expected on %s', (actionCreator, expected) => {
expect(sidebarReducer(undefined, actionCreator())).toEqual(expected);
});
});
});