mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 14:57:22 +03:00
Merge pull request #950 from acelaya-forks/feature/remove-ramda
Feature/remove ramda
This commit is contained in:
commit
788026f2d1
19 changed files with 79 additions and 98 deletions
3
.github/dependabot.yml
vendored
3
.github/dependabot.yml
vendored
|
@ -12,6 +12,9 @@ updates:
|
|||
fontawesome:
|
||||
patterns:
|
||||
- '@fortawesome*'
|
||||
shlink:
|
||||
patterns:
|
||||
- '@shlinkio*'
|
||||
ignore:
|
||||
# Bootstrap can introduce visual breaking changes on styles
|
||||
# Ignore it, since the plan is to remove it anyway
|
||||
|
|
40
package-lock.json
generated
40
package-lock.json
generated
|
@ -15,6 +15,7 @@
|
|||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@json2csv/plainjs": "^7.0.3",
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"@shlinkio/data-manipulation": "^1.0.1",
|
||||
"@shlinkio/shlink-frontend-kit": "^0.4.0",
|
||||
"@shlinkio/shlink-js-sdk": "^0.1.0",
|
||||
"@shlinkio/shlink-web-component": "^0.3.5",
|
||||
|
@ -24,7 +25,6 @@
|
|||
"compare-versions": "^6.1.0",
|
||||
"csvtojson": "^2.0.10",
|
||||
"date-fns": "^2.30.0",
|
||||
"ramda": "^0.27.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-external-link": "^2.2.0",
|
||||
|
@ -46,7 +46,6 @@
|
|||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"@total-typescript/shoehorn": "^0.1.1",
|
||||
"@types/ramda": "^0.27.66",
|
||||
"@types/react": "^18.2.33",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@types/uuid": "^9.0.6",
|
||||
|
@ -2790,6 +2789,11 @@
|
|||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@shlinkio/data-manipulation": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@shlinkio/data-manipulation/-/data-manipulation-1.0.1.tgz",
|
||||
"integrity": "sha512-LYmRXcI2CPi8RZfI5wE7lf0Bg74NiPK+y2wuePvRlQtQud2jDZTbCmCIe6fyQV4NGrpKTn+D4NjcmVT/Iq2InQ=="
|
||||
},
|
||||
"node_modules/@shlinkio/eslint-config-js-coding-standard": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@shlinkio/eslint-config-js-coding-standard/-/eslint-config-js-coding-standard-2.3.0.tgz",
|
||||
|
@ -3272,15 +3276,6 @@
|
|||
"version": "15.7.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ramda": {
|
||||
"version": "0.27.66",
|
||||
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.66.tgz",
|
||||
"integrity": "sha512-i2YW+E2U6NfMt3dp0RxNcejox+bxJUNDjB7BpYuRuoHIzv5juPHkJkNgcUOu+YSQEmaWu8cnAo/8r63C0NnuVA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ts-toolbelt": "^6.15.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.2.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.33.tgz",
|
||||
|
@ -9622,11 +9617,6 @@
|
|||
"typescript": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-toolbelt": {
|
||||
"version": "6.15.5",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"dev": true,
|
||||
|
@ -12507,6 +12497,11 @@
|
|||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz",
|
||||
"integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw=="
|
||||
},
|
||||
"@shlinkio/data-manipulation": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@shlinkio/data-manipulation/-/data-manipulation-1.0.1.tgz",
|
||||
"integrity": "sha512-LYmRXcI2CPi8RZfI5wE7lf0Bg74NiPK+y2wuePvRlQtQud2jDZTbCmCIe6fyQV4NGrpKTn+D4NjcmVT/Iq2InQ=="
|
||||
},
|
||||
"@shlinkio/eslint-config-js-coding-standard": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@shlinkio/eslint-config-js-coding-standard/-/eslint-config-js-coding-standard-2.3.0.tgz",
|
||||
|
@ -12853,15 +12848,6 @@
|
|||
"@types/prop-types": {
|
||||
"version": "15.7.3"
|
||||
},
|
||||
"@types/ramda": {
|
||||
"version": "0.27.66",
|
||||
"resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.27.66.tgz",
|
||||
"integrity": "sha512-i2YW+E2U6NfMt3dp0RxNcejox+bxJUNDjB7BpYuRuoHIzv5juPHkJkNgcUOu+YSQEmaWu8cnAo/8r63C0NnuVA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ts-toolbelt": "^6.15.1"
|
||||
}
|
||||
},
|
||||
"@types/react": {
|
||||
"version": "18.2.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.33.tgz",
|
||||
|
@ -17115,10 +17101,6 @@
|
|||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"ts-toolbelt": {
|
||||
"version": "6.15.5",
|
||||
"dev": true
|
||||
},
|
||||
"tsconfig-paths": {
|
||||
"version": "3.14.1",
|
||||
"dev": true,
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@json2csv/plainjs": "^7.0.3",
|
||||
"@reduxjs/toolkit": "^1.9.7",
|
||||
"@shlinkio/data-manipulation": "^1.0.1",
|
||||
"@shlinkio/shlink-frontend-kit": "^0.4.0",
|
||||
"@shlinkio/shlink-js-sdk": "^0.1.0",
|
||||
"@shlinkio/shlink-web-component": "^0.3.5",
|
||||
|
@ -40,7 +41,6 @@
|
|||
"compare-versions": "^6.1.0",
|
||||
"csvtojson": "^2.0.10",
|
||||
"date-fns": "^2.30.0",
|
||||
"ramda": "^0.27.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-external-link": "^2.2.0",
|
||||
|
@ -62,7 +62,6 @@
|
|||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.5.1",
|
||||
"@total-typescript/shoehorn": "^0.1.1",
|
||||
"@types/ramda": "^0.27.66",
|
||||
"@types/react": "^18.2.33",
|
||||
"@types/react-dom": "^18.2.14",
|
||||
"@types/uuid": "^9.0.6",
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { faExternalLinkAlt, faPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { isEmpty, values } from 'ramda';
|
||||
import { useEffect } from 'react';
|
||||
import { ExternalLink } from 'react-external-link';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
|
@ -16,8 +15,8 @@ interface HomeProps {
|
|||
|
||||
export const Home = ({ servers }: HomeProps) => {
|
||||
const navigate = useNavigate();
|
||||
const serversList = values(servers);
|
||||
const hasServers = !isEmpty(serversList);
|
||||
const serversList = Object.values(servers);
|
||||
const hasServers = serversList.length > 0;
|
||||
|
||||
useEffect(() => {
|
||||
// Try to redirect to the first server marked as auto-connect
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import { pipe } from 'ramda';
|
||||
import { ExternalLink } from 'react-external-link';
|
||||
import type { SelectedServer } from '../servers/data';
|
||||
import { isReachableServer } from '../servers/data';
|
||||
import { versionToPrintable, versionToSemVer } from '../utils/helpers/version';
|
||||
|
||||
const SHLINK_WEB_CLIENT_VERSION = '%_VERSION_%';
|
||||
const normalizeVersion = pipe(versionToSemVer(), versionToPrintable);
|
||||
const normalizeVersion = (version: string) => versionToPrintable(versionToSemVer(version));
|
||||
|
||||
export interface ShlinkVersionsProps {
|
||||
selectedServer: SelectedServer;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type { IContainer } from 'bottlejs';
|
||||
import Bottle from 'bottlejs';
|
||||
import { pick } from 'ramda';
|
||||
import { connect as reduxConnect } from 'react-redux';
|
||||
import { provideServices as provideApiServices } from '../api/services/provideServices';
|
||||
import { provideServices as provideAppServices } from '../app/services/provideServices';
|
||||
|
@ -18,14 +17,20 @@ 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 pickProps = (propsToPick: string[]) => (obj: any) => Object.fromEntries(
|
||||
propsToPick.map((key) => [key, obj[key]]),
|
||||
);
|
||||
|
||||
const connect: ConnectDecorator = (propsFromState: string[] | null, actionServiceNames: string[] = []) =>
|
||||
reduxConnect(
|
||||
propsFromState ? pick(propsFromState) : null,
|
||||
propsFromState ? pickProps(propsFromState) : null,
|
||||
actionServiceNames.reduce(mapActionService, {}),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { faPlus as plusIcon, faServer as serverIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { isEmpty, values } from 'ramda';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from 'reactstrap';
|
||||
import type { SelectedServer, ServersMap } from './data';
|
||||
|
@ -12,10 +11,10 @@ export interface ServersDropdownProps {
|
|||
}
|
||||
|
||||
export const ServersDropdown = ({ servers, selectedServer }: ServersDropdownProps) => {
|
||||
const serversList = values(servers);
|
||||
const serversList = Object.values(servers);
|
||||
|
||||
const renderServers = () => {
|
||||
if (isEmpty(serversList)) {
|
||||
if (serversList.length === 0) {
|
||||
return (
|
||||
<DropdownItem tag={Link} to="/server/create">
|
||||
<FontAwesomeIcon icon={plusIcon} /> <span className="ms-1">Add a server</span>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { omit } from 'ramda';
|
||||
import type { SemVer } from '../../utils/helpers/version';
|
||||
|
||||
export interface ServerData {
|
||||
|
@ -45,5 +44,4 @@ export const isNotFoundServer = (server: SelectedServer): server is NotFoundServ
|
|||
|
||||
export const getServerId = (server: SelectedServer) => (isServerWithId(server) ? server.id : '');
|
||||
|
||||
export const serverWithIdToServerData = (server: ServerWithId): ServerData =>
|
||||
omit<ServerWithId, 'id' | 'autoConnect'>(['id', 'autoConnect'], server);
|
||||
export const serverWithIdToServerData = ({ id, autoConnect, ...server }: ServerWithId): ServerData => server;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useElementRef, useToggle } from '@shlinkio/shlink-frontend-kit';
|
||||
import { complement } from 'ramda';
|
||||
import type { ChangeEvent, PropsWithChildren } from 'react';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { Button, UncontrolledTooltip } from 'reactstrap';
|
||||
|
@ -27,8 +26,8 @@ type ImportServersBtnDeps = {
|
|||
ServersImporter: ServersImporter
|
||||
};
|
||||
|
||||
const serversFiltering = (servers: ServerData[]) =>
|
||||
({ url, apiKey }: ServerData) => servers.some((server) => server.url === url && server.apiKey === apiKey);
|
||||
const serversInclude = (servers: ServerData[], { url, apiKey }: ServerData) =>
|
||||
servers.some((server) => server.url === url && server.apiKey === apiKey);
|
||||
|
||||
const ImportServersBtn: FCWithDeps<ImportServersBtnConnectProps, ImportServersBtnDeps> = ({
|
||||
createServers,
|
||||
|
@ -56,7 +55,7 @@ const ImportServersBtn: FCWithDeps<ImportServersBtnConnectProps, ImportServersBt
|
|||
serversToCreate.current = newServers;
|
||||
|
||||
const existingServers = Object.values(servers);
|
||||
const dupServers = newServers.filter(serversFiltering(existingServers));
|
||||
const dupServers = newServers.filter((server) => serversInclude(existingServers, server));
|
||||
const hasDuplicatedServers = !!dupServers.length;
|
||||
|
||||
!hasDuplicatedServers ? create(newServers) : setDuplicatedServers(dupServers);
|
||||
|
@ -75,7 +74,7 @@ const ImportServersBtn: FCWithDeps<ImportServersBtnConnectProps, ImportServersBt
|
|||
hideModal();
|
||||
}, [create, hideModal, serversToCreate]);
|
||||
const createNonDuplicatedServers = useCallback(() => {
|
||||
create(serversToCreate.current.filter(complement(serversFiltering(duplicatedServers))));
|
||||
create(serversToCreate.current.filter((server) => !serversInclude(duplicatedServers, server)));
|
||||
hideModal();
|
||||
}, [create, duplicatedServers, hideModal]);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createAction, createSlice } from '@reduxjs/toolkit';
|
||||
import { memoizeWith } from '@shlinkio/data-manipulation';
|
||||
import type { ShlinkHealth } from '@shlinkio/shlink-web-component/api-contract';
|
||||
import { memoizeWith, pipe } from 'ramda';
|
||||
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||
import { createAsyncThunk } from '../../utils/helpers/redux';
|
||||
import { versionToPrintable, versionToSemVer as toSemVer } from '../../utils/helpers/version';
|
||||
|
@ -12,9 +12,9 @@ export const MIN_FALLBACK_VERSION = '1.0.0';
|
|||
export const MAX_FALLBACK_VERSION = '999.999.999';
|
||||
export const LATEST_VERSION_CONSTRAINT = 'latest';
|
||||
|
||||
const versionToSemVer = pipe(
|
||||
(version: string) => (version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version),
|
||||
toSemVer(MIN_FALLBACK_VERSION),
|
||||
const versionToSemVer = (version: string) => toSemVer(
|
||||
version === LATEST_VERSION_CONSTRAINT ? MAX_FALLBACK_VERSION : version,
|
||||
MIN_FALLBACK_VERSION,
|
||||
);
|
||||
|
||||
const getServerVersion = memoizeWith(
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { assoc, dissoc, fromPairs, map, pipe, reduce, toPairs } from 'ramda';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import type { ServerData, ServersMap, ServerWithId } from '../data';
|
||||
|
||||
|
@ -17,14 +16,17 @@ interface SetAutoConnect {
|
|||
const initialState: ServersMap = {};
|
||||
|
||||
const serverWithId = (server: ServerWithId | ServerData): ServerWithId => {
|
||||
if ((server as ServerWithId).id) {
|
||||
return server as ServerWithId;
|
||||
if ('id' in server) {
|
||||
return server;
|
||||
}
|
||||
|
||||
return assoc('id', uuid(), server);
|
||||
return { ...server, id: uuid() };
|
||||
};
|
||||
|
||||
const serversListToMap = reduce<ServerWithId, ServersMap>((acc, server) => assoc(server.id, server, acc), {});
|
||||
const serversListToMap = (servers: ServerWithId[]): ServersMap => servers.reduce<ServersMap>(
|
||||
(acc, server) => ({ ...acc, [server.id]: server }),
|
||||
{},
|
||||
);
|
||||
|
||||
export const { actions, reducer } = createSlice({
|
||||
name: 'shlink/servers',
|
||||
|
@ -37,11 +39,14 @@ export const { actions, reducer } = createSlice({
|
|||
reducer: (state, { payload }: PayloadAction<EditServer>) => {
|
||||
const { serverId, serverData } = payload;
|
||||
return (
|
||||
!state[serverId] ? state : assoc(serverId, { ...state[serverId], ...serverData }, state)
|
||||
!state[serverId] ? state : { ...state, [serverId]: { ...state[serverId], ...serverData } }
|
||||
);
|
||||
},
|
||||
},
|
||||
deleteServer: (state, { payload }) => dissoc(payload.id, state),
|
||||
deleteServer: (state, { payload }) => {
|
||||
const { [payload.id]: deletedServer, ...rest } = state;
|
||||
return rest;
|
||||
},
|
||||
setAutoConnect: {
|
||||
prepare: ({ id: serverId }: ServerWithId, autoConnect: boolean) => ({
|
||||
payload: { serverId, autoConnect },
|
||||
|
@ -53,11 +58,11 @@ export const { actions, reducer } = createSlice({
|
|||
}
|
||||
|
||||
if (!autoConnect) {
|
||||
return assoc(serverId, { ...state[serverId], autoConnect }, state);
|
||||
return { ...state, [serverId]: { ...state[serverId], autoConnect } };
|
||||
}
|
||||
|
||||
return fromPairs(
|
||||
toPairs(state).map(([evaluatedServerId, server]) => [
|
||||
return Object.fromEntries(
|
||||
Object.entries(state).map(([evaluatedServerId, server]) => [
|
||||
evaluatedServerId,
|
||||
{ ...server, autoConnect: evaluatedServerId === serverId },
|
||||
]),
|
||||
|
@ -65,11 +70,10 @@ export const { actions, reducer } = createSlice({
|
|||
},
|
||||
},
|
||||
createServers: {
|
||||
prepare: pipe(
|
||||
map(serverWithId),
|
||||
serversListToMap,
|
||||
(payload: ServersMap) => ({ payload }),
|
||||
),
|
||||
prepare: (servers: ServerData[]) => {
|
||||
const payload = serversListToMap(servers.map(serverWithId));
|
||||
return { payload };
|
||||
},
|
||||
reducer: (state, { payload: newServers }: PayloadAction<ServersMap>) => ({ ...state, ...newServers }),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { values } from 'ramda';
|
||||
import type { JsonToCsv } from '../../utils/helpers/csvjson';
|
||||
import { saveCsv } from '../../utils/helpers/files';
|
||||
import type { LocalStorage } from '../../utils/services/LocalStorage';
|
||||
|
@ -15,7 +14,7 @@ export class ServersExporter {
|
|||
) {}
|
||||
|
||||
public readonly exportServers = async () => {
|
||||
const servers = values(this.storage.get<ServersMap>('servers') ?? {}).map(serverWithIdToServerData);
|
||||
const servers = Object.values(this.storage.get<ServersMap>('servers') ?? {}).map(serverWithIdToServerData);
|
||||
|
||||
try {
|
||||
const csv = this.jsonToCsv(servers);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type Bottle from 'bottlejs';
|
||||
import { prop } from 'ramda';
|
||||
import type { ConnectDecorator } from '../../container/types';
|
||||
import { CreateServerFactory } from '../CreateServer';
|
||||
import { DeleteServerButtonFactory } from '../DeleteServerButton';
|
||||
|
@ -70,5 +69,5 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
|
||||
// Reducers
|
||||
bottle.serviceFactory('selectedServerReducerCreator', selectedServerReducerCreator, 'selectServer');
|
||||
bottle.serviceFactory('selectedServerReducer', prop('reducer'), 'selectedServerReducerCreator');
|
||||
bottle.serviceFactory('selectedServerReducer', (obj) => obj.reducer, 'selectedServerReducerCreator');
|
||||
};
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { PayloadAction, PrepareAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { mergeDeepRight } from '@shlinkio/data-manipulation';
|
||||
import type { Theme } from '@shlinkio/shlink-frontend-kit';
|
||||
import type {
|
||||
Settings,
|
||||
|
@ -8,7 +9,6 @@ import type {
|
|||
TagsSettings,
|
||||
VisitsSettings,
|
||||
} from '@shlinkio/shlink-web-component';
|
||||
import { mergeDeepRight } from 'ramda';
|
||||
import type { Defined } from '../../utils/types';
|
||||
|
||||
type ShortUrlsOrder = Defined<ShortUrlsListSettings['defaultOrdering']>;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type { AsyncThunkPayloadCreator } from '@reduxjs/toolkit';
|
||||
import { createAsyncThunk as baseCreateAsyncThunk } from '@reduxjs/toolkit';
|
||||
import { identity } from 'ramda';
|
||||
import type { ShlinkState } from '../../container/types';
|
||||
|
||||
export const createAsyncThunk = <Returned, ThunkArg>(
|
||||
|
@ -9,5 +8,5 @@ export const createAsyncThunk = <Returned, ThunkArg>(
|
|||
) => baseCreateAsyncThunk(
|
||||
typePrefix,
|
||||
payloadCreator,
|
||||
{ serializeError: identity },
|
||||
{ serializeError: (e) => e },
|
||||
);
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import { memoizeWith } from '@shlinkio/data-manipulation';
|
||||
import { compare } from 'compare-versions';
|
||||
import { identity, isEmpty, isNil, memoizeWith } from 'ramda';
|
||||
|
||||
export type Empty = null | undefined | '' | never[];
|
||||
|
||||
const hasValue = <T>(value: T | Empty): value is T => !isNil(value) && !isEmpty(value);
|
||||
const isEmpty = (value: Exclude<any, undefined | null>): boolean => (
|
||||
(Array.isArray(value) && value.length === 0)
|
||||
|| (typeof value === 'string' && value === '')
|
||||
|| (typeof value === 'object' && Object.keys(value).length === 0)
|
||||
);
|
||||
|
||||
export const hasValue = <T>(value: T | Empty): value is T => value !== undefined && value !== null && !isEmpty(value);
|
||||
|
||||
type SemVerPatternFragment = `${bigint | '*'}`;
|
||||
|
||||
|
@ -29,7 +35,7 @@ export const versionMatch = (versionToMatch: SemVer | Empty, { maxVersion, minVe
|
|||
return matchesMaxVersion && matchesMinVersion;
|
||||
};
|
||||
|
||||
const versionIsValidSemVer = memoizeWith(identity, (version: string): version is SemVer => {
|
||||
const versionIsValidSemVer = memoizeWith((v) => v, (version: string): version is SemVer => {
|
||||
try {
|
||||
return compare(version, version, '=');
|
||||
} catch (e) {
|
||||
|
@ -39,5 +45,6 @@ const versionIsValidSemVer = memoizeWith(identity, (version: string): version is
|
|||
|
||||
export const versionToPrintable = (version: string) => (!versionIsValidSemVer(version) ? version : `v${version}`);
|
||||
|
||||
export const versionToSemVer = (defaultValue: SemVer = 'latest') =>
|
||||
(version: string): SemVer => (versionIsValidSemVer(version) ? version : defaultValue);
|
||||
export const versionToSemVer = (version: string, fallback: SemVer = 'latest'): SemVer => (
|
||||
versionIsValidSemVer(version) ? version : fallback
|
||||
);
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import { pipe, range } from 'ramda';
|
||||
import type { SyntheticEvent } from 'react';
|
||||
|
||||
type Optional<T> = T | null | undefined;
|
||||
|
||||
export type OptionalString = Optional<string>;
|
||||
|
||||
export const handleEventPreventingDefault = <T>(handler: () => T) => pipe(
|
||||
(e: SyntheticEvent) => e.preventDefault(),
|
||||
handler,
|
||||
);
|
||||
|
||||
export const rangeOf = <T>(size: number, mappingFn: (value: number) => T, startAt = 1): T[] =>
|
||||
range(startAt, size + 1).map(mappingFn);
|
||||
export const handleEventPreventingDefault = <T>(handler: () => T) => (e: SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
handler();
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { screen } from '@testing-library/react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { values } from 'ramda';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import type { ServersMap } from '../../src/servers/data';
|
||||
import { ServersDropdown } from '../../src/servers/ServersDropdown';
|
||||
|
@ -28,7 +27,7 @@ describe('<ServersDropdown />', () => {
|
|||
|
||||
await user.click(screen.getByText('Servers'));
|
||||
const items = screen.getAllByRole('menuitem');
|
||||
expect(items).toHaveLength(values(fallbackServers).length + 1);
|
||||
expect(items).toHaveLength(Object.values(fallbackServers).length + 1);
|
||||
expect(items[0]).toHaveTextContent('foo');
|
||||
expect(items[1]).toHaveTextContent('bar');
|
||||
expect(items[2]).toHaveTextContent('baz');
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { dissoc, values } from 'ramda';
|
||||
import type { RegularServer, ServersMap, ServerWithId } from '../../../src/servers/data';
|
||||
import {
|
||||
createServers,
|
||||
|
@ -101,17 +100,17 @@ describe('serversReducer', () => {
|
|||
|
||||
describe('createServers', () => {
|
||||
it('returns expected action', () => {
|
||||
const newServers = values(list);
|
||||
const newServers = Object.values(list);
|
||||
const { payload } = createServers(newServers);
|
||||
|
||||
expect(payload).toEqual(list);
|
||||
});
|
||||
|
||||
it('generates an id for every provided server if they do not have it', () => {
|
||||
const servers = values(list).map(dissoc('id'));
|
||||
const servers = Object.values(list).map(({ id, ...rest }) => rest);
|
||||
const { payload } = createServers(servers);
|
||||
|
||||
expect(values(payload).every(({ id }) => !!id)).toEqual(true);
|
||||
expect(Object.values(payload).every(({ id }) => !!id)).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue