diff options
Diffstat (limited to 'web/source/settings/admin/emoji')
-rw-r--r-- | web/source/settings/admin/emoji/category-select.jsx | 96 | ||||
-rw-r--r-- | web/source/settings/admin/emoji/local/detail.js | 146 | ||||
-rw-r--r-- | web/source/settings/admin/emoji/local/index.tsx | 35 | ||||
-rw-r--r-- | web/source/settings/admin/emoji/local/new-emoji.tsx | 116 | ||||
-rw-r--r-- | web/source/settings/admin/emoji/local/overview.js | 153 | ||||
-rw-r--r-- | web/source/settings/admin/emoji/local/use-shortcode.js | 56 | ||||
-rw-r--r-- | web/source/settings/admin/emoji/remote/index.tsx | 54 | ||||
-rw-r--r-- | web/source/settings/admin/emoji/remote/parse-from-toot.tsx | 235 |
8 files changed, 0 insertions, 891 deletions
diff --git a/web/source/settings/admin/emoji/category-select.jsx b/web/source/settings/admin/emoji/category-select.jsx deleted file mode 100644 index e5cf29939..000000000 --- a/web/source/settings/admin/emoji/category-select.jsx +++ /dev/null @@ -1,96 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -const React = require("react"); -const splitFilterN = require("split-filter-n"); -const syncpipe = require('syncpipe'); -const { matchSorter } = require("match-sorter"); - -const ComboBox = require("../../components/combo-box"); -const { useListEmojiQuery } = require("../../lib/query/admin/custom-emoji"); - -function useEmojiByCategory(emoji) { - // split all emoji over an object keyed by the category names (or Unsorted) - return React.useMemo(() => splitFilterN( - emoji, - [], - (entry) => entry.category ?? "Unsorted" - ), [emoji]); -} - -function CategorySelect({ field, children }) { - const { value, setIsNew } = field; - - const { - data: emoji = [], - isLoading, - isSuccess, - error - } = useListEmojiQuery({ filter: "domain:local" }); - - const emojiByCategory = useEmojiByCategory(emoji); - - const categories = React.useMemo(() => new Set(Object.keys(emojiByCategory)), [emojiByCategory]); - - // data used by the ComboBox element to select an emoji category - const categoryItems = React.useMemo(() => { - return syncpipe(emojiByCategory, [ - (_) => Object.keys(_), // just emoji category names - (_) => matchSorter(_, value, { threshold: matchSorter.rankings.NO_MATCH }), // sorted by complex algorithm - (_) => _.map((categoryName) => [ // map to input value, and selectable element with icon - categoryName, - <> - <img src={emojiByCategory[categoryName][0].static_url} aria-hidden="true"></img> - {categoryName} - </> - ]) - ]); - }, [emojiByCategory, value]); - - React.useEffect(() => { - if (value != undefined && isSuccess && value.trim().length > 0) { - setIsNew(!categories.has(value.trim())); - } - }, [categories, value, isSuccess, setIsNew]); - - if (error) { // fall back to plain text input, but this would almost certainly have caused a bigger error message elsewhere - return ( - <> - <input type="text" placeholder="e.g., reactions" onChange={(e) => { field.value = e.target.value; }} />; - </> - ); - } else if (isLoading) { - return <input type="text" value="Loading categories..." disabled={true} />; - } - - return ( - <ComboBox - field={field} - items={categoryItems} - label="Category" - placeholder="e.g., reactions" - children={children} - /> - ); -} - -module.exports = { - useEmojiByCategory, - CategorySelect -};
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/local/detail.js b/web/source/settings/admin/emoji/local/detail.js deleted file mode 100644 index a78e3e499..000000000 --- a/web/source/settings/admin/emoji/local/detail.js +++ /dev/null @@ -1,146 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -import React, { useEffect } from "react"; -import { useRoute, Link, Redirect } from "wouter"; - -import { useComboBoxInput, useFileInput, useValue } from "../../../lib/form"; -import { CategorySelect } from "../category-select"; - -import useFormSubmit from "../../../lib/form/submit"; -import { useBaseUrl } from "../../../lib/navigation/util"; - -import FakeToot from "../../../components/fake-toot"; -import FormWithData from "../../../lib/form/form-with-data"; -import Loading from "../../../components/loading"; -import { FileInput } from "../../../components/form/inputs"; -import MutationButton from "../../../components/form/mutation-button"; -import { Error } from "../../../components/error"; - -import { useGetEmojiQuery, useEditEmojiMutation, useDeleteEmojiMutation } from "../../../lib/query/admin/custom-emoji"; - -export default function EmojiDetailRoute({ }) { - const baseUrl = useBaseUrl(); - let [_match, params] = useRoute(`${baseUrl}/:emojiId`); - if (params?.emojiId == undefined) { - return <Redirect to={baseUrl} />; - } else { - return ( - <div className="emoji-detail"> - <Link to={baseUrl}><a>< go back</a></Link> - <FormWithData dataQuery={useGetEmojiQuery} queryArg={params.emojiId} DataForm={EmojiDetailForm} /> - </div> - ); - } -} - -function EmojiDetailForm({ data: emoji }) { - const baseUrl = useBaseUrl(); - const form = { - id: useValue("id", emoji.id), - category: useComboBoxInput("category", { source: emoji }), - image: useFileInput("image", { - withPreview: true, - maxSize: 50 * 1024 // TODO: get from instance api - }) - }; - - const [modifyEmoji, result] = useFormSubmit(form, useEditEmojiMutation()); - - // Automatic submitting of category change - useEffect(() => { - if ( - form.category.hasChanged() && - !form.category.state.open && - !form.category.isNew) { - modifyEmoji(); - } - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [form.category.hasChanged(), form.category.isNew, form.category.state.open]); - - const [deleteEmoji, deleteResult] = useDeleteEmojiMutation(); - - if (deleteResult.isSuccess) { - return <Redirect to={baseUrl} />; - } - - return ( - <> - <div className="emoji-header"> - <img src={emoji.url} alt={emoji.shortcode} title={emoji.shortcode} /> - <div> - <h2>{emoji.shortcode}</h2> - <MutationButton - label="Delete" - type="button" - onClick={() => deleteEmoji(emoji.id)} - className="danger" - showError={false} - result={deleteResult} - /> - </div> - </div> - - <form onSubmit={modifyEmoji} className="left-border"> - <h2>Modify this emoji {result.isLoading && <Loading />}</h2> - - <div className="update-category"> - <CategorySelect - field={form.category} - > - <MutationButton - name="create-category" - label="Create" - result={result} - showError={false} - style={{ visibility: (form.category.isNew ? "initial" : "hidden") }} - /> - </CategorySelect> - </div> - - <div className="update-image"> - <FileInput - field={form.image} - label="Image" - accept="image/png,image/gif" - /> - - <MutationButton - name="image" - label="Replace image" - showError={false} - result={result} - /> - - <FakeToot> - Look at this new custom emoji <img - className="emoji" - src={form.image.previewURL ?? emoji.url} - title={`:${emoji.shortcode}:`} - alt={emoji.shortcode} - /> isn't it cool? - </FakeToot> - - {result.error && <Error error={result.error} />} - {deleteResult.error && <Error error={deleteResult.error} />} - </div> - </form> - </> - ); -}
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/local/index.tsx b/web/source/settings/admin/emoji/local/index.tsx deleted file mode 100644 index 74a891f3e..000000000 --- a/web/source/settings/admin/emoji/local/index.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -import React from "react"; -import { Switch, Route } from "wouter"; - -import EmojiOverview from "./overview"; -import EmojiDetail from "./detail"; - -export default function CustomEmoji({ baseUrl }) { - return ( - <Switch> - <Route path={`${baseUrl}/:emojiId`}> - <EmojiDetail /> - </Route> - <EmojiOverview /> - </Switch> - ); -} diff --git a/web/source/settings/admin/emoji/local/new-emoji.tsx b/web/source/settings/admin/emoji/local/new-emoji.tsx deleted file mode 100644 index c6a203765..000000000 --- a/web/source/settings/admin/emoji/local/new-emoji.tsx +++ /dev/null @@ -1,116 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -import React, { useMemo, useEffect } from "react"; - -import { useFileInput, useComboBoxInput } from "../../../lib/form"; -import useShortcode from "./use-shortcode"; - -import useFormSubmit from "../../../lib/form/submit"; - -import { TextInput, FileInput } from "../../../components/form/inputs"; - -import { CategorySelect } from '../category-select'; -import FakeToot from "../../../components/fake-toot"; -import MutationButton from "../../../components/form/mutation-button"; -import { useAddEmojiMutation } from "../../../lib/query/admin/custom-emoji"; -import { useInstanceV1Query } from "../../../lib/query"; - -export default function NewEmojiForm() { - const shortcode = useShortcode(); - - const { data: instance } = useInstanceV1Query(); - const emojiMaxSize = useMemo(() => { - return instance?.configuration?.emojis?.emoji_size_limit ?? 50 * 1024; - }, [instance]); - - const image = useFileInput("image", { - withPreview: true, - maxSize: emojiMaxSize - }); - - const category = useComboBoxInput("category"); - - const [submitForm, result] = useFormSubmit({ - shortcode, image, category - }, useAddEmojiMutation()); - - useEffect(() => { - if (shortcode.value === undefined || shortcode.value.length == 0) { - if (image.value != undefined) { - let [name, _ext] = image.value.name.split("."); - shortcode.setter(name); - } - } - - /* We explicitly don't want to have 'shortcode' as a dependency here - because we only want to change the shortcode to the filename if the field is empty - at the moment the file is selected, not some time after when the field is emptied - */ - /* eslint-disable-next-line react-hooks/exhaustive-deps */ - }, [image.value]); - - let emojiOrShortcode; - - if (image.previewValue != undefined) { - emojiOrShortcode = <img - className="emoji" - src={image.previewValue} - title={`:${shortcode.value}:`} - alt={shortcode.value} - />; - } else if (shortcode.value !== undefined && shortcode.value.length > 0) { - emojiOrShortcode = `:${shortcode.value}:`; - } else { - emojiOrShortcode = `:your_emoji_here:`; - } - - return ( - <div> - <h2>Add new custom emoji</h2> - - <FakeToot> - Look at this new custom emoji {emojiOrShortcode} isn't it cool? - </FakeToot> - - <form onSubmit={submitForm} className="form-flex"> - <FileInput - field={image} - accept="image/png,image/gif,image/webp" - /> - - <TextInput - field={shortcode} - label="Shortcode, must be unique among the instance's local emoji" - /> - - <CategorySelect - field={category} - children={[]} - /> - - <MutationButton - disabled={image.previewValue === undefined} - label="Upload emoji" - result={result} - /> - </form> - </div> - ); -}
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/local/overview.js b/web/source/settings/admin/emoji/local/overview.js deleted file mode 100644 index 45bfd614d..000000000 --- a/web/source/settings/admin/emoji/local/overview.js +++ /dev/null @@ -1,153 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -const React = require("react"); -const { Link } = require("wouter"); -const syncpipe = require("syncpipe"); -const { matchSorter } = require("match-sorter"); - -const NewEmojiForm = require("./new-emoji").default; -const { useTextInput } = require("../../../lib/form"); - -const { useEmojiByCategory } = require("../category-select"); -const { useBaseUrl } = require("../../../lib/navigation/util"); - -const Loading = require("../../../components/loading"); -const { Error } = require("../../../components/error"); -const { TextInput } = require("../../../components/form/inputs"); -const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji"); - -module.exports = function EmojiOverview({ }) { - const { - data: emoji = [], - isLoading, - isError, - error - } = useListEmojiQuery({ filter: "domain:local" }); - - let content = null; - - if (isLoading) { - content = <Loading />; - } else if (isError) { - content = <Error error={error} />; - } else { - content = ( - <> - <EmojiList emoji={emoji} /> - <NewEmojiForm emoji={emoji} /> - </> - ); - } - - return ( - <> - <h1>Local Custom Emoji</h1> - <p> - To use custom emoji in your toots they have to be 'local' to the instance. - You can either upload them here directly, or copy from those already - present on other (known) instances through the <Link to={`./remote`}>Remote Emoji</Link> page. - </p> - <p> - <strong>Be warned!</strong> If you upload more than about 300-400 custom emojis in - total on your instance, this may lead to rate-limiting issues for users and clients - if they try to load all the emoji images at once (which is what many clients do). - </p> - {content} - </> - ); -}; - -function EmojiList({ emoji }) { - const filterField = useTextInput("filter"); - const filter = filterField.value; - - const emojiByCategory = useEmojiByCategory(emoji); - - /* Filter emoji based on shortcode match with user input, hiding empty categories */ - const { filteredEmoji, hidden } = React.useMemo(() => { - let hidden = emoji.length; - const filteredEmoji = syncpipe(emojiByCategory, [ - (_) => Object.entries(emojiByCategory), - (_) => _.map(([category, entries]) => { - let filteredEntries = matchSorter(entries, filter, { keys: ["shortcode"] }); - if (filteredEntries.length == 0) { - return null; - } else { - hidden -= filteredEntries.length; - return [category, filteredEntries]; - } - }), - (_) => _.filter((value) => value !== null) - ]); - - return { filteredEmoji, hidden }; - }, [filter, emojiByCategory, emoji.length]); - - return ( - <div> - <h2>Overview</h2> - {emoji.length > 0 - ? <span>{emoji.length} custom emoji {hidden > 0 && `(${hidden} filtered)`}</span> - : <span>No custom emoji yet, you can add one below.</span> - } - <div className="list emoji-list"> - <div className="header"> - <TextInput - field={filterField} - name="emoji-shortcode" - placeholder="Search" - /> - </div> - <div className="entries scrolling"> - {filteredEmoji.length > 0 - ? ( - <div className="entries scrolling"> - {filteredEmoji.map(([category, entries]) => { - return <EmojiCategory key={category} category={category} entries={entries} />; - })} - </div> - ) - : <div className="entry">No local emoji matched your filter.</div> - } - </div> - </div> - </div> - ); -} - -function EmojiCategory({ category, entries }) { - const baseUrl = useBaseUrl(); - return ( - <div className="entry"> - <b>{category}</b> - <div className="emoji-group"> - {entries.map((e) => { - return ( - <Link key={e.id} to={`${baseUrl}/${e.id}`}> - <a> - <img src={e.url} alt={e.shortcode} title={`:${e.shortcode}:`} /> - </a> - </Link> - ); - })} - </div> - </div> - ); -}
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/local/use-shortcode.js b/web/source/settings/admin/emoji/local/use-shortcode.js deleted file mode 100644 index 67255860f..000000000 --- a/web/source/settings/admin/emoji/local/use-shortcode.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -const React = require("react"); - -const { useTextInput } = require("../../../lib/form"); -const { useListEmojiQuery } = require("../../../lib/query/admin/custom-emoji"); - -const shortcodeRegex = /^\w{2,30}$/; - -module.exports = function useShortcode() { - const { data: emoji = [] } = useListEmojiQuery({ - filter: "domain:local" - }); - - const emojiCodes = React.useMemo(() => { - return new Set(emoji.map((e) => e.shortcode)); - }, [emoji]); - - return useTextInput("shortcode", { - validator: function validateShortcode(code) { - // technically invalid, but hacky fix to prevent validation error on page load - if (code == "") { return ""; } - - if (emojiCodes.has(code)) { - return "Shortcode already in use"; - } - - if (code.length < 2 || code.length > 30) { - return "Shortcode must be between 2 and 30 characters"; - } - - if (!shortcodeRegex.test(code)) { - return "Shortcode must only contain letters, numbers, and underscores"; - } - - return ""; - } - }); -};
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/remote/index.tsx b/web/source/settings/admin/emoji/remote/index.tsx deleted file mode 100644 index d9c786be2..000000000 --- a/web/source/settings/admin/emoji/remote/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -import React, { useMemo } from "react"; - -import ParseFromToot from "./parse-from-toot"; - -import Loading from "../../../components/loading"; -import { Error } from "../../../components/error"; -import { useListEmojiQuery } from "../../../lib/query/admin/custom-emoji"; - -export default function RemoteEmoji() { - // local emoji are queried for shortcode collision detection - const { - data: emoji = [], - isLoading, - error - } = useListEmojiQuery({ filter: "domain:local" }); - - const emojiCodes = useMemo(() => { - return new Set(emoji.map((e) => e.shortcode)); - }, [emoji]); - - return ( - <> - <h1>Custom Emoji (remote)</h1> - {error && - <Error error={error} /> - } - {isLoading - ? <Loading /> - : <> - <ParseFromToot emojiCodes={emojiCodes} /> - </> - } - </> - ); -} diff --git a/web/source/settings/admin/emoji/remote/parse-from-toot.tsx b/web/source/settings/admin/emoji/remote/parse-from-toot.tsx deleted file mode 100644 index df1c221ba..000000000 --- a/web/source/settings/admin/emoji/remote/parse-from-toot.tsx +++ /dev/null @@ -1,235 +0,0 @@ -/* - GoToSocial - Copyright (C) GoToSocial Authors admin@gotosocial.org - SPDX-License-Identifier: AGPL-3.0-or-later - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <http://www.gnu.org/licenses/>. -*/ - -import React, { useCallback, useEffect } from "react"; - -import { useTextInput, useComboBoxInput, useCheckListInput } from "../../../lib/form"; - -import useFormSubmit from "../../../lib/form/submit"; - -import CheckList from "../../../components/check-list"; -import { CategorySelect } from '../category-select'; - -import { TextInput } from "../../../components/form/inputs"; -import MutationButton from "../../../components/form/mutation-button"; -import { Error } from "../../../components/error"; -import { useSearchItemForEmojiMutation, usePatchRemoteEmojisMutation } from "../../../lib/query/admin/custom-emoji"; - -export default function ParseFromToot({ emojiCodes }) { - const [searchStatus, result] = useSearchItemForEmojiMutation(); - const urlField = useTextInput("url"); - - function submitSearch(e) { - e.preventDefault(); - if (urlField.value !== undefined && urlField.value.trim().length != 0) { - searchStatus(urlField.value); - } - } - - return ( - <div className="parse-emoji"> - <h2>Steal this look</h2> - <form onSubmit={submitSearch}> - <div className="form-field text"> - <label htmlFor="url"> - Link to a toot: - </label> - <div className="row"> - <input - type="text" - id="url" - name="url" - onChange={urlField.onChange} - value={urlField.value} - /> - <button disabled={result.isLoading}> - <i className={[ - "fa fa-fw", - (result.isLoading - ? "fa-refresh fa-spin" - : "fa-search") - ].join(" ")} aria-hidden="true" title="Search" /> - <span className="sr-only">Search</span> - </button> - </div> - </div> - </form> - <SearchResult result={result} localEmojiCodes={emojiCodes} /> - </div> - ); -} - -function SearchResult({ result, localEmojiCodes }) { - const { error, data, isSuccess, isError } = result; - - if (!(isSuccess || isError)) { - return null; - } - - if (error == "NONE_FOUND") { - return "No results found"; - } else if (error == "LOCAL_INSTANCE") { - return <b>This is a local user/toot, all referenced emoji are already on your instance</b>; - } else if (error != undefined) { - return <Error error={result.error} />; - } - - if (data.list.length == 0) { - return <b>This {data.type == "statuses" ? "toot" : "account"} doesn't use any custom emoji</b>; - } - - return ( - <CopyEmojiForm - localEmojiCodes={localEmojiCodes} - type={data.type} - emojiList={data.list} - /> - ); -} - -function CopyEmojiForm({ localEmojiCodes, type, emojiList }) { - const form = { - selectedEmoji: useCheckListInput("selectedEmoji", { - entries: emojiList, - uniqueKey: "id" - }), - category: useComboBoxInput("category") - }; - - const [formSubmit, result] = useFormSubmit( - form, - usePatchRemoteEmojisMutation(), - { - changedOnly: false, - onFinish: ({ data }) => { - if (data) { - // uncheck all successfully processed emoji - const processed = data.map((emoji) => { - return [emoji.id, { checked: false }]; - }); - form.selectedEmoji.updateMultiple(processed); - } - } - } - ); - - const buttonsInactive = form.selectedEmoji.someSelected - ? { - disabled: false, - title: "" - } - : { - disabled: true, - title: "No emoji selected, cannot perform any actions" - }; - - const checkListExtraProps = useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]); - - return ( - <div className="parsed"> - <span>This {type == "statuses" ? "toot" : "account"} uses the following custom emoji, select the ones you want to copy/disable:</span> - <form onSubmit={formSubmit}> - <CheckList - field={form.selectedEmoji} - header={<></>} - EntryComponent={EmojiEntry} - getExtraProps={checkListExtraProps} - /> - - <CategorySelect - field={form.category} - children={[]} - /> - - <div className="action-buttons row"> - <MutationButton - name="copy" - label="Copy to local emoji" - result={result} - showError={false} - {...buttonsInactive} - /> - <MutationButton - name="disable" - label="Disable" - result={result} - className="button danger" - showError={false} - {...buttonsInactive} - /> - </div> - {result.error && ( - Array.isArray(result.error) - ? <ErrorList errors={result.error} /> - : <Error error={result.error} /> - )} - </form> - </div> - ); -} - -function ErrorList({ errors }) { - return ( - <div className="error"> - One or multiple emoji failed to process: - {errors.map(([shortcode, err]) => ( - <div key={shortcode}> - <b>{shortcode}:</b> {err} - </div> - ))} - </div> - ); -} - -function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } }) { - const shortcodeField = useTextInput("shortcode", { - defaultValue: emoji.shortcode, - validator: function validateShortcode(code) { - return (emoji.checked && localEmojiCodes.has(code)) - ? "Shortcode already in use" - : ""; - } - }); - - useEffect(() => { - if (emoji.valid != shortcodeField.valid) { - onChange({ valid: shortcodeField.valid }); - } - }, [onChange, emoji.valid, shortcodeField.valid]); - - useEffect(() => { - shortcodeField.validate(); - // only need this update if it's the emoji.checked that updated, not shortcodeField - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [emoji.checked]); - - return ( - <> - <img className="emoji" src={emoji.url} title={emoji.shortcode} /> - - <TextInput - field={shortcodeField} - onChange={(e) => { - shortcodeField.onChange(e); - onChange({ shortcode: e.target.value, checked: true }); - }} - /> - </> - ); -}
\ No newline at end of file |