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 classNames from 'classnames';
import type { FC, ReactNode } from 'react';
import { useEffect } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { Fragment, useEffect, useMemo } from 'react';
import { BrowserRouter, Navigate, Route, Routes, useInRouterContext, useLocation } from 'react-router-dom';
import { AsideMenu } from './common/AsideMenu';
import { useFeature } from './utils/features';
import { useSwipeable } from './utils/helpers/hooks';
@ -30,6 +30,9 @@ export const Main = (
): FC<MainProps> => ({ createNotFound }) => {
const location = useLocation();
const routesPrefix = useRoutesPrefix();
const inRouterContext = useInRouterContext();
const Wrapper = useMemo(() => (inRouterContext ? Fragment : BrowserRouter), [inRouterContext]);
const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle();
useEffect(() => hideSidebar(), [location]);
@ -37,10 +40,10 @@ export const Main = (
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
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 (
<>
<Wrapper basename={routesPrefix}>
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
<div {...swipeableProps} className="menu-layout__swipeable">
@ -67,6 +70,6 @@ export const Main = (
</div>
</div>
</div>
</>
</Wrapper>
);
};

View file

@ -1,17 +1,22 @@
import classNames from 'classnames';
import { useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import type { SelectedServer } from '../servers/data';
import type { Sidebar } from './reducers/sidebar';
import { ShlinkVersions } from './ShlinkVersions';
import './ShlinkVersionsContainer.scss';
export interface ShlinkVersionsContainerProps {
export type ShlinkVersionsContainerProps = {
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', {
'shlink-versions-container--with-sidebar': sidebar.sidebarPresent,
'shlink-versions-container--with-sidebar': withPadding,
});
return (

View file

@ -1,6 +1,5 @@
import type { Settings, ShlinkWebComponentType, TagColorsStorage } from '@shlinkio/shlink-web-component';
import type { FC } from 'react';
import { useEffect } from 'react';
import type { ShlinkApiClientBuilder } from '../api/services/ShlinkApiClientBuilder';
import { isReachableServer } from '../servers/data';
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
@ -8,8 +7,6 @@ import { NotFound } from './NotFound';
import './ShlinkWebComponentContainer.scss';
interface ShlinkWebComponentContainerProps {
sidebarPresent: Function;
sidebarNotPresent: Function;
settings: Settings;
}
@ -18,17 +15,10 @@ export const ShlinkWebComponentContainer = (
tagColorsStorage: TagColorsStorage,
ShlinkWebComponent: ShlinkWebComponentType,
ServerError: FC,
) => withSelectedServer<ShlinkWebComponentContainerProps>((
{ selectedServer, sidebarNotPresent, sidebarPresent, settings },
) => {
) => withSelectedServer<ShlinkWebComponentContainerProps>(({ selectedServer, settings }) => {
const selectedServerIsReachable = isReachableServer(selectedServer);
const routesPrefix = selectedServerIsReachable ? `/server/${selectedServer.id}` : '';
useEffect(() => {
selectedServerIsReachable && sidebarPresent();
return () => sidebarNotPresent();
}, []);
if (!selectedServerIsReachable) {
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 { Home } from '../Home';
import { MainHeader } from '../MainHeader';
import { sidebarNotPresent, sidebarPresent } from '../reducers/sidebar';
import { ScrollToTop } from '../ScrollToTop';
import { ShlinkVersionsContainer } from '../ShlinkVersionsContainer';
import { ShlinkWebComponentContainer } from '../ShlinkWebComponentContainer';
@ -36,17 +35,10 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
'ShlinkWebComponent',
'ServerError',
);
bottle.decorator('ShlinkWebComponentContainer', connect(
['selectedServer', 'settings'],
['selectServer', 'sidebarPresent', 'sidebarNotPresent'],
));
bottle.decorator('ShlinkWebComponentContainer', connect(['selectedServer', 'settings'], ['selectServer']));
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
bottle.decorator('ShlinkVersionsContainer', connect(['selectedServer', 'sidebar']));
bottle.decorator('ShlinkVersionsContainer', connect(['selectedServer']));
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 { Sidebar } from '../common/reducers/sidebar';
import type { SelectedServer, ServersMap } from '../servers/data';
export interface ShlinkState {
@ -7,7 +6,6 @@ export interface ShlinkState {
selectedServer: SelectedServer;
settings: Settings;
appUpdated: boolean;
sidebar: Sidebar;
}
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;

View file

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

View file

@ -1,16 +1,25 @@
import { render } from '@testing-library/react';
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';
describe('<ShlinkVersionsContainer />', () => {
const setUp = (sidebar: Sidebar) => render(
<ShlinkVersionsContainer selectedServer={fromPartial({})} sidebar={sidebar} />,
const setUp = (activeRoute: string) => {
const history = createMemoryHistory();
history.push(activeRoute);
return render(
<Router location={history.location} navigator={history}>
<ShlinkVersionsContainer selectedServer={fromPartial({})} />
</Router>,
);
};
it.each([
[{ sidebarPresent: false }, 'text-center'],
[{ sidebarPresent: true }, 'text-center shlink-versions-container--with-sidebar'],
['/something', 'text-center'],
['/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) => {
const { container } = setUp(sidebar);
expect(container.firstChild).toHaveAttribute('class', `${expectedClasses}`);

View file

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