Merge pull request #7148 from tchak/refactor-champs-ts
refactor(champs): refactor champs components to use typescript
This commit is contained in:
commit
9b4bc7518a
15 changed files with 119 additions and 111 deletions
|
@ -1,35 +0,0 @@
|
||||||
import Chartkick from 'chartkick';
|
|
||||||
import Highcharts from 'highcharts';
|
|
||||||
import { toggle, delegate } from '@utils';
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleChart(event) {
|
|
||||||
const nextSelectorItem = event.target,
|
|
||||||
chartClass = event.target.dataset.toggleChart,
|
|
||||||
nextChart = document.querySelector(chartClass),
|
|
||||||
nextChartId = nextChart.children[0].id,
|
|
||||||
currentSelectorItem = nextSelectorItem.parentElement.querySelector(
|
|
||||||
'.segmented-control-item-active'
|
|
||||||
),
|
|
||||||
currentChart = nextSelectorItem.parentElement.parentElement.querySelector(
|
|
||||||
'.chart:not(.hidden)'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Change the current selector and the next selector states
|
|
||||||
currentSelectorItem.classList.toggle('segmented-control-item-active');
|
|
||||||
nextSelectorItem.classList.toggle('segmented-control-item-active');
|
|
||||||
|
|
||||||
// Hide the currently shown chart and show the new one
|
|
||||||
toggle(currentChart);
|
|
||||||
toggle(nextChart);
|
|
||||||
|
|
||||||
// Reflow needed, see https://github.com/highcharts/highcharts/issues/1979
|
|
||||||
Chartkick.charts[nextChartId].getChartObject().reflow();
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate('click', '[data-toggle-chart]', toggleChart);
|
|
||||||
|
|
||||||
Chartkick.use(Highcharts);
|
|
38
app/javascript/components/Chartkick.tsx
Normal file
38
app/javascript/components/Chartkick.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import Chartkick from 'chartkick';
|
||||||
|
import Highcharts from 'highcharts';
|
||||||
|
import { toggle, delegate } from '@utils';
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleChart(event: MouseEvent) {
|
||||||
|
const nextSelectorItem = event.target as HTMLButtonElement,
|
||||||
|
chartClass = nextSelectorItem.dataset.toggleChart,
|
||||||
|
nextChart = chartClass
|
||||||
|
? document.querySelector<HTMLDivElement>(chartClass)
|
||||||
|
: undefined,
|
||||||
|
nextChartId = nextChart?.children[0]?.id,
|
||||||
|
currentSelectorItem = nextSelectorItem.parentElement?.querySelector(
|
||||||
|
'.segmented-control-item-active'
|
||||||
|
),
|
||||||
|
currentChart =
|
||||||
|
nextSelectorItem.parentElement?.parentElement?.querySelector<HTMLDivElement>(
|
||||||
|
'.chart:not(.hidden)'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Change the current selector and the next selector states
|
||||||
|
currentSelectorItem?.classList.toggle('segmented-control-item-active');
|
||||||
|
nextSelectorItem.classList.toggle('segmented-control-item-active');
|
||||||
|
|
||||||
|
// Hide the currently shown chart and show the new one
|
||||||
|
currentChart && toggle(currentChart);
|
||||||
|
nextChart && toggle(nextChart);
|
||||||
|
|
||||||
|
// Reflow needed, see https://github.com/highcharts/highcharts/issues/1979
|
||||||
|
nextChartId && Chartkick.charts[nextChartId]?.getChartObject()?.reflow();
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate('click', '[data-toggle-chart]', toggleChart);
|
||||||
|
|
||||||
|
Chartkick.use(Highcharts);
|
|
@ -1,16 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { QueryClientProvider } from 'react-query';
|
import { QueryClientProvider } from 'react-query';
|
||||||
|
|
||||||
import ComboSearch from './ComboSearch';
|
import ComboSearch, { ComboSearchProps } from './ComboSearch';
|
||||||
import { queryClient } from './shared/queryClient';
|
import { queryClient } from './shared/queryClient';
|
||||||
|
|
||||||
function ComboAnnuaireEducationSearch(props) {
|
type AnnuaireEducationResult = {
|
||||||
|
fields: {
|
||||||
|
identifiant_de_l_etablissement: string;
|
||||||
|
nom_etablissement: string;
|
||||||
|
nom_commune: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function transformResults(_: unknown, result: unknown) {
|
||||||
|
const results = result as { records: AnnuaireEducationResult[] };
|
||||||
|
return results.records as AnnuaireEducationResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ComboAnnuaireEducationSearch(
|
||||||
|
props: ComboSearchProps<AnnuaireEducationResult>
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ComboSearch
|
<ComboSearch
|
||||||
|
{...props}
|
||||||
scope="annuaire-education"
|
scope="annuaire-education"
|
||||||
minimumInputLength={3}
|
minimumInputLength={3}
|
||||||
transformResults={(_, { records }) => records}
|
transformResults={transformResults}
|
||||||
transformResult={({
|
transformResult={({
|
||||||
fields: {
|
fields: {
|
||||||
identifiant_de_l_etablissement: id,
|
identifiant_de_l_etablissement: id,
|
||||||
|
@ -18,10 +34,7 @@ function ComboAnnuaireEducationSearch(props) {
|
||||||
nom_commune
|
nom_commune
|
||||||
}
|
}
|
||||||
}) => [id, `${nom_etablissement}, ${nom_commune} (${id})`]}
|
}) => [id, `${nom_etablissement}, ${nom_commune} (${id})`]}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ComboAnnuaireEducationSearch;
|
|
|
@ -1,19 +1,21 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { QueryClientProvider } from 'react-query';
|
import { QueryClientProvider } from 'react-query';
|
||||||
import { matchSorter } from 'match-sorter';
|
import { matchSorter } from 'match-sorter';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import ComboSearch from './ComboSearch';
|
import ComboSearch, { ComboSearchProps } from './ComboSearch';
|
||||||
import { queryClient } from './shared/queryClient';
|
import { queryClient } from './shared/queryClient';
|
||||||
import { ComboDepartementsSearch } from './ComboDepartementsSearch';
|
import { ComboDepartementsSearch } from './ComboDepartementsSearch';
|
||||||
import { useHiddenField, groupId } from './shared/hooks';
|
import { useHiddenField, groupId } from './shared/hooks';
|
||||||
|
|
||||||
|
type CommuneResult = { code: string; nom: string; codesPostaux: string[] };
|
||||||
|
|
||||||
// Avoid hiding similar matches for precise queries (like "Sainte Marie")
|
// Avoid hiding similar matches for precise queries (like "Sainte Marie")
|
||||||
function searchResultsLimit(term) {
|
function searchResultsLimit(term: string) {
|
||||||
return term.length > 5 ? 10 : 5;
|
return term.length > 5 ? 10 : 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
function expandResultsWithMultiplePostalCodes(term, results) {
|
function expandResultsWithMultiplePostalCodes(term: string, result: unknown) {
|
||||||
|
const results = result as CommuneResult[];
|
||||||
// A single result may have several associated postal codes.
|
// A single result may have several associated postal codes.
|
||||||
// To make the search results more precise, we want to generate
|
// To make the search results more precise, we want to generate
|
||||||
// an actual result for each postal code.
|
// an actual result for each postal code.
|
||||||
|
@ -44,13 +46,16 @@ const placeholderDepartements = [
|
||||||
['77 – Seine-et-Marne', 'Melun'],
|
['77 – Seine-et-Marne', 'Melun'],
|
||||||
['22 – Côtes d’Armor', 'Saint-Brieuc'],
|
['22 – Côtes d’Armor', 'Saint-Brieuc'],
|
||||||
['47 – Lot-et-Garonne', 'Agen']
|
['47 – Lot-et-Garonne', 'Agen']
|
||||||
];
|
] as const;
|
||||||
const [placeholderDepartement, placeholderCommune] =
|
const [placeholderDepartement, placeholderCommune] =
|
||||||
placeholderDepartements[
|
placeholderDepartements[
|
||||||
Math.floor(Math.random() * (placeholderDepartements.length - 1))
|
Math.floor(Math.random() * (placeholderDepartements.length - 1))
|
||||||
];
|
];
|
||||||
|
|
||||||
function ComboCommunesSearch({ id, ...props }) {
|
export default function ComboCommunesSearch({
|
||||||
|
id,
|
||||||
|
...props
|
||||||
|
}: ComboSearchProps<CommuneResult> & { id: string }) {
|
||||||
const group = groupId(id);
|
const group = groupId(id);
|
||||||
const [departementValue, setDepartementValue] = useHiddenField(
|
const [departementValue, setDepartementValue] = useHiddenField(
|
||||||
group,
|
group,
|
||||||
|
@ -74,14 +79,14 @@ function ComboCommunesSearch({ id, ...props }) {
|
||||||
</div>
|
</div>
|
||||||
<ComboDepartementsSearch
|
<ComboDepartementsSearch
|
||||||
{...props}
|
{...props}
|
||||||
id={!codeDepartement ? id : null}
|
id={!codeDepartement ? id : undefined}
|
||||||
describedby={departementDescribedBy}
|
describedby={departementDescribedBy}
|
||||||
placeholder={placeholderDepartement}
|
placeholder={placeholderDepartement}
|
||||||
addForeignDepartement={false}
|
addForeignDepartement={false}
|
||||||
value={departementValue}
|
value={departementValue}
|
||||||
onChange={(_, result) => {
|
onChange={(_, result) => {
|
||||||
setDepartementValue(result?.nom);
|
setDepartementValue(result?.nom ?? '');
|
||||||
setCodeDepartement(result?.code);
|
setCodeDepartement(result?.code ?? '');
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -112,9 +117,3 @@ function ComboCommunesSearch({ id, ...props }) {
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboCommunesSearch.propTypes = {
|
|
||||||
id: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ComboCommunesSearch;
|
|
|
@ -1,14 +1,16 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { QueryClientProvider } from 'react-query';
|
import { QueryClientProvider } from 'react-query';
|
||||||
import { matchSorter } from 'match-sorter';
|
import { matchSorter } from 'match-sorter';
|
||||||
|
|
||||||
import ComboSearch from './ComboSearch';
|
import ComboSearch, { ComboSearchProps } from './ComboSearch';
|
||||||
import { queryClient } from './shared/queryClient';
|
import { queryClient } from './shared/queryClient';
|
||||||
|
|
||||||
|
type DepartementResult = { code: string; nom: string };
|
||||||
|
|
||||||
const extraTerms = [{ code: '99', nom: 'Etranger' }];
|
const extraTerms = [{ code: '99', nom: 'Etranger' }];
|
||||||
|
|
||||||
function expandResultsWithForeignDepartement(term, results) {
|
function expandResultsWithForeignDepartement(term: string, result: unknown) {
|
||||||
|
const results = result as DepartementResult[];
|
||||||
return [
|
return [
|
||||||
...results,
|
...results,
|
||||||
...matchSorter(extraTerms, term, {
|
...matchSorter(extraTerms, term, {
|
||||||
|
@ -17,10 +19,17 @@ function expandResultsWithForeignDepartement(term, results) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ComboDepartementsSearchProps = Omit<
|
||||||
|
ComboSearchProps<DepartementResult> & {
|
||||||
|
addForeignDepartement: boolean;
|
||||||
|
},
|
||||||
|
'transformResult' | 'transformResults'
|
||||||
|
>;
|
||||||
|
|
||||||
export function ComboDepartementsSearch({
|
export function ComboDepartementsSearch({
|
||||||
addForeignDepartement = true,
|
addForeignDepartement = true,
|
||||||
...props
|
...props
|
||||||
}) {
|
}: ComboDepartementsSearchProps) {
|
||||||
return (
|
return (
|
||||||
<ComboSearch
|
<ComboSearch
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -34,17 +43,12 @@ export function ComboDepartementsSearch({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ComboDepartementsSearchDefault(params) {
|
export default function ComboDepartementsSearchDefault(
|
||||||
|
params: ComboDepartementsSearchProps
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ComboDepartementsSearch {...params} />
|
<ComboDepartementsSearch {...params} />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboDepartementsSearch.propTypes = {
|
|
||||||
...ComboSearch.propTypes,
|
|
||||||
addForeignDepartement: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ComboDepartementsSearchDefault;
|
|
|
@ -1,15 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
import { groupId } from './shared/hooks';
|
|
||||||
import ComboMultiple from './ComboMultiple';
|
|
||||||
|
|
||||||
function ComboMultipleDropdownList({ id, ...props }) {
|
|
||||||
return <ComboMultiple group={groupId(id)} id={id} {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComboMultipleDropdownList.propTypes = {
|
|
||||||
id: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ComboMultipleDropdownList;
|
|
11
app/javascript/components/ComboMultipleDropdownList.tsx
Normal file
11
app/javascript/components/ComboMultipleDropdownList.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { groupId } from './shared/hooks';
|
||||||
|
import ComboMultiple, { ComboMultipleProps } from './ComboMultiple';
|
||||||
|
|
||||||
|
export default function ComboMultipleDropdownList({
|
||||||
|
id,
|
||||||
|
...props
|
||||||
|
}: ComboMultipleProps & { id: string }) {
|
||||||
|
return <ComboMultiple id={id} {...props} group={groupId(id)} />;
|
||||||
|
}
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { QueryClientProvider } from 'react-query';
|
import { QueryClientProvider } from 'react-query';
|
||||||
|
|
||||||
import ComboSearch from './ComboSearch';
|
import ComboSearch, { ComboSearchProps } from './ComboSearch';
|
||||||
import { queryClient } from './shared/queryClient';
|
import { queryClient } from './shared/queryClient';
|
||||||
|
|
||||||
function ComboPaysSearch(props) {
|
export default function ComboPaysSearch(
|
||||||
|
props: ComboSearchProps<{ code: string; value: string; label: string }>
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ComboSearch
|
<ComboSearch
|
||||||
|
{...props}
|
||||||
scope="pays"
|
scope="pays"
|
||||||
minimumInputLength={0}
|
minimumInputLength={0}
|
||||||
transformResult={({ code, value, label }) => [code, value, label]}
|
transformResult={({ code, value, label }) => [code, value, label]}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ComboPaysSearch;
|
|
|
@ -1,20 +1,20 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { QueryClientProvider } from 'react-query';
|
import { QueryClientProvider } from 'react-query';
|
||||||
|
|
||||||
import ComboSearch from './ComboSearch';
|
import ComboSearch, { ComboSearchProps } from './ComboSearch';
|
||||||
import { queryClient } from './shared/queryClient';
|
import { queryClient } from './shared/queryClient';
|
||||||
|
|
||||||
function ComboRegionsSearch(props) {
|
export default function ComboRegionsSearch(
|
||||||
|
props: ComboSearchProps<{ code: string; nom: string }>
|
||||||
|
) {
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<ComboSearch
|
<ComboSearch
|
||||||
|
{...props}
|
||||||
scope="regions"
|
scope="regions"
|
||||||
minimumInputLength={0}
|
minimumInputLength={0}
|
||||||
transformResult={({ code, nom }) => [code, nom]}
|
transformResult={({ code, nom }) => [code, nom]}
|
||||||
{...props}
|
|
||||||
/>
|
/>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ComboRegionsSearch;
|
|
|
@ -20,7 +20,7 @@ import { useDeferredSubmit, useHiddenField, groupId } from './shared/hooks';
|
||||||
type TransformResults<Result> = (term: string, results: unknown) => Result[];
|
type TransformResults<Result> = (term: string, results: unknown) => Result[];
|
||||||
type TransformResult<Result> = (
|
type TransformResult<Result> = (
|
||||||
result: Result
|
result: Result
|
||||||
) => [key: string, value: string, label: string];
|
) => [key: string, value: string, label?: string];
|
||||||
|
|
||||||
export type ComboSearchProps<Result> = {
|
export type ComboSearchProps<Result> = {
|
||||||
onChange?: (value: string | null, result?: Result) => void;
|
onChange?: (value: string | null, result?: Result) => void;
|
||||||
|
@ -28,7 +28,7 @@ export type ComboSearchProps<Result> = {
|
||||||
scope: string;
|
scope: string;
|
||||||
scopeExtra?: string;
|
scopeExtra?: string;
|
||||||
minimumInputLength: number;
|
minimumInputLength: number;
|
||||||
transformResults: TransformResults<Result>;
|
transformResults?: TransformResults<Result>;
|
||||||
transformResult: TransformResult<Result>;
|
transformResult: TransformResult<Result>;
|
||||||
allowInputValues?: boolean;
|
allowInputValues?: boolean;
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
|
@ -42,15 +42,20 @@ export function removeClass(el: HTMLElement, cssClass: string) {
|
||||||
el && el.classList.remove(cssClass);
|
el && el.classList.remove(cssClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function delegate(
|
export function delegate<E extends Event = Event>(
|
||||||
eventNames: string,
|
eventNames: string,
|
||||||
selector: string,
|
selector: string,
|
||||||
callback: () => void
|
callback: (event: E) => void
|
||||||
) {
|
) {
|
||||||
eventNames
|
eventNames
|
||||||
.split(' ')
|
.split(' ')
|
||||||
.forEach((eventName) =>
|
.forEach((eventName) =>
|
||||||
Rails.delegate(document, selector, eventName, callback)
|
Rails.delegate(
|
||||||
|
document,
|
||||||
|
selector,
|
||||||
|
eventName,
|
||||||
|
callback as (event: Event) => void
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
1
app/javascript/types.d.ts
vendored
1
app/javascript/types.d.ts
vendored
|
@ -21,3 +21,4 @@ declare module '@tmcw/togeojson/dist/togeojson.es.js' {
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module 'react-coordinate-input';
|
declare module 'react-coordinate-input';
|
||||||
|
declare module 'chartkick';
|
||||||
|
|
|
@ -86,12 +86,6 @@ module.exports = function (api) {
|
||||||
{
|
{
|
||||||
async: false
|
async: false
|
||||||
}
|
}
|
||||||
],
|
|
||||||
isProductionEnv && [
|
|
||||||
'babel-plugin-transform-react-remove-prop-types',
|
|
||||||
{
|
|
||||||
removeImport: true
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
].filter(Boolean)
|
].filter(Boolean)
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
"@sentry/browser": "6.12.0",
|
"@sentry/browser": "6.12.0",
|
||||||
"@tmcw/togeojson": "^4.3.0",
|
"@tmcw/togeojson": "^4.3.0",
|
||||||
"babel-plugin-macros": "^2.8.0",
|
"babel-plugin-macros": "^2.8.0",
|
||||||
"babel-plugin-transform-react-remove-prop-types": "^0.4.24",
|
|
||||||
"chartkick": "^4.1.1",
|
"chartkick": "^4.1.1",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"debounce": "^1.2.1",
|
"debounce": "^1.2.1",
|
||||||
|
@ -30,7 +29,6 @@
|
||||||
"is-hotkey": "^0.2.0",
|
"is-hotkey": "^0.2.0",
|
||||||
"maplibre-gl": "^1.15.2",
|
"maplibre-gl": "^1.15.2",
|
||||||
"match-sorter": "^6.2.0",
|
"match-sorter": "^6.2.0",
|
||||||
"prop-types": "^15.7.2",
|
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-coordinate-input": "^1.0.0",
|
"react-coordinate-input": "^1.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
|
|
|
@ -3417,11 +3417,6 @@ babel-plugin-polyfill-regenerator@^0.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-define-polyfill-provider" "^0.3.0"
|
"@babel/helper-define-polyfill-provider" "^0.3.0"
|
||||||
|
|
||||||
babel-plugin-transform-react-remove-prop-types@^0.4.24:
|
|
||||||
version "0.4.24"
|
|
||||||
resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz#f2edaf9b4c6a5fbe5c1d678bfb531078c1555f3a"
|
|
||||||
integrity sha512-eqj0hVcJUR57/Ug2zE1Yswsw4LhuqqHhD+8v120T1cl3kjg76QwtyBrdIk4WVwK+lAhBJVYCd/v+4nc4y+8JsA==
|
|
||||||
|
|
||||||
backoff@^2.5.0:
|
backoff@^2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f"
|
resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue