diff options
| author | 2024-04-24 12:12:47 +0200 | |
|---|---|---|
| committer | 2024-04-24 11:12:47 +0100 | |
| commit | 7a1e6394831fb07e303c5ed0900dfe1ea4820de5 (patch) | |
| tree | bcd526463b19a85fbe821dcad2276da401daec18 /web/source/settings/views/admin/emoji/remote | |
| parent | [chore]: Bump codeberg.org/gruf/go-mutexes from 1.4.0 to 1.4.1 (#2860) (diff) | |
| download | gotosocial-7a1e6394831fb07e303c5ed0900dfe1ea4820de5.tar.xz | |
[chore] Refactor settings panel routing (and other fixes) (#2864)
Diffstat (limited to 'web/source/settings/views/admin/emoji/remote')
| -rw-r--r-- | web/source/settings/views/admin/emoji/remote/index.tsx | 46 | ||||
| -rw-r--r-- | web/source/settings/views/admin/emoji/remote/steal-this-look.tsx | 235 |
2 files changed, 281 insertions, 0 deletions
diff --git a/web/source/settings/views/admin/emoji/remote/index.tsx b/web/source/settings/views/admin/emoji/remote/index.tsx new file mode 100644 index 000000000..5521d1115 --- /dev/null +++ b/web/source/settings/views/admin/emoji/remote/index.tsx @@ -0,0 +1,46 @@ +/* + 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 StealThisLook from "./steal-this-look"; + +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(() => new Set(emoji.map((e) => e.shortcode)), [emoji]); + + return ( + <> + <h1>Custom Emoji (remote)</h1> + {error && <Error error={error} />} + {isLoading ? <Loading /> : <StealThisLook emojiCodes={emojiCodes} />} + </> + ); +} diff --git a/web/source/settings/views/admin/emoji/remote/steal-this-look.tsx b/web/source/settings/views/admin/emoji/remote/steal-this-look.tsx new file mode 100644 index 000000000..43d0b83e1 --- /dev/null +++ b/web/source/settings/views/admin/emoji/remote/steal-this-look.tsx @@ -0,0 +1,235 @@ +/* + 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 StealThisLook({ 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 status: + </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/status, 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" ? "status" : "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" ? "status" : "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 }); + }} + /> + </> + ); +} |
