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