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