]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/common/UserSelect.js
add up to 4 player support for zsr sync
[alttp.git] / resources / js / components / common / UserSelect.js
1 import axios from 'axios';
2 import PropTypes from 'prop-types';
3 import React, { useCallback, useEffect, useRef, useState } from 'react';
4 import { Button, Form, ListGroup } from 'react-bootstrap';
5
6 import Icon from '../common/Icon';
7 import UserBox from '../users/Box';
8 import debounce from '../../helpers/debounce';
9
10 const UserSelect = ({ name, onChange, value }) => {
11         const [resolved, setResolved] = useState(null);
12         const [results, setResults] = useState([]);
13         const [search, setSearch] = useState('');
14         const [showResults, setShowResults] = useState(false);
15
16         const ref = useRef(null);
17
18         useEffect(() => {
19                 const handleEventOutside = e => {
20                         if (ref.current && !ref.current.contains(e.target)) {
21                                 setShowResults(false);
22                         }
23                 };
24                 document.addEventListener('mousedown', handleEventOutside, true);
25                 document.addEventListener('focus', handleEventOutside, true);
26                 return () => {
27                         document.removeEventListener('mousedown', handleEventOutside, true);
28                         document.removeEventListener('focus', handleEventOutside, true);
29                 };
30         }, []);
31
32         let ctrl = null;
33         const fetch = useCallback(debounce(async phrase => {
34                 if (ctrl) {
35                         ctrl.abort();
36                 }
37                 ctrl = new AbortController();
38                 if (!phrase || phrase.length < 3) {
39                         setResults([]);
40                         return;
41                 }
42                 try {
43                         const response = await axios.get(`/api/users`, {
44                                 params: {
45                                         phrase,
46                                 },
47                                 signal: ctrl.signal,
48                         });
49                         ctrl = null;
50                         setResults(response.data);
51                 } catch (e) {
52                         ctrl = null;
53                         console.error(e);
54                 }
55         }, 300), []);
56
57         useEffect(() => {
58                 fetch(search);
59         }, [search]);
60
61         useEffect(() => {
62                 if (value) {
63                         axios
64                                 .get(`/api/users/${value}`)
65                         .then(response => {
66                                 setResolved(response.data);
67                         });
68                 } else {
69                         setResolved(null);
70                 }
71         }, [value]);
72
73         if (value) {
74                 return <div className="d-flex justify-content-between">
75                         {resolved ? <UserBox discriminator noLink user={resolved} /> : <span>value</span>}
76                         <Button
77                                 onClick={() => onChange({ target: { name, value: null }})}
78                                 size="sm"
79                                 variant="outline-danger"
80                         >
81                                 <Icon.REMOVE />
82                         </Button>
83                 </div>;
84         }
85         return <div className={`user-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
86                 <Form.Control
87                         className="search-input"
88                         name={Math.random().toString(20).substr(2, 10)}
89                         onChange={e => setSearch(e.target.value)}
90                         onFocus={() => setShowResults(true)}
91                         type="search"
92                         value={search}
93                 />
94                 <div className="search-results-holder">
95                         <ListGroup className="search-results">
96                                 {results.map(result =>
97                                         <ListGroup.Item
98                                                 action
99                                                 key={result.id}
100                                                 onClick={() => onChange({
101                                                         target: { name, value: result.id },
102                                                 })}
103                                         >
104                                                 <UserBox discriminator noLink user={result} />
105                                         </ListGroup.Item>
106                                 )}
107                         </ListGroup>
108                 </div>
109         </div>;
110 };
111
112 UserSelect.propTypes = {
113         name: PropTypes.string,
114         onChange: PropTypes.func,
115         value: PropTypes.string,
116 };
117
118 export default UserSelect;