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