]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/common/ChannelSelect.js
guessing game controls
[alttp.git] / resources / js / components / common / ChannelSelect.js
1 import axios from 'axios';
2 import PropTypes from 'prop-types';
3 import React, { useCallback, useEffect, useRef, useState } from 'react';
4 import { Alert, Button, Form, ListGroup } from 'react-bootstrap';
5 import { useTranslation } from 'react-i18next';
6
7 import Icon from './Icon';
8 import debounce from '../../helpers/debounce';
9
10 const ChannelSelect = ({ autoSelect, joinable, manageable, 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         const { t } = useTranslation();
18
19         useEffect(() => {
20                 const handleEventOutside = e => {
21                         if (ref.current && !ref.current.contains(e.target)) {
22                                 setShowResults(false);
23                         }
24                 };
25                 document.addEventListener('click', handleEventOutside, true);
26                 document.addEventListener('focus', handleEventOutside, true);
27                 return () => {
28                         document.removeEventListener('click', handleEventOutside, true);
29                         document.removeEventListener('focus', handleEventOutside, true);
30                 };
31         }, []);
32
33         let ctrl = null;
34         const fetch = useCallback(debounce(async phrase => {
35                 if (ctrl) {
36                         ctrl.abort();
37                 }
38                 ctrl = new AbortController();
39                 try {
40                         const response = await axios.get(`/api/channels`, {
41                                 params: {
42                                         joinable: joinable ? 1 : 0,
43                                         manageable: manageable ? 1 : 0,
44                                         phrase,
45                                 },
46                                 signal: ctrl.signal,
47                         });
48                         ctrl = null;
49                         setResults(response.data);
50                         if (autoSelect && !phrase && response.data.length === 1) {
51                                 onChange({
52                                         channel: response.data[0],
53                                         target: { value: response.data[0].id },
54                                 });
55                         }
56                 } catch (e) {
57                         ctrl = null;
58                         console.error(e);
59                 }
60         }, 300), [autoSelect, joinable, manageable]);
61
62         useEffect(() => {
63                 fetch(search);
64         }, [search]);
65
66         useEffect(() => {
67                 if (value) {
68                         axios
69                                 .get(`/api/channels/${value}`)
70                         .then(response => {
71                                 setResolved(response.data);
72                         });
73                 } else {
74                         setResolved(null);
75                 }
76         }, [value]);
77
78         if (value) {
79                 return <div className="d-flex align-items-center justify-content-between">
80                         <span>{resolved ? resolved.title : value}</span>
81                         <Button
82                                 className="ms-2"
83                                 onClick={() => onChange({ channel: null, target: { value: '' }})}
84                                 title={t('button.unset')}
85                                 variant="outline-danger"
86                         >
87                                 <Icon.REMOVE title="" />
88                         </Button>
89                 </div>;
90         }
91         return <div className={`channel-select ${showResults ? 'expanded' : 'collapsed'}`} ref={ref}>
92                 <Form.Control
93                         className="search-input"
94                         name={Math.random().toString(20).substr(2, 10)}
95                         onChange={e => setSearch(e.target.value)}
96                         onFocus={() => setShowResults(true)}
97                         type="search"
98                         value={search}
99                 />
100                 <div className="search-results-holder">
101                         {results.length ?
102                                 <ListGroup className="search-results">
103                                         {results.map(result =>
104                                                 <ListGroup.Item
105                                                         action
106                                                         key={result.id}
107                                                         onClick={() => onChange({
108                                                                 channel: result,
109                                                                 target: { value: result.id },
110                                                         })}
111                                                 >
112                                                         {result.title}
113                                                 </ListGroup.Item>
114                                         )}
115                                 </ListGroup>
116                         :
117                                 <Alert className="search-results" variant="info">
118                                         {t('search.noResults')}
119                                 </Alert>
120                         }
121                 </div>
122         </div>;
123 };
124
125 ChannelSelect.propTypes = {
126         autoSelect: PropTypes.bool,
127         joinable: PropTypes.bool,
128         manageable: PropTypes.bool,
129         onChange: PropTypes.func,
130         value: PropTypes.oneOfType([
131                 PropTypes.number,
132                 PropTypes.string,
133         ]),
134 };
135
136 export default ChannelSelect;