category-select.jsx (3004B)
1 /* 2 GoToSocial 3 Copyright (C) GoToSocial Authors admin@gotosocial.org 4 SPDX-License-Identifier: AGPL-3.0-or-later 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 "use strict"; 21 22 const React = require("react"); 23 const splitFilterN = require("split-filter-n"); 24 const syncpipe = require('syncpipe'); 25 const { matchSorter } = require("match-sorter"); 26 27 const query = require("../../lib/query"); 28 29 const ComboBox = require("../../components/combo-box"); 30 31 function useEmojiByCategory(emoji) { 32 // split all emoji over an object keyed by the category names (or Unsorted) 33 return React.useMemo(() => splitFilterN( 34 emoji, 35 [], 36 (entry) => entry.category ?? "Unsorted" 37 ), [emoji]); 38 } 39 40 function CategorySelect({ field, children }) { 41 const { value, setIsNew } = field; 42 43 const { 44 data: emoji = [], 45 isLoading, 46 isSuccess, 47 error 48 } = query.useListEmojiQuery({ filter: "domain:local" }); 49 50 const emojiByCategory = useEmojiByCategory(emoji); 51 52 const categories = React.useMemo(() => new Set(Object.keys(emojiByCategory)), [emojiByCategory]); 53 54 // data used by the ComboBox element to select an emoji category 55 const categoryItems = React.useMemo(() => { 56 return syncpipe(emojiByCategory, [ 57 (_) => Object.keys(_), // just emoji category names 58 (_) => matchSorter(_, value, { threshold: matchSorter.rankings.NO_MATCH }), // sorted by complex algorithm 59 (_) => _.map((categoryName) => [ // map to input value, and selectable element with icon 60 categoryName, 61 <> 62 <img src={emojiByCategory[categoryName][0].static_url} aria-hidden="true"></img> 63 {categoryName} 64 </> 65 ]) 66 ]); 67 }, [emojiByCategory, value]); 68 69 React.useEffect(() => { 70 if (value != undefined && isSuccess && value.trim().length > 0) { 71 setIsNew(!categories.has(value.trim())); 72 } 73 }, [categories, value, isSuccess, setIsNew]); 74 75 if (error) { // fall back to plain text input, but this would almost certainly have caused a bigger error message elsewhere 76 return ( 77 <> 78 <input type="text" placeholder="e.g., reactions" onChange={(e) => { field.value = e.target.value; }} />; 79 </> 80 ); 81 } else if (isLoading) { 82 return <input type="text" value="Loading categories..." disabled={true} />; 83 } 84 85 return ( 86 <ComboBox 87 field={field} 88 items={categoryItems} 89 label="Category" 90 placeholder="e.g., reactions" 91 children={children} 92 /> 93 ); 94 } 95 96 module.exports = { 97 useEmojiByCategory, 98 CategorySelect 99 };