Ott 102 search (#40)
parent
5bfa0d7b7e
commit
c23e650512
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,5 @@ |
||||
export const SPORT_COLORS = { |
||||
basketball: '#f1903b', |
||||
football: '#00a435', |
||||
hockey: '#5eb1ff', |
||||
} |
||||
@ -1,4 +1,10 @@ |
||||
export const authenticatedLexics = { |
||||
basketball: 6960, |
||||
football: 6958, |
||||
hockey: 6959, |
||||
logout: 4306, |
||||
player: 630, |
||||
team: 658, |
||||
tournament: 1009, |
||||
user_account: 12928, |
||||
} |
||||
|
||||
@ -1,2 +1,22 @@ |
||||
export const API_ROOT = 'http://api-staging.instat.tv' |
||||
export const DATA_URL = `${API_ROOT}/data` |
||||
|
||||
export const LOGOS_FALLBACKS = { |
||||
basketball: { |
||||
players: 'https://basketball.instatscout.com/images/player-no-photo.png', |
||||
teams: 'https://basketball.instatscout.com/images/team-no-photo.png', |
||||
tournaments: 'https://basketball.instatscout.com/images/tournaments/180/no-photo.png', |
||||
}, |
||||
|
||||
football: { |
||||
players: 'https://football.instatscout.com/images/player-no-photo.png', |
||||
teams: 'https://football.instatscout.com/images/team-no-photo.png', |
||||
tournaments: 'https://football.instatscout.com/images/tournament-no-photo.png', |
||||
}, |
||||
|
||||
hockey: { |
||||
players: 'https://hockey.instatscout.com/images/player-no-photo.png', |
||||
teams: 'https://hockey.instatscout.com/images/team-no-photo.png', |
||||
tournaments: 'https://hockey.instatscout.com/images/tournaments/180/no-photo.png', |
||||
}, |
||||
} |
||||
|
||||
@ -0,0 +1,22 @@ |
||||
import { css } from 'styled-components/macro' |
||||
|
||||
export const сustomScrollbar = css` |
||||
::-webkit-scrollbar { |
||||
width: 8px; |
||||
height: 8px; |
||||
} |
||||
|
||||
::-webkit-scrollbar-thumb { |
||||
border-radius: 6px; |
||||
background: #3F3F3F; |
||||
} |
||||
|
||||
::-webkit-scrollbar-button { |
||||
display: none; |
||||
} |
||||
|
||||
::-webkit-scrollbar-track, |
||||
::-webkit-scrollbar-corner { |
||||
background: transparent; |
||||
} |
||||
` |
||||
@ -0,0 +1,62 @@ |
||||
import { |
||||
SyntheticEvent, |
||||
useEffect, |
||||
useRef, |
||||
useCallback, |
||||
} from 'react' |
||||
|
||||
import forEach from 'lodash/forEach' |
||||
|
||||
export const useItemsList = () => { |
||||
const ref = useRef<HTMLUListElement>(null) |
||||
|
||||
const onError = useCallback((fallbackImage: string) => (e: SyntheticEvent<HTMLImageElement>) => { |
||||
// eslint-disable-next-line no-param-reassign
|
||||
e.currentTarget.src = fallbackImage |
||||
}, []) |
||||
|
||||
useEffect(() => { |
||||
if (ref.current) { |
||||
/** |
||||
* Реализуем ленивую загрузку изображений |
||||
* для оптимизации запросов к серверу |
||||
*/ |
||||
|
||||
// устанавливаем настройки
|
||||
const options = { |
||||
// область просмотра
|
||||
root: ref.current.parentElement, |
||||
// процент пересечения с областью просмотра - половина изображения
|
||||
threshold: 0.5, |
||||
} |
||||
|
||||
// создаем наблюдатель
|
||||
const intersectionObserver = new IntersectionObserver((entries, observer) => { |
||||
// для каждой записи-целевого элемента
|
||||
forEach(entries, (entry) => { |
||||
// если элемент является наблюдаемым
|
||||
if (entry.isIntersecting) { |
||||
const imgElem = entry.target as HTMLImageElement |
||||
// устанавливаем аттрибут src из data-src
|
||||
imgElem.src = imgElem.dataset.src! |
||||
|
||||
// прекращаем наблюдение
|
||||
observer.unobserve(imgElem) |
||||
} |
||||
}) |
||||
}, options) |
||||
|
||||
const imgElems = ref.current.getElementsByTagName('img') |
||||
|
||||
// находим все элементы img и делаем наблюдаемыми
|
||||
forEach(imgElems, (imgElem) => { |
||||
intersectionObserver.observe(imgElem) |
||||
}) |
||||
} |
||||
}, []) |
||||
|
||||
return { |
||||
onError, |
||||
ref, |
||||
} |
||||
} |
||||
@ -0,0 +1,72 @@ |
||||
import React from 'react' |
||||
|
||||
import map from 'lodash/map' |
||||
|
||||
import { SPORT_COLORS } from 'config' |
||||
import type { SportType } from 'features/Search/config' |
||||
|
||||
import { useItemsList } from './hooks' |
||||
import { |
||||
Logo, |
||||
LogoWrapper, |
||||
Item, |
||||
ItemInfo, |
||||
Name, |
||||
SportName, |
||||
StyledLink, |
||||
TeamOrCountry, |
||||
Wrapper, |
||||
} from './styled' |
||||
|
||||
type SearchItemsListProps = { |
||||
list: Array<{ |
||||
fallbackImage: string, |
||||
id: number, |
||||
logo: string, |
||||
name: string, |
||||
profileUrl: string, |
||||
sportType: SportType, |
||||
teamOrCountry?: string, |
||||
}>, |
||||
} |
||||
|
||||
export const ItemsList = ({ list }: SearchItemsListProps) => { |
||||
const { |
||||
onError, |
||||
ref, |
||||
} = useItemsList() |
||||
|
||||
return ( |
||||
<Wrapper ref={ref}> |
||||
{map(list, ({ |
||||
fallbackImage, |
||||
id, |
||||
logo, |
||||
name, |
||||
profileUrl, |
||||
sportType, |
||||
teamOrCountry, |
||||
}) => ( |
||||
<Item key={id}> |
||||
<StyledLink to={profileUrl} target='_blank'> |
||||
<LogoWrapper> |
||||
<Logo |
||||
data-src={logo} |
||||
onError={onError(fallbackImage)} |
||||
alt={name} |
||||
/> |
||||
</LogoWrapper> |
||||
<ItemInfo> |
||||
<Name>{name}</Name> |
||||
<SportName |
||||
color={SPORT_COLORS[sportType]} |
||||
t={sportType} |
||||
/> |
||||
<TeamOrCountry>{teamOrCountry}</TeamOrCountry> |
||||
</ItemInfo> |
||||
</StyledLink> |
||||
</Item> |
||||
))} |
||||
</Wrapper> |
||||
) |
||||
} |
||||
@ -0,0 +1,67 @@ |
||||
import { Link } from 'react-router-dom' |
||||
|
||||
import styled from 'styled-components/macro' |
||||
|
||||
import { T9n } from 'features/T9n' |
||||
|
||||
export const Wrapper = styled.ul` |
||||
margin: 0; |
||||
padding: 0; |
||||
list-style-type: none; |
||||
` |
||||
|
||||
export const Item = styled.li` |
||||
:focus { |
||||
background-color: #999; |
||||
} |
||||
` |
||||
|
||||
export const StyledLink = styled(Link)` |
||||
display: flex; |
||||
align-items: center; |
||||
width: 100%; |
||||
height: 56px; |
||||
background-color: #666; |
||||
|
||||
:focus-within,
|
||||
:hover { |
||||
background-color: #999; |
||||
outline: none; |
||||
} |
||||
` |
||||
|
||||
export const ItemInfo = styled.div` |
||||
line-height: 17px; |
||||
` |
||||
|
||||
export const Name = styled.div` |
||||
font-size: 16px; |
||||
font-weight: bold; |
||||
color: #ccc; |
||||
` |
||||
|
||||
export const SportName = styled(T9n)<{ color: string }>` |
||||
margin-right: 10px; |
||||
font-size: 11px; |
||||
color: ${({ color }) => color}; |
||||
letter-spacing: 0.02em; |
||||
text-transform: uppercase; |
||||
` |
||||
|
||||
export const TeamOrCountry = styled.span` |
||||
font-size: 11px; |
||||
color: #ccc; |
||||
` |
||||
|
||||
export const LogoWrapper = styled.div` |
||||
display: flex; |
||||
justify-content: center; |
||||
align-items: center; |
||||
flex-shrink: 0; |
||||
width: 55px; |
||||
` |
||||
|
||||
export const Logo = styled.img` |
||||
width: 24px; |
||||
height: 24px; |
||||
` |
||||
@ -0,0 +1,21 @@ |
||||
import React from 'react' |
||||
|
||||
import { T9n } from 'features/T9n' |
||||
|
||||
import { |
||||
Wrapper, |
||||
Icon, |
||||
titleStyles, |
||||
} from './styled' |
||||
|
||||
type HeaderProps = { |
||||
image: string, |
||||
title: string, |
||||
} |
||||
|
||||
export const Header = ({ image, title }: HeaderProps) => ( |
||||
<Wrapper> |
||||
<Icon image={image} /> |
||||
<T9n t={title} customStyles={titleStyles} /> |
||||
</Wrapper> |
||||
) |
||||
@ -0,0 +1,24 @@ |
||||
import styled, { css } from 'styled-components/macro' |
||||
|
||||
export const Wrapper = styled.div` |
||||
display: flex; |
||||
align-items: center; |
||||
height: 30px; |
||||
padding-left: 20px; |
||||
background-color: #005EDD; |
||||
` |
||||
|
||||
export const Icon = styled.div<{ image: string }>` |
||||
width: 20px; |
||||
height: 20px; |
||||
margin-right: 10px; |
||||
background-image: url(/images/${({ image }) => `${image}.svg`}); |
||||
background-size: 100%; |
||||
background-repeat: no-repeat; |
||||
` |
||||
|
||||
export const titleStyles = css` |
||||
letter-spacing: 0.02em; |
||||
text-transform: uppercase; |
||||
color: #fff; |
||||
` |
||||
@ -0,0 +1,11 @@ |
||||
export const SEARCH_DELAY = 1500 // Задержка поиска в мс
|
||||
|
||||
export const MIN_CHARACTERS_LENGTH = 3 // Минимальное число символов для поиска
|
||||
|
||||
export type SportType = 'football' | 'basketball' | 'hockey' |
||||
|
||||
export const SPORT_TYPES = { |
||||
1: 'football', |
||||
2: 'hockey', |
||||
3: 'basketball', |
||||
} as const |
||||
@ -0,0 +1,85 @@ |
||||
import type { FormEvent, ChangeEvent } from 'react' |
||||
import { |
||||
useState, |
||||
FocusEvent, |
||||
useRef, |
||||
useCallback, |
||||
} from 'react' |
||||
|
||||
import trim from 'lodash/trim' |
||||
import debounce from 'lodash/debounce' |
||||
import isEmpty from 'lodash/isEmpty' |
||||
import size from 'lodash/size' |
||||
|
||||
import type { SearchItems } from 'requests' |
||||
import { getSearchItems } from 'requests' |
||||
import { useToggle } from 'hooks' |
||||
|
||||
import { SEARCH_DELAY, MIN_CHARACTERS_LENGTH } from '../config' |
||||
import { useNormalizedItems } from './useNormalizedItems' |
||||
|
||||
export const useSearch = () => { |
||||
const [searchItems, setSearchItems] = useState<SearchItems>({}) |
||||
const abortControllerRef = useRef<AbortController | null>(null) |
||||
const { |
||||
close, |
||||
isOpen, |
||||
open, |
||||
} = useToggle() |
||||
|
||||
const fetchSearchItems = useCallback(debounce((searchString: string) => { |
||||
const abortController = new window.AbortController() |
||||
abortControllerRef.current = abortController |
||||
|
||||
getSearchItems(searchString, abortController.signal).then((data) => { |
||||
setSearchItems(data) |
||||
abortControllerRef.current = null |
||||
}) |
||||
}, SEARCH_DELAY), []) |
||||
|
||||
const cancelRequest = useCallback(() => { |
||||
const abortController = abortControllerRef.current |
||||
|
||||
if (abortController) { |
||||
abortController.abort() |
||||
abortControllerRef.current = null |
||||
} |
||||
}, []) |
||||
|
||||
const onChange = useCallback(({ target: { value } }: ChangeEvent<HTMLInputElement>) => { |
||||
const trimmedValue = trim(value) |
||||
|
||||
if (size(trimmedValue) >= MIN_CHARACTERS_LENGTH) { |
||||
cancelRequest() |
||||
setSearchItems({}) |
||||
fetchSearchItems(trimmedValue) |
||||
open() |
||||
} else { |
||||
close() |
||||
} |
||||
}, [ |
||||
cancelRequest, |
||||
close, |
||||
fetchSearchItems, |
||||
open, |
||||
]) |
||||
|
||||
const onFocus = useCallback(({ target: { value } }: FocusEvent<HTMLInputElement>) => { |
||||
if (size(value) >= MIN_CHARACTERS_LENGTH) { |
||||
open() |
||||
} |
||||
}, [open]) |
||||
|
||||
const onSubmit = useCallback((e: FormEvent<HTMLFormElement>) => { |
||||
e.preventDefault() |
||||
}, []) |
||||
|
||||
return { |
||||
close, |
||||
normalizedItems: useNormalizedItems(searchItems), |
||||
onChange, |
||||
onFocus, |
||||
onSubmit, |
||||
showResults: isOpen && !isEmpty(searchItems), |
||||
} |
||||
} |
||||
@ -0,0 +1,104 @@ |
||||
import map from 'lodash/map' |
||||
|
||||
import type { SearchItems } from 'requests' |
||||
import { LOGOS_FALLBACKS } from 'config' |
||||
import { getLogo } from 'helpers' |
||||
import { useLexicsStore } from 'features/LexicsStore' |
||||
|
||||
import { SPORT_TYPES } from '../config' |
||||
|
||||
type Firstname = 'firstname_eng' | 'firstname_rus' |
||||
type Lastname = 'lastname_eng' | 'lastname_rus' |
||||
type Name = 'name_eng' | 'name_rus' |
||||
|
||||
export const useNormalizedItems = (searchItems: SearchItems) => { |
||||
const { |
||||
suffix, |
||||
} = useLexicsStore() |
||||
|
||||
const players = map(searchItems.players, (player) => { |
||||
const type = 'players' |
||||
|
||||
const firstName = player[`firstname_${suffix}` as Firstname] |
||||
const lastName = player[`lastname_${suffix}` as Lastname] |
||||
const teamName = player.team?.[`name_${suffix}` as Name] |
||||
|
||||
const sportType = SPORT_TYPES[player.sport] |
||||
|
||||
const { id } = player |
||||
|
||||
const logo = getLogo({ |
||||
id, |
||||
sportType, |
||||
type, |
||||
}) |
||||
|
||||
return { |
||||
fallbackImage: LOGOS_FALLBACKS[sportType].players, |
||||
id, |
||||
logo, |
||||
name: `${firstName} ${lastName}`, |
||||
profileUrl: `/players/${id}`, |
||||
sportType, |
||||
teamOrCountry: teamName, |
||||
} |
||||
}) |
||||
|
||||
const teams = map(searchItems.teams, (team) => { |
||||
const name = team[`name_${suffix}` as Name] |
||||
|
||||
const sportType = SPORT_TYPES[team.sport] |
||||
|
||||
const { id } = team |
||||
|
||||
const logo = getLogo({ |
||||
id, |
||||
sportType, |
||||
type: 'teams', |
||||
}) |
||||
|
||||
const country = team.country?.[`name_${suffix}` as Name] |
||||
|
||||
return { |
||||
fallbackImage: LOGOS_FALLBACKS[sportType].teams, |
||||
id, |
||||
logo, |
||||
name, |
||||
profileUrl: `/teams/${id}`, |
||||
sportType, |
||||
teamOrCountry: country, |
||||
} |
||||
}) |
||||
|
||||
const tournaments = map(searchItems.tournaments, (tournament) => { |
||||
const name = tournament[`name_${suffix}` as Name] |
||||
|
||||
const sportType = SPORT_TYPES[tournament.sport] |
||||
|
||||
const { id } = tournament |
||||
|
||||
const logo = getLogo({ |
||||
id, |
||||
sportType, |
||||
type: 'tournaments', |
||||
}) |
||||
|
||||
const country = tournament.country?.[`name_${suffix}` as Name] |
||||
|
||||
return { |
||||
fallbackImage: LOGOS_FALLBACKS[sportType].tournaments, |
||||
id, |
||||
logo, |
||||
name, |
||||
profileUrl: `/tournaments/${id}`, |
||||
sportType, |
||||
teamOrCountry: country, |
||||
} |
||||
}) |
||||
|
||||
return { |
||||
players, |
||||
teams, |
||||
tournaments, |
||||
} |
||||
} |
||||
@ -1,5 +1,69 @@ |
||||
import React from 'react' |
||||
import React, { Fragment } from 'react' |
||||
|
||||
import { Wrapper } from './styled' |
||||
import isEmpty from 'lodash/isEmpty' |
||||
|
||||
export const Search = () => <Wrapper /> |
||||
import { Input } from 'features/Common' |
||||
import { ItemsList } from 'features/ItemsList' |
||||
import { OutsideClick } from 'features/OutsideClick' |
||||
|
||||
import { useSearch } from './hooks' |
||||
import { Header } from './components/Header' |
||||
import { |
||||
Wrapper, |
||||
Form, |
||||
Results, |
||||
} from './styled' |
||||
|
||||
export const Search = () => { |
||||
const { |
||||
close, |
||||
normalizedItems: { |
||||
players, |
||||
teams, |
||||
tournaments, |
||||
}, |
||||
onChange, |
||||
onFocus, |
||||
onSubmit, |
||||
showResults, |
||||
} = useSearch() |
||||
|
||||
return ( |
||||
<OutsideClick onClick={close}> |
||||
<Wrapper> |
||||
<Form role='search' onSubmit={onSubmit}> |
||||
<Input |
||||
id='searchInput' |
||||
type='search' |
||||
onChange={onChange} |
||||
onFocus={onFocus} |
||||
/> |
||||
</Form> |
||||
{showResults && ( |
||||
<Results> |
||||
{!isEmpty(tournaments) && ( |
||||
<Fragment> |
||||
<Header title='tournament' image='tournament' /> |
||||
<ItemsList list={tournaments} /> |
||||
</Fragment> |
||||
)} |
||||
|
||||
{!isEmpty(teams) && ( |
||||
<Fragment> |
||||
<Header title='team' image='team' /> |
||||
<ItemsList list={teams} /> |
||||
</Fragment> |
||||
)} |
||||
|
||||
{!isEmpty(players) && ( |
||||
<Fragment> |
||||
<Header title='player' image='player' /> |
||||
<ItemsList list={players} /> |
||||
</Fragment> |
||||
)} |
||||
</Results> |
||||
)} |
||||
</Wrapper> |
||||
</OutsideClick> |
||||
) |
||||
} |
||||
|
||||
@ -1,3 +1,64 @@ |
||||
import styled from 'styled-components/macro' |
||||
|
||||
export const Wrapper = styled.div`` |
||||
import { сustomScrollbar } from 'features/Common' |
||||
import { |
||||
InputWrapper, |
||||
InputStyled, |
||||
Label, |
||||
} from 'features/Common/Input/styled' |
||||
|
||||
export const Wrapper = styled.div` |
||||
position: relative; |
||||
` |
||||
|
||||
export const Form = styled.form` |
||||
${InputWrapper} { |
||||
margin: 0; |
||||
padding-bottom: 13px; |
||||
} |
||||
|
||||
${InputStyled} { |
||||
width: 100%; |
||||
|
||||
::-webkit-search-decoration, |
||||
::-webkit-search-cancel-button, |
||||
::-webkit-search-results-button, |
||||
::-webkit-search-results-decoration { |
||||
display: none;
|
||||
} |
||||
} |
||||
|
||||
${Label} { |
||||
::before { |
||||
content: ''; |
||||
display: block; |
||||
width: 25px; |
||||
height: 25px; |
||||
background-image: url(/images/search.svg); |
||||
background-repeat: no-repeat; |
||||
} |
||||
} |
||||
|
||||
:focus-within { |
||||
${InputWrapper} { |
||||
padding-left: 0; |
||||
} |
||||
|
||||
${Label} { |
||||
::before { |
||||
display: none; |
||||
} |
||||
} |
||||
} |
||||
|
||||
` |
||||
|
||||
export const Results = styled.div` |
||||
position: absolute; |
||||
top: 56px; |
||||
width: 448px; |
||||
max-height: 431px; |
||||
overflow-y: auto; |
||||
|
||||
${сustomScrollbar} |
||||
` |
||||
|
||||
@ -0,0 +1,23 @@ |
||||
import { getLogo } from '..' |
||||
|
||||
describe('getLogo helper', () => { |
||||
it('returns logo url', () => { |
||||
expect(getLogo({ |
||||
id: 1, |
||||
sportType: 'football', |
||||
type: 'players', |
||||
})).toBe('https://instatscout.com/images/players/180/1.png') |
||||
|
||||
expect(getLogo({ |
||||
id: 1, |
||||
sportType: 'basketball', |
||||
type: 'teams', |
||||
})).toBe('https://basketball.instatscout.com/images/teams/180/1.png') |
||||
|
||||
expect(getLogo({ |
||||
id: 1, |
||||
sportType: 'hockey', |
||||
type: 'tournaments', |
||||
})).toBe('https://hockey.instatscout.com/images/tournaments/180/1.png') |
||||
}) |
||||
}) |
||||
@ -0,0 +1,23 @@ |
||||
import type { SportType } from 'features/Search/config' |
||||
|
||||
const IMAGES_URLS = { |
||||
basketball: 'https://basketball.instatscout.com/images', |
||||
football: 'https://instatscout.com/images', |
||||
hockey: 'https://hockey.instatscout.com/images', |
||||
} |
||||
|
||||
type GetLogoArgs = { |
||||
id: number, |
||||
size?: number, |
||||
sportType: SportType, |
||||
type: 'players' | 'teams' | 'tournaments', |
||||
} |
||||
|
||||
export const getLogo = ({ |
||||
id, |
||||
size = 180, |
||||
sportType, |
||||
type, |
||||
}: GetLogoArgs) => ( |
||||
`${IMAGES_URLS[sportType]}/${type}/${size}/${id}.png` |
||||
) |
||||
@ -1,3 +1,4 @@ |
||||
export * from './callApi' |
||||
export * from './callApi/getResponseData' |
||||
export * from './token' |
||||
export * from './getLogo' |
||||
|
||||
@ -0,0 +1,75 @@ |
||||
import { DATA_URL, PROCEDURES } from 'config' |
||||
import { callApi, getResponseData } from 'helpers' |
||||
|
||||
const proc = PROCEDURES.get_players_teams_tournaments |
||||
|
||||
type Gender = 1 | 2 |
||||
|
||||
type Sport = 1 | 2 | 3 |
||||
|
||||
type Player = { |
||||
firstname_eng: string, |
||||
firstname_rus: string, |
||||
gender?: Gender, |
||||
id: number, |
||||
lastname_eng: string, |
||||
lastname_rus: string, |
||||
sport: Sport, |
||||
team?: { |
||||
id: number, |
||||
name_eng: string, |
||||
name_rus: string, |
||||
}, |
||||
} |
||||
|
||||
type Team = { |
||||
country?: { |
||||
id: number, |
||||
name_eng: string, |
||||
name_rus: string, |
||||
}, |
||||
gender?: Gender, |
||||
id: number, |
||||
name_eng: string, |
||||
name_rus: string, |
||||
sport: Sport, |
||||
} |
||||
|
||||
type Tournament = { |
||||
country?: { |
||||
id: number, |
||||
name_eng: string, |
||||
name_rus: string, |
||||
}, |
||||
gender?: Gender, |
||||
id: number, |
||||
name_eng: string, |
||||
name_rus: string, |
||||
sport: Sport, |
||||
} |
||||
|
||||
export type SearchItems = { |
||||
players?: Array<Player>, |
||||
teams?: Array<Team>, |
||||
tournaments?: Array<Tournament>, |
||||
} |
||||
|
||||
export const getSearchItems = ( |
||||
searchString: string, |
||||
abortSignal: AbortSignal, |
||||
): Promise<SearchItems> => { |
||||
const config = { |
||||
body: { |
||||
params: { |
||||
_p_name: searchString, |
||||
}, |
||||
proc, |
||||
}, |
||||
} |
||||
|
||||
return callApi({ |
||||
abortSignal, |
||||
config, |
||||
url: DATA_URL, |
||||
}).then(getResponseData(proc)) |
||||
} |
||||
Loading…
Reference in new issue