mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-03-14 02:08:41 +03:00
Move settings from store to another context
This commit is contained in:
parent
dddbc232c2
commit
b3122219be
37 changed files with 293 additions and 263 deletions
|
@ -2,18 +2,19 @@ import type { FC } from 'react';
|
|||
import { useEffect } from 'react';
|
||||
import { isReachableServer } from '../servers/data';
|
||||
import { withSelectedServer } from '../servers/helpers/withSelectedServer';
|
||||
import type { ShlinkWebComponentType } from '../shlink-web-component';
|
||||
import { ShlinkWebComponent } from '../shlink-web-component';
|
||||
import type { Settings } from '../shlink-web-component/utils/settings';
|
||||
import './MenuLayout.scss';
|
||||
|
||||
interface MenuLayoutProps {
|
||||
sidebarPresent: Function;
|
||||
sidebarNotPresent: Function;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export const MenuLayout = (
|
||||
ServerError: FC,
|
||||
ShlinkWebComponent: ShlinkWebComponentType,
|
||||
) => withSelectedServer<MenuLayoutProps>(({ selectedServer, sidebarNotPresent, sidebarPresent }) => {
|
||||
) => withSelectedServer<MenuLayoutProps>(({ selectedServer, sidebarNotPresent, sidebarPresent, settings }) => {
|
||||
const showContent = isReachableServer(selectedServer);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -28,6 +29,7 @@ export const MenuLayout = (
|
|||
return (
|
||||
<ShlinkWebComponent
|
||||
serverVersion={selectedServer.version}
|
||||
settings={settings}
|
||||
routesPrefix={`/server/${selectedServer.id}`}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -31,8 +31,11 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
bottle.decorator('Home', withoutSelectedServer);
|
||||
bottle.decorator('Home', connect(['servers'], ['resetSelectedServer']));
|
||||
|
||||
bottle.serviceFactory('MenuLayout', MenuLayout, 'ServerError', 'ShlinkWebComponent');
|
||||
bottle.decorator('MenuLayout', connect(['selectedServer'], ['selectServer', 'sidebarPresent', 'sidebarNotPresent']));
|
||||
bottle.serviceFactory('MenuLayout', MenuLayout, 'ServerError');
|
||||
bottle.decorator('MenuLayout', connect(
|
||||
['selectedServer', 'settings'],
|
||||
['selectServer', 'sidebarPresent', 'sidebarNotPresent'],
|
||||
));
|
||||
|
||||
bottle.serviceFactory('ShlinkVersionsContainer', () => ShlinkVersionsContainer);
|
||||
bottle.decorator('ShlinkVersionsContainer', connect(['selectedServer', 'sidebar']));
|
||||
|
|
|
@ -7,12 +7,6 @@ import { provideServices as provideAppServices } from '../app/services/provideSe
|
|||
import { provideServices as provideCommonServices } from '../common/services/provideServices';
|
||||
import { provideServices as provideServersServices } from '../servers/services/provideServices';
|
||||
import { provideServices as provideSettingsServices } from '../settings/services/provideServices';
|
||||
import { provideServices as provideWebComponentServices } from '../shlink-web-component/container/provideServices';
|
||||
import { provideServices as provideDomainsServices } from '../shlink-web-component/domains/services/provideServices';
|
||||
import { provideServices as provideMercureServices } from '../shlink-web-component/mercure/services/provideServices';
|
||||
import { provideServices as provideShortUrlsServices } from '../shlink-web-component/short-urls/services/provideServices';
|
||||
import { provideServices as provideTagsServices } from '../shlink-web-component/tags/services/provideServices';
|
||||
import { provideServices as provideVisitsServices } from '../shlink-web-component/visits/services/provideServices';
|
||||
import { provideServices as provideUtilsServices } from '../utils/services/provideServices';
|
||||
import type { ConnectDecorator } from './types';
|
||||
|
||||
|
@ -38,14 +32,6 @@ const connect: ConnectDecorator = (propsFromState: string[] | null, actionServic
|
|||
provideAppServices(bottle, connect);
|
||||
provideCommonServices(bottle, connect);
|
||||
provideApiServices(bottle);
|
||||
provideShortUrlsServices(bottle, connect);
|
||||
provideServersServices(bottle, connect);
|
||||
provideTagsServices(bottle, connect);
|
||||
provideVisitsServices(bottle, connect);
|
||||
provideUtilsServices(bottle);
|
||||
provideMercureServices(bottle);
|
||||
provideSettingsServices(bottle, connect);
|
||||
provideDomainsServices(bottle, connect);
|
||||
|
||||
// TODO This should not be needed.
|
||||
provideWebComponentServices(bottle);
|
||||
|
|
|
@ -1,42 +1,11 @@
|
|||
import type { Sidebar } from '../common/reducers/sidebar';
|
||||
import type { SelectedServer, ServersMap } from '../servers/data';
|
||||
import type { Settings } from '../settings/reducers/settings';
|
||||
import type { DomainsList } from '../shlink-web-component/domains/reducers/domainsList';
|
||||
import type { MercureInfo } from '../shlink-web-component/mercure/reducers/mercureInfo';
|
||||
import type { ShortUrlCreation } from '../shlink-web-component/short-urls/reducers/shortUrlCreation';
|
||||
import type { ShortUrlDeletion } from '../shlink-web-component/short-urls/reducers/shortUrlDeletion';
|
||||
import type { ShortUrlDetail } from '../shlink-web-component/short-urls/reducers/shortUrlDetail';
|
||||
import type { ShortUrlEdition } from '../shlink-web-component/short-urls/reducers/shortUrlEdition';
|
||||
import type { ShortUrlsList } from '../shlink-web-component/short-urls/reducers/shortUrlsList';
|
||||
import type { TagDeletion } from '../shlink-web-component/tags/reducers/tagDelete';
|
||||
import type { TagEdition } from '../shlink-web-component/tags/reducers/tagEdit';
|
||||
import type { TagsList } from '../shlink-web-component/tags/reducers/tagsList';
|
||||
import type { DomainVisits } from '../shlink-web-component/visits/reducers/domainVisits';
|
||||
import type { ShortUrlVisits } from '../shlink-web-component/visits/reducers/shortUrlVisits';
|
||||
import type { TagVisits } from '../shlink-web-component/visits/reducers/tagVisits';
|
||||
import type { VisitsInfo } from '../shlink-web-component/visits/reducers/types';
|
||||
import type { VisitsOverview } from '../shlink-web-component/visits/reducers/visitsOverview';
|
||||
|
||||
export interface ShlinkState {
|
||||
servers: ServersMap;
|
||||
selectedServer: SelectedServer;
|
||||
shortUrlsList: ShortUrlsList;
|
||||
shortUrlCreation: ShortUrlCreation;
|
||||
shortUrlDeletion: ShortUrlDeletion;
|
||||
shortUrlEdition: ShortUrlEdition;
|
||||
shortUrlVisits: ShortUrlVisits;
|
||||
tagVisits: TagVisits;
|
||||
domainVisits: DomainVisits;
|
||||
orphanVisits: VisitsInfo;
|
||||
nonOrphanVisits: VisitsInfo;
|
||||
shortUrlDetail: ShortUrlDetail;
|
||||
tagsList: TagsList;
|
||||
tagDelete: TagDeletion;
|
||||
tagEdit: TagEdition;
|
||||
mercureInfo: MercureInfo;
|
||||
settings: Settings;
|
||||
domainsList: DomainsList;
|
||||
visitsOverview: VisitsOverview;
|
||||
appUpdated: boolean;
|
||||
sidebar: Sidebar;
|
||||
}
|
||||
|
|
|
@ -7,30 +7,9 @@ import { serversReducer } from '../servers/reducers/servers';
|
|||
import { settingsReducer } from '../settings/reducers/settings';
|
||||
|
||||
export const initReducers = (container: IContainer) => combineReducers<ShlinkState>({
|
||||
// Main shlink-web-client reducers
|
||||
appUpdated: appUpdatesReducer,
|
||||
servers: serversReducer,
|
||||
selectedServer: container.selectedServerReducer,
|
||||
settings: settingsReducer,
|
||||
sidebar: sidebarReducer,
|
||||
|
||||
// TBD
|
||||
mercureInfo: container.mercureInfoReducer,
|
||||
|
||||
// Nested shlink-web-component reducers
|
||||
shortUrlsList: container.shortUrlsListReducer,
|
||||
shortUrlCreation: container.shortUrlCreationReducer,
|
||||
shortUrlDeletion: container.shortUrlDeletionReducer,
|
||||
shortUrlEdition: container.shortUrlEditionReducer,
|
||||
shortUrlDetail: container.shortUrlDetailReducer,
|
||||
shortUrlVisits: container.shortUrlVisitsReducer,
|
||||
tagVisits: container.tagVisitsReducer,
|
||||
domainVisits: container.domainVisitsReducer,
|
||||
orphanVisits: container.orphanVisitsReducer,
|
||||
nonOrphanVisits: container.nonOrphanVisitsReducer,
|
||||
tagsList: container.tagsListReducer,
|
||||
tagDelete: container.tagDeleteReducer,
|
||||
tagEdit: container.tagEditReducer,
|
||||
domainsList: container.domainsListReducer,
|
||||
visitsOverview: container.visitsOverviewReducer,
|
||||
});
|
||||
|
|
|
@ -66,12 +66,14 @@ export const selectServerListener = (
|
|||
) => {
|
||||
const listener = createListenerMiddleware();
|
||||
|
||||
listener.startListening({
|
||||
actionCreator: selectServerThunk.fulfilled,
|
||||
effect: ({ payload }, { dispatch }) => {
|
||||
isReachableServer(payload) && dispatch(loadMercureInfo());
|
||||
},
|
||||
});
|
||||
// TODO Find a way for the mercure info to be re-loaded when server changes, without leaking mercure implementation
|
||||
// details
|
||||
// listener.startListening({
|
||||
// actionCreator: selectServerThunk.fulfilled,
|
||||
// effect: ({ payload }, { dispatch }) => {
|
||||
// isReachableServer(payload) && dispatch(loadMercureInfo());
|
||||
// },
|
||||
// });
|
||||
|
||||
return listener;
|
||||
};
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type Bottle from 'bottlejs';
|
||||
import { prop } from 'ramda';
|
||||
import type { ConnectDecorator } from '../../container/types';
|
||||
import { Overview } from '../../shlink-web-component/overview/Overview';
|
||||
import { CreateServer } from '../CreateServer';
|
||||
import { DeleteServerButton } from '../DeleteServerButton';
|
||||
import { DeleteServerModal } from '../DeleteServerModal';
|
||||
|
@ -63,12 +62,6 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
bottle.serviceFactory('ServerError', ServerError, 'DeleteServerButton');
|
||||
bottle.decorator('ServerError', connect(['servers', 'selectedServer']));
|
||||
|
||||
bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable', 'CreateShortUrl');
|
||||
bottle.decorator('Overview', connect(
|
||||
['shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview', 'settings'],
|
||||
['listShortUrls', 'listTags', 'createNewVisits', 'loadMercureInfo', 'loadVisitsOverview'],
|
||||
));
|
||||
|
||||
// Services
|
||||
bottle.constant('fileReaderFactory', () => new FileReader());
|
||||
bottle.service('ServersImporter', ServersImporter, 'csvToJson', 'fileReaderFactory');
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import type { FC } from 'react';
|
||||
import { FormGroup } from 'reactstrap';
|
||||
import type { Settings } from '../shlink-web-component/utils/settings';
|
||||
import { DateIntervalSelector } from '../utils/dates/DateIntervalSelector';
|
||||
import { FormText } from '../utils/forms/FormText';
|
||||
import { LabeledFormGroup } from '../utils/forms/LabeledFormGroup';
|
||||
import type { DateInterval } from '../utils/helpers/dateIntervals';
|
||||
import { SimpleCard } from '../utils/SimpleCard';
|
||||
import { ToggleSwitch } from '../utils/ToggleSwitch';
|
||||
import type { Settings, VisitsSettings as VisitsSettingsConfig } from './reducers/settings';
|
||||
|
||||
type VisitsSettingsConfig = Settings['visits'];
|
||||
|
||||
interface VisitsProps {
|
||||
settings: Settings;
|
||||
|
|
|
@ -2,59 +2,13 @@ import type { PayloadAction, PrepareAction } from '@reduxjs/toolkit';
|
|||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { mergeDeepRight } from 'ramda';
|
||||
import type { ShortUrlsOrder } from '../../shlink-web-component/short-urls/data';
|
||||
import type { TagsOrder } from '../../shlink-web-component/tags/data/TagsListChildrenProps';
|
||||
import type { DateInterval } from '../../utils/helpers/dateIntervals';
|
||||
import type { Theme } from '../../utils/theme';
|
||||
import type { Settings } from '../../shlink-web-component/utils/settings';
|
||||
|
||||
export const DEFAULT_SHORT_URLS_ORDERING: ShortUrlsOrder = {
|
||||
field: 'dateCreated',
|
||||
dir: 'DESC',
|
||||
};
|
||||
|
||||
/**
|
||||
* Important! When adding new props in the main Settings interface or any of the nested props, they have to be set as
|
||||
* optional, as old instances of the app will load partial objects from local storage until it is saved again.
|
||||
*/
|
||||
|
||||
export interface RealTimeUpdatesSettings {
|
||||
enabled: boolean;
|
||||
interval?: number;
|
||||
}
|
||||
|
||||
export type TagFilteringMode = 'startsWith' | 'includes';
|
||||
|
||||
export interface ShortUrlCreationSettings {
|
||||
validateUrls: boolean;
|
||||
tagFilteringMode?: TagFilteringMode;
|
||||
forwardQuery?: boolean;
|
||||
}
|
||||
|
||||
export interface UiSettings {
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
export interface VisitsSettings {
|
||||
defaultInterval: DateInterval;
|
||||
excludeBots?: boolean;
|
||||
}
|
||||
|
||||
export interface TagsSettings {
|
||||
defaultOrdering?: TagsOrder;
|
||||
}
|
||||
|
||||
export interface ShortUrlsListSettings {
|
||||
defaultOrdering?: ShortUrlsOrder;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
realTimeUpdates: RealTimeUpdatesSettings;
|
||||
shortUrlCreation?: ShortUrlCreationSettings;
|
||||
shortUrlsList?: ShortUrlsListSettings;
|
||||
ui?: UiSettings;
|
||||
visits?: VisitsSettings;
|
||||
tags?: TagsSettings;
|
||||
}
|
||||
|
||||
const initialState: Settings = {
|
||||
realTimeUpdates: {
|
||||
enabled: true,
|
||||
|
@ -87,12 +41,14 @@ const { reducer, actions } = createSlice({
|
|||
toggleRealTimeUpdates: toReducer((enabled: boolean) => toPreparedAction({ realTimeUpdates: { enabled } })),
|
||||
setRealTimeUpdatesInterval: toReducer((interval: number) => toPreparedAction({ realTimeUpdates: { interval } })),
|
||||
setShortUrlCreationSettings: toReducer(
|
||||
(shortUrlCreation: ShortUrlCreationSettings) => toPreparedAction({ shortUrlCreation }),
|
||||
(shortUrlCreation: Settings['shortUrlCreation']) => toPreparedAction({ shortUrlCreation }),
|
||||
),
|
||||
setShortUrlsListSettings: toReducer((shortUrlsList: ShortUrlsListSettings) => toPreparedAction({ shortUrlsList })),
|
||||
setUiSettings: toReducer((ui: UiSettings) => toPreparedAction({ ui })),
|
||||
setVisitsSettings: toReducer((visits: VisitsSettings) => toPreparedAction({ visits })),
|
||||
setTagsSettings: toReducer((tags: TagsSettings) => toPreparedAction({ tags })),
|
||||
setShortUrlsListSettings: toReducer(
|
||||
(shortUrlsList: Settings['shortUrlsList']) => toPreparedAction({ shortUrlsList }),
|
||||
),
|
||||
setUiSettings: toReducer((ui: Settings['ui']) => toPreparedAction({ ui })),
|
||||
setVisitsSettings: toReducer((visits: Settings['visits']) => toPreparedAction({ visits })),
|
||||
setTagsSettings: toReducer((tags: Settings['tags']) => toPreparedAction({ tags })),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import { faBars as burgerIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import type { Store } from '@reduxjs/toolkit';
|
||||
import classNames from 'classnames';
|
||||
import type { FC } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { AsideMenu } from '../common/AsideMenu';
|
||||
import { NotFound } from '../common/NotFound';
|
||||
import { useSwipeable, useToggle } from '../utils/helpers/hooks';
|
||||
import type { SemVer } from '../utils/helpers/version';
|
||||
import { FeaturesProvider, useFeatures } from './utils/features';
|
||||
import type { Settings } from './utils/settings';
|
||||
import { SettingsProvider } from './utils/settings';
|
||||
|
||||
type ShlinkWebComponentProps = {
|
||||
routesPrefix?: string;
|
||||
serverVersion: SemVer;
|
||||
settings?: Settings;
|
||||
};
|
||||
|
||||
export const ShlinkWebComponent = (
|
||||
|
@ -27,7 +32,8 @@ export const ShlinkWebComponent = (
|
|||
Overview: FC,
|
||||
EditShortUrl: FC,
|
||||
ManageDomains: FC,
|
||||
): FC<ShlinkWebComponentProps> => ({ routesPrefix = '', serverVersion }) => {
|
||||
store: Store,
|
||||
): FC<ShlinkWebComponentProps> => ({ routesPrefix = '', serverVersion, settings }) => {
|
||||
const location = useLocation();
|
||||
const [sidebarVisible, toggleSidebar, showSidebar, hideSidebar] = useToggle();
|
||||
useEffect(() => hideSidebar(), [location]);
|
||||
|
@ -41,37 +47,41 @@ export const ShlinkWebComponent = (
|
|||
// TODO Check if this is already wrapped by a router, and wrap otherwise
|
||||
|
||||
return (
|
||||
<FeaturesProvider value={features}>
|
||||
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
|
||||
<Provider store={store}>
|
||||
<SettingsProvider value={settings}>
|
||||
<FeaturesProvider value={features}>
|
||||
<FontAwesomeIcon icon={burgerIcon} className={burgerClasses} onClick={toggleSidebar} />
|
||||
|
||||
<div {...swipeableProps} className="menu-layout__swipeable">
|
||||
<div className="menu-layout__swipeable-inner">
|
||||
<AsideMenu routePrefix={routesPrefix} showOnMobile={sidebarVisible} />
|
||||
<div className="menu-layout__container" onClick={() => hideSidebar()}>
|
||||
<div className="container-xl">
|
||||
<Routes>
|
||||
<Route index element={<Navigate replace to="overview" />} />
|
||||
<Route path="/overview" element={<Overview />} />
|
||||
<Route path="/list-short-urls/:page" element={<ShortUrlsList />} />
|
||||
<Route path="/create-short-url" element={<CreateShortUrl />} />
|
||||
<Route path="/short-code/:shortCode/visits/*" element={<ShortUrlVisits />} />
|
||||
<Route path="/short-code/:shortCode/edit" element={<EditShortUrl />} />
|
||||
<Route path="/tag/:tag/visits/*" element={<TagVisits />} />
|
||||
{addDomainVisitsRoute && <Route path="/domain/:domain/visits/*" element={<DomainVisits />} />}
|
||||
<Route path="/orphan-visits/*" element={<OrphanVisits />} />
|
||||
{addNonOrphanVisitsRoute && <Route path="/non-orphan-visits/*" element={<NonOrphanVisits />} />}
|
||||
<Route path="/manage-tags" element={<TagsList />} />
|
||||
<Route path="/manage-domains" element={<ManageDomains />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<NotFound to={`${routesPrefix}/list-short-urls/1`}>List short URLs</NotFound>}
|
||||
/>
|
||||
</Routes>
|
||||
<div {...swipeableProps} className="menu-layout__swipeable">
|
||||
<div className="menu-layout__swipeable-inner">
|
||||
<AsideMenu routePrefix={routesPrefix} showOnMobile={sidebarVisible} />
|
||||
<div className="menu-layout__container" onClick={() => hideSidebar()}>
|
||||
<div className="container-xl">
|
||||
<Routes>
|
||||
<Route index element={<Navigate replace to="overview" />} />
|
||||
<Route path="/overview" element={<Overview />} />
|
||||
<Route path="/list-short-urls/:page" element={<ShortUrlsList />} />
|
||||
<Route path="/create-short-url" element={<CreateShortUrl />} />
|
||||
<Route path="/short-code/:shortCode/visits/*" element={<ShortUrlVisits />} />
|
||||
<Route path="/short-code/:shortCode/edit" element={<EditShortUrl />} />
|
||||
<Route path="/tag/:tag/visits/*" element={<TagVisits />} />
|
||||
{addDomainVisitsRoute && <Route path="/domain/:domain/visits/*" element={<DomainVisits />} />}
|
||||
<Route path="/orphan-visits/*" element={<OrphanVisits />} />
|
||||
{addNonOrphanVisitsRoute && <Route path="/non-orphan-visits/*" element={<NonOrphanVisits />} />}
|
||||
<Route path="/manage-tags" element={<TagsList />} />
|
||||
<Route path="/manage-domains" element={<ManageDomains />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={<NotFound to={`${routesPrefix}/list-short-urls/1`}>List short URLs</NotFound>}
|
||||
/>
|
||||
</Routes>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FeaturesProvider>
|
||||
</FeaturesProvider>
|
||||
</SettingsProvider>
|
||||
</Provider>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,2 +1,71 @@
|
|||
// TODO Create a separated container here
|
||||
export { container } from '../../container';
|
||||
import type { IContainer } from 'bottlejs';
|
||||
import Bottle from 'bottlejs';
|
||||
import { pick } from 'ramda';
|
||||
import { connect as reduxConnect } from 'react-redux/es/exports';
|
||||
import { HttpClient } from '../../common/services/HttpClient';
|
||||
import { ImageDownloader } from '../../common/services/ImageDownloader';
|
||||
import { ReportExporter } from '../../common/services/ReportExporter';
|
||||
import { csvToJson, jsonToCsv } from '../../utils/helpers/csvjson';
|
||||
import { useTimeoutToggle } from '../../utils/helpers/hooks';
|
||||
import { ColorGenerator } from '../../utils/services/ColorGenerator';
|
||||
import { LocalStorage } from '../../utils/services/LocalStorage';
|
||||
import { provideServices as provideDomainsServices } from '../domains/services/provideServices';
|
||||
import { provideServices as provideMercureServices } from '../mercure/services/provideServices';
|
||||
import { provideServices as provideOverviewServices } from '../overview/services/provideServices';
|
||||
import { provideServices as provideShortUrlsServices } from '../short-urls/services/provideServices';
|
||||
import { provideServices as provideTagsServices } from '../tags/services/provideServices';
|
||||
import { provideServices as provideVisitsServices } from '../visits/services/provideServices';
|
||||
import { provideServices as provideWebComponentServices } from './provideServices';
|
||||
import { setUpStore } from './store';
|
||||
|
||||
type LazyActionMap = Record<string, Function>;
|
||||
|
||||
export type ConnectDecorator = (props: string[] | null, actions?: string[]) => any;
|
||||
|
||||
const bottle = new Bottle();
|
||||
|
||||
export const { container } = bottle;
|
||||
|
||||
const lazyService = <T extends Function, K>(cont: IContainer, serviceName: string) =>
|
||||
(...args: any[]) => (cont[serviceName] as T)(...args) as K;
|
||||
const mapActionService = (map: LazyActionMap, actionName: string): LazyActionMap => ({
|
||||
...map,
|
||||
// Wrap actual action service in a function so that it is lazily created the first time it is called
|
||||
[actionName]: lazyService(container, actionName),
|
||||
});
|
||||
const connect: ConnectDecorator = (propsFromState: string[] | null, actionServiceNames: string[] = []) =>
|
||||
reduxConnect(
|
||||
propsFromState ? pick(propsFromState) : null,
|
||||
actionServiceNames.reduce(mapActionService, {}),
|
||||
);
|
||||
|
||||
provideWebComponentServices(bottle);
|
||||
provideShortUrlsServices(bottle, connect);
|
||||
provideTagsServices(bottle, connect);
|
||||
provideVisitsServices(bottle, connect);
|
||||
provideMercureServices(bottle);
|
||||
provideDomainsServices(bottle, connect);
|
||||
provideOverviewServices(bottle, connect);
|
||||
|
||||
// TODO Check which of these can be moved to shlink-web-component, and which are needed by the app too
|
||||
bottle.constant('window', window);
|
||||
bottle.constant('console', console);
|
||||
bottle.constant('fetch', window.fetch.bind(window));
|
||||
|
||||
bottle.service('HttpClient', HttpClient, 'fetch');
|
||||
bottle.service('ImageDownloader', ImageDownloader, 'HttpClient', 'window');
|
||||
bottle.service('ReportExporter', ReportExporter, 'window', 'jsonToCsv');
|
||||
|
||||
bottle.constant('localStorage', window.localStorage);
|
||||
bottle.service('Storage', LocalStorage, 'localStorage');
|
||||
bottle.service('ColorGenerator', ColorGenerator, 'Storage');
|
||||
|
||||
bottle.constant('csvToJson', csvToJson);
|
||||
bottle.constant('jsonToCsv', jsonToCsv);
|
||||
|
||||
bottle.constant('setTimeout', window.setTimeout);
|
||||
bottle.constant('clearTimeout', window.clearTimeout);
|
||||
bottle.serviceFactory('useTimeoutToggle', useTimeoutToggle, 'setTimeout', 'clearTimeout');
|
||||
|
||||
// FIXME This has to be last. Find a way to delay the creation, perhaps using some kind of runtime factory
|
||||
bottle.constant('store', setUpStore(container));
|
||||
|
|
|
@ -16,5 +16,6 @@ export const provideServices = (bottle: Bottle) => {
|
|||
'Overview',
|
||||
'EditShortUrl',
|
||||
'ManageDomains',
|
||||
'store',
|
||||
);
|
||||
};
|
||||
|
|
|
@ -6,10 +6,7 @@ const isProduction = process.env.NODE_ENV === 'production';
|
|||
export const setUpStore = (container: IContainer) => configureStore({
|
||||
devTools: !isProduction,
|
||||
reducer: combineReducers({
|
||||
// TODO Check if this should be here or not
|
||||
mercureInfo: container.mercureInfoReducer,
|
||||
|
||||
// Nested shlink-web-component reducers
|
||||
shortUrlsList: container.shortUrlsListReducer,
|
||||
shortUrlCreation: container.shortUrlCreationReducer,
|
||||
shortUrlDeletion: container.shortUrlDeletionReducer,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type Bottle from 'bottlejs';
|
||||
import { prop } from 'ramda';
|
||||
import type { ConnectDecorator } from '../../../container/types';
|
||||
import type { ConnectDecorator } from '../../container';
|
||||
import { DomainSelector } from '../DomainSelector';
|
||||
import { ManageDomains } from '../ManageDomains';
|
||||
import { editDomainRedirects } from '../reducers/domainRedirects';
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { container } from './container';
|
||||
import type { ShlinkWebComponentType } from './ShlinkWebComponent';
|
||||
|
||||
export const { ShlinkWebComponent } = container;
|
||||
|
||||
export type { ShlinkWebComponentType } from './ShlinkWebComponent';
|
||||
export const ShlinkWebComponent = container.ShlinkWebComponent as ShlinkWebComponentType;
|
||||
|
|
|
@ -19,14 +19,15 @@ const initialState: MercureInfo = {
|
|||
export const mercureInfoReducerCreator = (buildShlinkApiClient: ShlinkApiClientBuilder) => {
|
||||
const loadMercureInfo = createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/loadMercureInfo`,
|
||||
(_: void, { getState }): Promise<ShlinkMercureInfo> => {
|
||||
const { settings } = getState();
|
||||
if (!settings.realTimeUpdates.enabled) {
|
||||
throw new Error('Real time updates not enabled');
|
||||
}
|
||||
(_: void, { getState }): Promise<ShlinkMercureInfo> =>
|
||||
// TODO Get settings here, where info is only available via hook
|
||||
// const { settings } = getState();
|
||||
// if (!settings.realTimeUpdates.enabled) {
|
||||
// throw new Error('Real time updates not enabled');
|
||||
// }
|
||||
|
||||
return buildShlinkApiClient(getState).mercureInfo();
|
||||
},
|
||||
buildShlinkApiClient(getState).mercureInfo()
|
||||
,
|
||||
);
|
||||
|
||||
const { reducer } = createSlice({
|
||||
|
|
|
@ -7,7 +7,6 @@ import type { SelectedServer } from '../../servers/data';
|
|||
import { getServerId } from '../../servers/data';
|
||||
import { HighlightCard } from '../../servers/helpers/HighlightCard';
|
||||
import { VisitsHighlightCard } from '../../servers/helpers/VisitsHighlightCard';
|
||||
import type { Settings } from '../../settings/reducers/settings';
|
||||
import { prettify } from '../../utils/helpers/numbers';
|
||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||
import { Topics } from '../mercure/helpers/Topics';
|
||||
|
@ -17,6 +16,7 @@ import { ITEMS_IN_OVERVIEW_PAGE } from '../short-urls/reducers/shortUrlsList';
|
|||
import type { ShortUrlsTableType } from '../short-urls/ShortUrlsTable';
|
||||
import type { TagsList } from '../tags/reducers/tagsList';
|
||||
import { useFeature } from '../utils/features';
|
||||
import { useSetting } from '../utils/settings';
|
||||
import type { VisitsOverview } from '../visits/reducers/visitsOverview';
|
||||
|
||||
interface OverviewConnectProps {
|
||||
|
@ -27,7 +27,6 @@ interface OverviewConnectProps {
|
|||
selectedServer: SelectedServer;
|
||||
visitsOverview: VisitsOverview;
|
||||
loadVisitsOverview: Function;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export const Overview = (
|
||||
|
@ -41,7 +40,6 @@ export const Overview = (
|
|||
selectedServer,
|
||||
loadVisitsOverview,
|
||||
visitsOverview,
|
||||
settings: { visits },
|
||||
}: OverviewConnectProps) => {
|
||||
const { loading, shortUrls } = shortUrlsList;
|
||||
const { loading: loadingTags } = tagsList;
|
||||
|
@ -49,6 +47,7 @@ export const Overview = (
|
|||
const serverId = getServerId(selectedServer);
|
||||
const linkToNonOrphanVisits = useFeature('nonOrphanVisits');
|
||||
const navigate = useNavigate();
|
||||
const visits = useSetting('visits');
|
||||
|
||||
useEffect(() => {
|
||||
listShortUrls({ itemsPerPage: ITEMS_IN_OVERVIEW_PAGE, orderBy: { field: 'dateCreated', dir: 'DESC' } });
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import type Bottle from 'bottlejs';
|
||||
import type { ConnectDecorator } from '../../container';
|
||||
import { Overview } from '../Overview';
|
||||
|
||||
export function provideServices(bottle: Bottle, connect: ConnectDecorator) {
|
||||
bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable', 'CreateShortUrl');
|
||||
bottle.decorator('Overview', connect(
|
||||
['shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview'],
|
||||
['listShortUrls', 'listTags', 'createNewVisits', 'loadMercureInfo', 'loadVisitsOverview'],
|
||||
));
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import type { FC } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import type { Settings, ShortUrlCreationSettings } from '../../settings/reducers/settings';
|
||||
import type { ShortUrlCreationSettings } from '../utils/settings';
|
||||
import { useSetting } from '../utils/settings';
|
||||
import type { ShortUrlData } from './data';
|
||||
import type { CreateShortUrlResultProps } from './helpers/CreateShortUrlResult';
|
||||
import type { ShortUrlCreation } from './reducers/shortUrlCreation';
|
||||
|
@ -11,7 +12,6 @@ export interface CreateShortUrlProps {
|
|||
}
|
||||
|
||||
interface CreateShortUrlConnectProps extends CreateShortUrlProps {
|
||||
settings: Settings;
|
||||
shortUrlCreation: ShortUrlCreation;
|
||||
createShortUrl: (data: ShortUrlData) => Promise<void>;
|
||||
resetCreateShortUrl: () => void;
|
||||
|
@ -40,8 +40,8 @@ export const CreateShortUrl = (
|
|||
shortUrlCreation,
|
||||
resetCreateShortUrl,
|
||||
basicMode = false,
|
||||
settings: { shortUrlCreation: shortUrlCreationSettings },
|
||||
}: CreateShortUrlConnectProps) => {
|
||||
const shortUrlCreationSettings = useSetting('shortUrlCreation');
|
||||
const initialState = useMemo(() => getInitialState(shortUrlCreationSettings), [shortUrlCreationSettings]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,11 +6,11 @@ import { ExternalLink } from 'react-external-link';
|
|||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Button, Card } from 'reactstrap';
|
||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
||||
import type { Settings } from '../../settings/reducers/settings';
|
||||
import { useGoBack } from '../../utils/helpers/hooks';
|
||||
import { parseQuery } from '../../utils/helpers/query';
|
||||
import { Message } from '../../utils/Message';
|
||||
import { Result } from '../../utils/Result';
|
||||
import { useSetting } from '../utils/settings';
|
||||
import type { ShortUrlIdentifier } from './data';
|
||||
import { shortUrlDataFromShortUrl, urlDecodeShortCode } from './helpers';
|
||||
import type { ShortUrlDetail } from './reducers/shortUrlDetail';
|
||||
|
@ -18,7 +18,6 @@ import type { EditShortUrl as EditShortUrlInfo, ShortUrlEdition } from './reduce
|
|||
import type { ShortUrlFormProps } from './ShortUrlForm';
|
||||
|
||||
interface EditShortUrlConnectProps {
|
||||
settings: Settings;
|
||||
shortUrlDetail: ShortUrlDetail;
|
||||
shortUrlEdition: ShortUrlEdition;
|
||||
getShortUrlDetail: (shortUrl: ShortUrlIdentifier) => void;
|
||||
|
@ -26,7 +25,6 @@ interface EditShortUrlConnectProps {
|
|||
}
|
||||
|
||||
export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
|
||||
settings: { shortUrlCreation: shortUrlCreationSettings },
|
||||
shortUrlDetail,
|
||||
getShortUrlDetail,
|
||||
shortUrlEdition,
|
||||
|
@ -38,6 +36,7 @@ export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
|
|||
const { loading, error, errorData, shortUrl } = shortUrlDetail;
|
||||
const { saving, saved, error: savingError, errorData: savingErrorData } = shortUrlEdition;
|
||||
const { domain } = parseQuery<{ domain?: string }>(search);
|
||||
const shortUrlCreationSettings = useSetting('shortUrlCreation');
|
||||
const initialState = useMemo(
|
||||
() => shortUrlDataFromShortUrl(shortUrl, shortUrlCreationSettings),
|
||||
[shortUrl, shortUrlCreationSettings],
|
||||
|
|
|
@ -4,7 +4,6 @@ import classNames from 'classnames';
|
|||
import { isEmpty, pipe } from 'ramda';
|
||||
import type { FC } from 'react';
|
||||
import { Button, InputGroup, Row, UncontrolledTooltip } from 'reactstrap';
|
||||
import type { Settings } from '../../settings/reducers/settings';
|
||||
import { DateRangeSelector } from '../../utils/dates/DateRangeSelector';
|
||||
import { formatIsoDate } from '../../utils/helpers/date';
|
||||
import type { DateRange } from '../../utils/helpers/dateIntervals';
|
||||
|
@ -14,6 +13,7 @@ import { OrderingDropdown } from '../../utils/OrderingDropdown';
|
|||
import { SearchField } from '../../utils/SearchField';
|
||||
import type { TagsSelectorProps } from '../tags/helpers/TagsSelector';
|
||||
import { useFeature } from '../utils/features';
|
||||
import { useSetting } from '../utils/settings';
|
||||
import type { ShortUrlsOrder, ShortUrlsOrderableFields } from './data';
|
||||
import { SHORT_URLS_ORDERABLE_FIELDS } from './data';
|
||||
import type { ExportShortUrlsBtnProps } from './helpers/ExportShortUrlsBtn';
|
||||
|
@ -23,7 +23,6 @@ import './ShortUrlsFilteringBar.scss';
|
|||
|
||||
interface ShortUrlsFilteringProps {
|
||||
order: ShortUrlsOrder;
|
||||
settings: Settings;
|
||||
handleOrderBy: (orderField?: ShortUrlsOrderableFields, orderDir?: OrderDir) => void;
|
||||
className?: string;
|
||||
shortUrlsAmount?: number;
|
||||
|
@ -32,7 +31,7 @@ interface ShortUrlsFilteringProps {
|
|||
export const ShortUrlsFilteringBar = (
|
||||
ExportShortUrlsBtn: FC<ExportShortUrlsBtnProps>,
|
||||
TagsSelector: FC<TagsSelectorProps>,
|
||||
): FC<ShortUrlsFilteringProps> => ({ className, shortUrlsAmount, order, handleOrderBy, settings }) => {
|
||||
): FC<ShortUrlsFilteringProps> => ({ className, shortUrlsAmount, order, handleOrderBy }) => {
|
||||
const [filter, toFirstPage] = useShortUrlsQuery();
|
||||
const {
|
||||
search,
|
||||
|
@ -45,6 +44,7 @@ export const ShortUrlsFilteringBar = (
|
|||
tagsMode = 'any',
|
||||
} = filter;
|
||||
const supportsDisabledFiltering = useFeature('filterDisabledUrls');
|
||||
const visitsSettings = useSetting('visits');
|
||||
|
||||
const setDates = pipe(
|
||||
({ startDate: theStartDate, endDate: theEndDate }: DateRange) => ({
|
||||
|
@ -95,7 +95,7 @@ export const ShortUrlsFilteringBar = (
|
|||
<ShortUrlsFilterDropdown
|
||||
className="ms-0 ms-md-2 mt-3 mt-md-0"
|
||||
selected={{
|
||||
excludeBots: excludeBots ?? settings.visits?.excludeBots,
|
||||
excludeBots: excludeBots ?? visitsSettings?.excludeBots,
|
||||
excludeMaxVisitsReached,
|
||||
excludePastValidUntil,
|
||||
}}
|
||||
|
|
|
@ -5,7 +5,6 @@ import { Card } from 'reactstrap';
|
|||
import type { ShlinkShortUrlsListParams, ShlinkShortUrlsOrder } from '../../api/types';
|
||||
import type { SelectedServer } from '../../servers/data';
|
||||
import { getServerId } from '../../servers/data';
|
||||
import type { Settings } from '../../settings/reducers/settings';
|
||||
import { DEFAULT_SHORT_URLS_ORDERING } from '../../settings/reducers/settings';
|
||||
import type { OrderDir } from '../../utils/helpers/ordering';
|
||||
import { determineOrderDir } from '../../utils/helpers/ordering';
|
||||
|
@ -13,6 +12,7 @@ import { TableOrderIcon } from '../../utils/table/TableOrderIcon';
|
|||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||
import { Topics } from '../mercure/helpers/Topics';
|
||||
import { useFeature } from '../utils/features';
|
||||
import { useSettings } from '../utils/settings';
|
||||
import type { ShortUrlsOrder, ShortUrlsOrderableFields } from './data';
|
||||
import { useShortUrlsQuery } from './helpers/hooks';
|
||||
import { Paginator } from './Paginator';
|
||||
|
@ -24,17 +24,17 @@ interface ShortUrlsListProps {
|
|||
selectedServer: SelectedServer;
|
||||
shortUrlsList: ShortUrlsListState;
|
||||
listShortUrls: (params: ShlinkShortUrlsListParams) => void;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export const ShortUrlsList = (
|
||||
ShortUrlsTable: ShortUrlsTableType,
|
||||
ShortUrlsFilteringBar: ShortUrlsFilteringBarType,
|
||||
) => boundToMercureHub(({ listShortUrls, shortUrlsList, selectedServer, settings }: ShortUrlsListProps) => {
|
||||
) => boundToMercureHub(({ listShortUrls, shortUrlsList, selectedServer }: ShortUrlsListProps) => {
|
||||
const serverId = getServerId(selectedServer);
|
||||
const { page } = useParams();
|
||||
const location = useLocation();
|
||||
const [filter, toFirstPage] = useShortUrlsQuery();
|
||||
const settings = useSettings();
|
||||
const {
|
||||
tags,
|
||||
search,
|
||||
|
@ -104,7 +104,6 @@ export const ShortUrlsList = (
|
|||
shortUrlsAmount={shortUrlsList.shortUrls?.pagination.totalItems}
|
||||
order={actualOrderBy}
|
||||
handleOrderBy={handleOrderBy}
|
||||
settings={settings}
|
||||
className="mb-3"
|
||||
/>
|
||||
<Card body className="pb-0">
|
||||
|
|
|
@ -2,11 +2,11 @@ import type { FC } from 'react';
|
|||
import { useEffect, useRef } from 'react';
|
||||
import { ExternalLink } from 'react-external-link';
|
||||
import type { SelectedServer } from '../../../servers/data';
|
||||
import type { Settings } from '../../../settings/reducers/settings';
|
||||
import { CopyToClipboardIcon } from '../../../utils/CopyToClipboardIcon';
|
||||
import { Time } from '../../../utils/dates/Time';
|
||||
import type { TimeoutToggle } from '../../../utils/helpers/hooks';
|
||||
import type { ColorGenerator } from '../../../utils/services/ColorGenerator';
|
||||
import { useSetting } from '../../utils/settings';
|
||||
import type { ShortUrl } from '../data';
|
||||
import { useShortUrlsQuery } from './hooks';
|
||||
import type { ShortUrlsRowMenuType } from './ShortUrlsRowMenu';
|
||||
|
@ -21,22 +21,18 @@ interface ShortUrlsRowProps {
|
|||
shortUrl: ShortUrl;
|
||||
}
|
||||
|
||||
interface ShortUrlsRowConnectProps extends ShortUrlsRowProps {
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export type ShortUrlsRowType = FC<ShortUrlsRowProps>;
|
||||
|
||||
export const ShortUrlsRow = (
|
||||
ShortUrlsRowMenu: ShortUrlsRowMenuType,
|
||||
colorGenerator: ColorGenerator,
|
||||
useTimeoutToggle: TimeoutToggle,
|
||||
) => ({ shortUrl, selectedServer, onTagClick, settings }: ShortUrlsRowConnectProps) => {
|
||||
) => ({ shortUrl, selectedServer, onTagClick }: ShortUrlsRowProps) => {
|
||||
const [copiedToClipboard, setCopiedToClipboard] = useTimeoutToggle();
|
||||
const [active, setActive] = useTimeoutToggle(false, 500);
|
||||
const isFirstRun = useRef(true);
|
||||
const [{ excludeBots }] = useShortUrlsQuery();
|
||||
const { visits } = settings;
|
||||
const visits = useSetting('visits');
|
||||
const doExcludeBots = excludeBots ?? visits?.excludeBots;
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { isNil } from 'ramda';
|
||||
import type { ShortUrlCreationSettings } from '../../../settings/reducers/settings';
|
||||
import type { OptionalString } from '../../../utils/utils';
|
||||
import type { ShortUrlCreationSettings } from '../../utils/settings';
|
||||
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
|
||||
import type { ShortUrl, ShortUrlData } from '../data';
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type Bottle from 'bottlejs';
|
||||
import { prop } from 'ramda';
|
||||
import type { ConnectDecorator } from '../../../container/types';
|
||||
import type { ConnectDecorator } from '../../container';
|
||||
import { CreateShortUrl } from '../CreateShortUrl';
|
||||
import { EditShortUrl } from '../EditShortUrl';
|
||||
import { CreateShortUrlResult } from '../helpers/CreateShortUrlResult';
|
||||
|
@ -23,15 +23,12 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
// Components
|
||||
bottle.serviceFactory('ShortUrlsList', ShortUrlsList, 'ShortUrlsTable', 'ShortUrlsFilteringBar');
|
||||
bottle.decorator('ShortUrlsList', connect(
|
||||
['selectedServer', 'mercureInfo', 'shortUrlsList', 'settings'],
|
||||
['selectedServer', 'mercureInfo', 'shortUrlsList'],
|
||||
['listShortUrls', 'createNewVisits', 'loadMercureInfo'],
|
||||
));
|
||||
|
||||
bottle.serviceFactory('ShortUrlsTable', ShortUrlsTable, 'ShortUrlsRow');
|
||||
|
||||
bottle.serviceFactory('ShortUrlsRow', ShortUrlsRow, 'ShortUrlsRowMenu', 'ColorGenerator', 'useTimeoutToggle');
|
||||
bottle.decorator('ShortUrlsRow', connect(['settings']));
|
||||
|
||||
bottle.serviceFactory('ShortUrlsRowMenu', ShortUrlsRowMenu, 'DeleteShortUrlModal', 'QrCodeModal');
|
||||
bottle.serviceFactory('CreateShortUrlResult', CreateShortUrlResult, 'useTimeoutToggle');
|
||||
bottle.serviceFactory('ShortUrlForm', ShortUrlForm, 'TagsSelector', 'DomainSelector');
|
||||
|
@ -39,12 +36,12 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
bottle.serviceFactory('CreateShortUrl', CreateShortUrl, 'ShortUrlForm', 'CreateShortUrlResult');
|
||||
bottle.decorator(
|
||||
'CreateShortUrl',
|
||||
connect(['shortUrlCreation', 'settings'], ['createShortUrl', 'resetCreateShortUrl']),
|
||||
connect(['shortUrlCreation'], ['createShortUrl', 'resetCreateShortUrl']),
|
||||
);
|
||||
|
||||
bottle.serviceFactory('EditShortUrl', EditShortUrl, 'ShortUrlForm');
|
||||
bottle.decorator('EditShortUrl', connect(
|
||||
['shortUrlDetail', 'shortUrlEdition', 'settings'],
|
||||
['shortUrlDetail', 'shortUrlEdition'],
|
||||
['getShortUrlDetail', 'editShortUrl'],
|
||||
));
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import { useEffect, useState } from 'react';
|
|||
import { Row } from 'reactstrap';
|
||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
||||
import type { SelectedServer } from '../../servers/data';
|
||||
import type { Settings } from '../../settings/reducers/settings';
|
||||
import { determineOrderDir, sortList } from '../../utils/helpers/ordering';
|
||||
import { Message } from '../../utils/Message';
|
||||
import { OrderingDropdown } from '../../utils/OrderingDropdown';
|
||||
|
@ -12,6 +11,7 @@ import { Result } from '../../utils/Result';
|
|||
import { SearchField } from '../../utils/SearchField';
|
||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||
import { Topics } from '../mercure/helpers/Topics';
|
||||
import { useSettings } from '../utils/settings';
|
||||
import type { SimplifiedTag } from './data';
|
||||
import type { TagsOrder, TagsOrderableFields } from './data/TagsListChildrenProps';
|
||||
import { TAGS_ORDERABLE_FIELDS } from './data/TagsListChildrenProps';
|
||||
|
@ -23,12 +23,12 @@ export interface TagsListProps {
|
|||
forceListTags: Function;
|
||||
tagsList: TagsListState;
|
||||
selectedServer: SelectedServer;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
export const TagsList = (TagsTable: FC<TagsTableProps>) => boundToMercureHub((
|
||||
{ filterTags, forceListTags, tagsList, selectedServer, settings }: TagsListProps,
|
||||
{ filterTags, forceListTags, tagsList, selectedServer }: TagsListProps,
|
||||
) => {
|
||||
const settings = useSettings();
|
||||
const [order, setOrder] = useState<TagsOrder>(settings.tags?.defaultOrdering ?? {});
|
||||
const resolveSortedTags = pipe(
|
||||
() => tagsList.filteredTags.map((tag): SimplifiedTag => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect } from 'react';
|
||||
import type { SuggestionComponentProps, TagComponentProps } from 'react-tag-autocomplete';
|
||||
import ReactTags from 'react-tag-autocomplete';
|
||||
import type { Settings } from '../../../settings/reducers/settings';
|
||||
import type { ColorGenerator } from '../../../utils/services/ColorGenerator';
|
||||
import { useSetting } from '../../utils/settings';
|
||||
import type { TagsList } from '../reducers/tagsList';
|
||||
import { Tag } from './Tag';
|
||||
import { TagBullet } from './TagBullet';
|
||||
|
@ -17,19 +17,19 @@ export interface TagsSelectorProps {
|
|||
interface TagsSelectorConnectProps extends TagsSelectorProps {
|
||||
listTags: () => void;
|
||||
tagsList: TagsList;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
const toComponentTag = (tag: string) => ({ id: tag, name: tag });
|
||||
|
||||
export const TagsSelector = (colorGenerator: ColorGenerator) => (
|
||||
{ selectedTags, onChange, placeholder, listTags, tagsList, settings, allowNew = true }: TagsSelectorConnectProps,
|
||||
{ selectedTags, onChange, placeholder, listTags, tagsList, allowNew = true }: TagsSelectorConnectProps,
|
||||
) => {
|
||||
const shortUrlCreation = useSetting('shortUrlCreation');
|
||||
useEffect(() => {
|
||||
listTags();
|
||||
}, []);
|
||||
|
||||
const searchMode = settings.shortUrlCreation?.tagFilteringMode ?? 'startsWith';
|
||||
const searchMode = shortUrlCreation?.tagFilteringMode ?? 'startsWith';
|
||||
const ReactTagsTag = ({ tag, onDelete }: TagComponentProps) =>
|
||||
<Tag colorGenerator={colorGenerator} text={tag.name} clearable className="react-tags__tag" onClose={onDelete} />;
|
||||
const ReactTagsSuggestion = ({ item }: SuggestionComponentProps) => (
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type { IContainer } from 'bottlejs';
|
||||
import type Bottle from 'bottlejs';
|
||||
import { prop } from 'ramda';
|
||||
import type { ConnectDecorator } from '../../../container/types';
|
||||
import type { ConnectDecorator } from '../../container';
|
||||
import { DeleteTagConfirmModal } from '../helpers/DeleteTagConfirmModal';
|
||||
import { EditTagModal } from '../helpers/EditTagModal';
|
||||
import { TagsSelector } from '../helpers/TagsSelector';
|
||||
|
@ -15,7 +15,7 @@ import { TagsTableRow } from '../TagsTableRow';
|
|||
export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||
// Components
|
||||
bottle.serviceFactory('TagsSelector', TagsSelector, 'ColorGenerator');
|
||||
bottle.decorator('TagsSelector', connect(['tagsList', 'settings'], ['listTags']));
|
||||
bottle.decorator('TagsSelector', connect(['tagsList'], ['listTags']));
|
||||
|
||||
bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal);
|
||||
bottle.decorator('DeleteTagConfirmModal', connect(['tagDelete'], ['deleteTag', 'tagDeleted']));
|
||||
|
@ -24,13 +24,11 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
bottle.decorator('EditTagModal', connect(['tagEdit'], ['editTag', 'tagEdited']));
|
||||
|
||||
bottle.serviceFactory('TagsTableRow', TagsTableRow, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator');
|
||||
bottle.decorator('TagsTableRow', connect(['settings']));
|
||||
|
||||
bottle.serviceFactory('TagsTable', TagsTable, 'TagsTableRow');
|
||||
|
||||
bottle.serviceFactory('TagsList', TagsList, 'TagsTable');
|
||||
bottle.decorator('TagsList', connect(
|
||||
['tagsList', 'selectedServer', 'mercureInfo', 'settings'],
|
||||
['tagsList', 'selectedServer', 'mercureInfo'],
|
||||
['forceListTags', 'filterTags', 'createNewVisits', 'loadMercureInfo'],
|
||||
));
|
||||
|
||||
|
|
83
src/shlink-web-component/utils/settings.ts
Normal file
83
src/shlink-web-component/utils/settings.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { createContext, useContext } from 'react';
|
||||
import type { DateInterval } from '../../utils/helpers/dateIntervals';
|
||||
import type { Theme } from '../../utils/theme';
|
||||
import type { ShortUrlsOrder } from '../short-urls/data';
|
||||
import type { TagsOrder } from '../tags/data/TagsListChildrenProps';
|
||||
|
||||
export const DEFAULT_SHORT_URLS_ORDERING: ShortUrlsOrder = {
|
||||
field: 'dateCreated',
|
||||
dir: 'DESC',
|
||||
};
|
||||
|
||||
/**
|
||||
* Important! When adding new props in the main Settings interface or any of the nested props, they have to be set as
|
||||
* optional, as old instances of the app will load partial objects from local storage until it is saved again.
|
||||
*/
|
||||
|
||||
export interface RealTimeUpdatesSettings {
|
||||
enabled: boolean;
|
||||
interval?: number;
|
||||
}
|
||||
|
||||
export type TagFilteringMode = 'startsWith' | 'includes';
|
||||
|
||||
export interface ShortUrlCreationSettings {
|
||||
validateUrls: boolean;
|
||||
tagFilteringMode?: TagFilteringMode;
|
||||
forwardQuery?: boolean;
|
||||
}
|
||||
|
||||
export interface UiSettings {
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
export interface VisitsSettings {
|
||||
defaultInterval: DateInterval;
|
||||
excludeBots?: boolean;
|
||||
}
|
||||
|
||||
export interface TagsSettings {
|
||||
defaultOrdering?: TagsOrder;
|
||||
}
|
||||
|
||||
export interface ShortUrlsListSettings {
|
||||
defaultOrdering?: ShortUrlsOrder;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
realTimeUpdates?: RealTimeUpdatesSettings;
|
||||
shortUrlCreation?: ShortUrlCreationSettings;
|
||||
shortUrlsList?: ShortUrlsListSettings;
|
||||
ui?: UiSettings;
|
||||
visits?: VisitsSettings;
|
||||
tags?: TagsSettings;
|
||||
}
|
||||
|
||||
const defaultSettings: Settings = {
|
||||
realTimeUpdates: {
|
||||
enabled: true,
|
||||
},
|
||||
shortUrlCreation: {
|
||||
validateUrls: false,
|
||||
},
|
||||
ui: {
|
||||
theme: 'light',
|
||||
},
|
||||
visits: {
|
||||
defaultInterval: 'last30Days',
|
||||
},
|
||||
shortUrlsList: {
|
||||
defaultOrdering: DEFAULT_SHORT_URLS_ORDERING,
|
||||
},
|
||||
};
|
||||
|
||||
const SettingsContext = createContext<Settings | undefined>(defaultSettings);
|
||||
|
||||
export const SettingsProvider = SettingsContext.Provider;
|
||||
|
||||
export const useSettings = (): Settings => useContext(SettingsContext) ?? defaultSettings;
|
||||
|
||||
export const useSetting = <T extends keyof Settings>(settingName: T): Settings[T] => {
|
||||
const settings = useSettings();
|
||||
return settings[settingName];
|
||||
};
|
|
@ -6,12 +6,11 @@ import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
|||
import { Topics } from '../mercure/helpers/Topics';
|
||||
import type { DomainVisits as DomainVisitsState, LoadDomainVisits } from './reducers/domainVisits';
|
||||
import type { NormalizedVisit } from './types';
|
||||
import type { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||
import { toApiParams } from './types/helpers';
|
||||
import { VisitsHeader } from './VisitsHeader';
|
||||
import { VisitsStats } from './VisitsStats';
|
||||
|
||||
export interface DomainVisitsProps extends CommonVisitsProps {
|
||||
export interface DomainVisitsProps {
|
||||
getDomainVisits: (params: LoadDomainVisits) => void;
|
||||
domainVisits: DomainVisitsState;
|
||||
cancelGetDomainVisits: () => void;
|
||||
|
@ -21,7 +20,6 @@ export const DomainVisits = ({ exportVisits }: ReportExporter) => boundToMercure
|
|||
getDomainVisits,
|
||||
domainVisits,
|
||||
cancelGetDomainVisits,
|
||||
settings,
|
||||
}: DomainVisitsProps) => {
|
||||
const goBack = useGoBack();
|
||||
const { domain = '' } = useParams();
|
||||
|
@ -35,7 +33,6 @@ export const DomainVisits = ({ exportVisits }: ReportExporter) => boundToMercure
|
|||
getVisits={loadVisits}
|
||||
cancelGetVisits={cancelGetDomainVisits}
|
||||
visitsInfo={domainVisits}
|
||||
settings={settings}
|
||||
exportCsv={exportCsv}
|
||||
>
|
||||
<VisitsHeader goBack={goBack} visits={domainVisits.visits} title={`"${authority}" visits`} />
|
||||
|
|
|
@ -4,12 +4,11 @@ import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
|||
import { Topics } from '../mercure/helpers/Topics';
|
||||
import type { LoadVisits, VisitsInfo } from './reducers/types';
|
||||
import type { NormalizedVisit, VisitsParams } from './types';
|
||||
import type { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||
import { toApiParams } from './types/helpers';
|
||||
import { VisitsHeader } from './VisitsHeader';
|
||||
import { VisitsStats } from './VisitsStats';
|
||||
|
||||
export interface NonOrphanVisitsProps extends CommonVisitsProps {
|
||||
export interface NonOrphanVisitsProps {
|
||||
getNonOrphanVisits: (params: LoadVisits) => void;
|
||||
nonOrphanVisits: VisitsInfo;
|
||||
cancelGetNonOrphanVisits: () => void;
|
||||
|
@ -19,7 +18,6 @@ export const NonOrphanVisits = ({ exportVisits }: ReportExporter) => boundToMerc
|
|||
getNonOrphanVisits,
|
||||
nonOrphanVisits,
|
||||
cancelGetNonOrphanVisits,
|
||||
settings,
|
||||
}: NonOrphanVisitsProps) => {
|
||||
const goBack = useGoBack();
|
||||
const exportCsv = (visits: NormalizedVisit[]) => exportVisits('non_orphan_visits.csv', visits);
|
||||
|
@ -31,7 +29,6 @@ export const NonOrphanVisits = ({ exportVisits }: ReportExporter) => boundToMerc
|
|||
getVisits={loadVisits}
|
||||
cancelGetVisits={cancelGetNonOrphanVisits}
|
||||
visitsInfo={nonOrphanVisits}
|
||||
settings={settings}
|
||||
exportCsv={exportCsv}
|
||||
>
|
||||
<VisitsHeader title="Non-orphan visits" goBack={goBack} visits={nonOrphanVisits.visits} />
|
||||
|
|
|
@ -5,12 +5,11 @@ import { Topics } from '../mercure/helpers/Topics';
|
|||
import type { LoadOrphanVisits } from './reducers/orphanVisits';
|
||||
import type { VisitsInfo } from './reducers/types';
|
||||
import type { NormalizedVisit, VisitsParams } from './types';
|
||||
import type { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||
import { toApiParams } from './types/helpers';
|
||||
import { VisitsHeader } from './VisitsHeader';
|
||||
import { VisitsStats } from './VisitsStats';
|
||||
|
||||
export interface OrphanVisitsProps extends CommonVisitsProps {
|
||||
export interface OrphanVisitsProps {
|
||||
getOrphanVisits: (params: LoadOrphanVisits) => void;
|
||||
orphanVisits: VisitsInfo;
|
||||
cancelGetOrphanVisits: () => void;
|
||||
|
@ -20,7 +19,6 @@ export const OrphanVisits = ({ exportVisits }: ReportExporter) => boundToMercure
|
|||
getOrphanVisits,
|
||||
orphanVisits,
|
||||
cancelGetOrphanVisits,
|
||||
settings,
|
||||
}: OrphanVisitsProps) => {
|
||||
const goBack = useGoBack();
|
||||
const exportCsv = (visits: NormalizedVisit[]) => exportVisits('orphan_visits.csv', visits);
|
||||
|
@ -33,7 +31,6 @@ export const OrphanVisits = ({ exportVisits }: ReportExporter) => boundToMercure
|
|||
getVisits={loadVisits}
|
||||
cancelGetVisits={cancelGetOrphanVisits}
|
||||
visitsInfo={orphanVisits}
|
||||
settings={settings}
|
||||
exportCsv={exportCsv}
|
||||
isOrphanVisits
|
||||
>
|
||||
|
|
|
@ -11,11 +11,10 @@ import type { ShortUrlDetail } from '../short-urls/reducers/shortUrlDetail';
|
|||
import type { LoadShortUrlVisits, ShortUrlVisits as ShortUrlVisitsState } from './reducers/shortUrlVisits';
|
||||
import { ShortUrlVisitsHeader } from './ShortUrlVisitsHeader';
|
||||
import type { NormalizedVisit, VisitsParams } from './types';
|
||||
import type { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||
import { toApiParams } from './types/helpers';
|
||||
import { VisitsStats } from './VisitsStats';
|
||||
|
||||
export interface ShortUrlVisitsProps extends CommonVisitsProps {
|
||||
export interface ShortUrlVisitsProps {
|
||||
getShortUrlVisits: (params: LoadShortUrlVisits) => void;
|
||||
shortUrlVisits: ShortUrlVisitsState;
|
||||
getShortUrlDetail: (shortUrl: ShortUrlIdentifier) => void;
|
||||
|
@ -29,7 +28,6 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
|
|||
getShortUrlVisits,
|
||||
getShortUrlDetail,
|
||||
cancelGetShortUrlVisits,
|
||||
settings,
|
||||
}: ShortUrlVisitsProps) => {
|
||||
const { shortCode = '' } = useParams<{ shortCode: string }>();
|
||||
const { search } = useLocation();
|
||||
|
@ -54,7 +52,6 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
|
|||
getVisits={loadVisits}
|
||||
cancelGetVisits={cancelGetShortUrlVisits}
|
||||
visitsInfo={shortUrlVisits}
|
||||
settings={settings}
|
||||
exportCsv={exportCsv}
|
||||
>
|
||||
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
||||
|
|
|
@ -8,11 +8,10 @@ import { Topics } from '../mercure/helpers/Topics';
|
|||
import type { LoadTagVisits, TagVisits as TagVisitsState } from './reducers/tagVisits';
|
||||
import { TagVisitsHeader } from './TagVisitsHeader';
|
||||
import type { NormalizedVisit } from './types';
|
||||
import type { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||
import { toApiParams } from './types/helpers';
|
||||
import { VisitsStats } from './VisitsStats';
|
||||
|
||||
export interface TagVisitsProps extends CommonVisitsProps {
|
||||
export interface TagVisitsProps {
|
||||
getTagVisits: (params: LoadTagVisits) => void;
|
||||
tagVisits: TagVisitsState;
|
||||
cancelGetTagVisits: () => void;
|
||||
|
@ -22,7 +21,6 @@ export const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: Repo
|
|||
getTagVisits,
|
||||
tagVisits,
|
||||
cancelGetTagVisits,
|
||||
settings,
|
||||
}: TagVisitsProps) => {
|
||||
const goBack = useGoBack();
|
||||
const { tag = '' } = useParams();
|
||||
|
@ -35,7 +33,6 @@ export const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: Repo
|
|||
getVisits={loadVisits}
|
||||
cancelGetVisits={cancelGetTagVisits}
|
||||
visitsInfo={tagVisits}
|
||||
settings={settings}
|
||||
exportCsv={exportCsv}
|
||||
>
|
||||
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
|
||||
|
|
|
@ -8,7 +8,6 @@ import { useEffect, useMemo, useRef, useState } from 'react';
|
|||
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { Button, Progress, Row } from 'reactstrap';
|
||||
import { ShlinkApiError } from '../../api/ShlinkApiError';
|
||||
import type { Settings } from '../../settings/reducers/settings';
|
||||
import { DateRangeSelector } from '../../utils/dates/DateRangeSelector';
|
||||
import { ExportBtn } from '../../utils/ExportBtn';
|
||||
import type { DateInterval, DateRange } from '../../utils/helpers/dateIntervals';
|
||||
|
@ -17,6 +16,7 @@ import { prettify } from '../../utils/helpers/numbers';
|
|||
import { Message } from '../../utils/Message';
|
||||
import { NavPillItem, NavPills } from '../../utils/NavPills';
|
||||
import { Result } from '../../utils/Result';
|
||||
import { useSetting } from '../utils/settings';
|
||||
import { DoughnutChartCard } from './charts/DoughnutChartCard';
|
||||
import { LineChartCard } from './charts/LineChartCard';
|
||||
import { SortableBarChartCard } from './charts/SortableBarChartCard';
|
||||
|
@ -33,7 +33,6 @@ import { VisitsTable } from './VisitsTable';
|
|||
export type VisitsStatsProps = PropsWithChildren<{
|
||||
getVisits: (params: VisitsParams, doIntervalFallback?: boolean) => void;
|
||||
visitsInfo: VisitsInfo;
|
||||
settings: Settings;
|
||||
cancelGetVisits: () => void;
|
||||
exportCsv: (visits: NormalizedVisit[]) => void;
|
||||
isOrphanVisits?: boolean;
|
||||
|
@ -61,12 +60,12 @@ export const VisitsStats: FC<VisitsStatsProps> = ({
|
|||
visitsInfo,
|
||||
getVisits,
|
||||
cancelGetVisits,
|
||||
settings,
|
||||
exportCsv,
|
||||
isOrphanVisits = false,
|
||||
}) => {
|
||||
const { visits, loading, loadingLarge, error, errorData, progress, fallbackInterval } = visitsInfo;
|
||||
const [{ dateRange, visitsFilter }, updateFiltering] = useVisitsQuery();
|
||||
const visitsSettings = useSetting('visits');
|
||||
const setDates = pipe(
|
||||
({ startDate: theStartDate, endDate: theEndDate }: DateRange) => ({
|
||||
dateRange: {
|
||||
|
@ -77,7 +76,7 @@ export const VisitsStats: FC<VisitsStatsProps> = ({
|
|||
updateFiltering,
|
||||
);
|
||||
const initialInterval = useRef<DateRange | DateInterval>(
|
||||
dateRange ?? fallbackInterval ?? settings.visits?.defaultInterval ?? 'last30Days',
|
||||
dateRange ?? fallbackInterval ?? visitsSettings?.defaultInterval ?? 'last30Days',
|
||||
);
|
||||
const [highlightedVisits, setHighlightedVisits] = useState<NormalizedVisit[]>([]);
|
||||
const [highlightedLabel, setHighlightedLabel] = useState<string | undefined>();
|
||||
|
@ -92,7 +91,7 @@ export const VisitsStats: FC<VisitsStatsProps> = ({
|
|||
);
|
||||
const resolvedFilter = useMemo(() => ({
|
||||
...visitsFilter,
|
||||
excludeBots: visitsFilter.excludeBots ?? settings.visits?.excludeBots,
|
||||
excludeBots: visitsFilter.excludeBots ?? visitsSettings?.excludeBots,
|
||||
}), [visitsFilter]);
|
||||
const mapLocations = values(citiesForMap);
|
||||
|
||||
|
@ -122,7 +121,7 @@ export const VisitsStats: FC<VisitsStatsProps> = ({
|
|||
}, [dateRange, visitsFilter]);
|
||||
useEffect(() => {
|
||||
// As soon as the fallback is loaded, if the initial interval used the settings one, we do fall back
|
||||
if (fallbackInterval && initialInterval.current === (settings.visits?.defaultInterval ?? 'last30Days')) {
|
||||
if (fallbackInterval && initialInterval.current === (visitsSettings?.defaultInterval ?? 'last30Days')) {
|
||||
initialInterval.current = fallbackInterval;
|
||||
}
|
||||
}, [fallbackInterval]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type Bottle from 'bottlejs';
|
||||
import { prop } from 'ramda';
|
||||
import type { ConnectDecorator } from '../../../container/types';
|
||||
import type { ConnectDecorator } from '../../container';
|
||||
import { DomainVisits } from '../DomainVisits';
|
||||
import { MapModal } from '../helpers/MapModal';
|
||||
import { NonOrphanVisits } from '../NonOrphanVisits';
|
||||
|
@ -22,31 +22,31 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
|
||||
bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'ReportExporter');
|
||||
bottle.decorator('ShortUrlVisits', connect(
|
||||
['shortUrlVisits', 'shortUrlDetail', 'mercureInfo', 'settings'],
|
||||
['shortUrlVisits', 'shortUrlDetail', 'mercureInfo'],
|
||||
['getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisits', 'loadMercureInfo'],
|
||||
));
|
||||
|
||||
bottle.serviceFactory('TagVisits', TagVisits, 'ColorGenerator', 'ReportExporter');
|
||||
bottle.decorator('TagVisits', connect(
|
||||
['tagVisits', 'mercureInfo', 'settings'],
|
||||
['tagVisits', 'mercureInfo'],
|
||||
['getTagVisits', 'cancelGetTagVisits', 'createNewVisits', 'loadMercureInfo'],
|
||||
));
|
||||
|
||||
bottle.serviceFactory('DomainVisits', DomainVisits, 'ReportExporter');
|
||||
bottle.decorator('DomainVisits', connect(
|
||||
['domainVisits', 'mercureInfo', 'settings'],
|
||||
['domainVisits', 'mercureInfo'],
|
||||
['getDomainVisits', 'cancelGetDomainVisits', 'createNewVisits', 'loadMercureInfo'],
|
||||
));
|
||||
|
||||
bottle.serviceFactory('OrphanVisits', OrphanVisits, 'ReportExporter');
|
||||
bottle.decorator('OrphanVisits', connect(
|
||||
['orphanVisits', 'mercureInfo', 'settings'],
|
||||
['orphanVisits', 'mercureInfo'],
|
||||
['getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo'],
|
||||
));
|
||||
|
||||
bottle.serviceFactory('NonOrphanVisits', NonOrphanVisits, 'ReportExporter');
|
||||
bottle.decorator('NonOrphanVisits', connect(
|
||||
['nonOrphanVisits', 'mercureInfo', 'settings'],
|
||||
['nonOrphanVisits', 'mercureInfo'],
|
||||
['getNonOrphanVisits', 'cancelGetNonOrphanVisits', 'createNewVisits', 'loadMercureInfo'],
|
||||
));
|
||||
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
import type { Settings } from '../../../settings/reducers/settings';
|
||||
|
||||
export interface CommonVisitsProps {
|
||||
settings: Settings;
|
||||
}
|
Loading…
Add table
Reference in a new issue