gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

parse-from-toot.js (6031B)


      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 
     24 const query = require("../../../lib/query");
     25 
     26 const {
     27 	useTextInput,
     28 	useComboBoxInput,
     29 	useCheckListInput
     30 } = require("../../../lib/form");
     31 
     32 const useFormSubmit = require("../../../lib/form/submit");
     33 
     34 const CheckList = require("../../../components/check-list");
     35 const { CategorySelect } = require('../category-select');
     36 
     37 const { TextInput } = require("../../../components/form/inputs");
     38 const MutationButton = require("../../../components/form/mutation-button");
     39 const { Error } = require("../../../components/error");
     40 
     41 module.exports = function ParseFromToot({ emojiCodes }) {
     42 	const [searchStatus, result] = query.useSearchStatusForEmojiMutation();
     43 
     44 	const [onURLChange, _resetURL, { url }] = useTextInput("url");
     45 
     46 	function submitSearch(e) {
     47 		e.preventDefault();
     48 		if (url.trim().length != 0) {
     49 			searchStatus(url);
     50 		}
     51 	}
     52 
     53 	return (
     54 		<div className="parse-emoji">
     55 			<h2>Steal this look</h2>
     56 			<form onSubmit={submitSearch}>
     57 				<div className="form-field text">
     58 					<label htmlFor="url">
     59 						Link to a toot:
     60 					</label>
     61 					<div className="row">
     62 						<input
     63 							type="text"
     64 							id="url"
     65 							name="url"
     66 							onChange={onURLChange}
     67 							value={url}
     68 						/>
     69 						<button disabled={result.isLoading}>
     70 							<i className={[
     71 								"fa fa-fw",
     72 								(result.isLoading
     73 									? "fa-refresh fa-spin"
     74 									: "fa-search")
     75 							].join(" ")} aria-hidden="true" title="Search" />
     76 							<span className="sr-only">Search</span>
     77 						</button>
     78 					</div>
     79 				</div>
     80 			</form>
     81 			<SearchResult result={result} localEmojiCodes={emojiCodes} />
     82 		</div>
     83 	);
     84 };
     85 
     86 function SearchResult({ result, localEmojiCodes }) {
     87 	const { error, data, isSuccess, isError } = result;
     88 
     89 	if (!(isSuccess || isError)) {
     90 		return null;
     91 	}
     92 
     93 	if (error == "NONE_FOUND") {
     94 		return "No results found";
     95 	} else if (error == "LOCAL_INSTANCE") {
     96 		return <b>This is a local user/toot, all referenced emoji are already on your instance</b>;
     97 	} else if (error != undefined) {
     98 		return <Error error={result.error} />;
     99 	}
    100 
    101 	if (data.list.length == 0) {
    102 		return <b>This {data.type == "statuses" ? "toot" : "account"} doesn't use any custom emoji</b>;
    103 	}
    104 
    105 	return (
    106 		<CopyEmojiForm
    107 			localEmojiCodes={localEmojiCodes}
    108 			type={data.type}
    109 			domain={data.domain}
    110 			emojiList={data.list}
    111 		/>
    112 	);
    113 }
    114 
    115 function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
    116 	const form = {
    117 		selectedEmoji: useCheckListInput("selectedEmoji", {
    118 			entries: emojiList,
    119 			uniqueKey: "id"
    120 		}),
    121 		category: useComboBoxInput("category")
    122 	};
    123 
    124 	const [formSubmit, result] = useFormSubmit(
    125 		form,
    126 		query.usePatchRemoteEmojisMutation(),
    127 		{
    128 			changedOnly: false,
    129 			onFinish: ({ data }) => {
    130 				if (data != undefined) {
    131 					form.selectedEmoji.updateMultiple(
    132 						// uncheck all successfully processed emoji
    133 						data.map(([id]) => [id, { checked: false }])
    134 					);
    135 				}
    136 			}
    137 		}
    138 	);
    139 
    140 	const buttonsInactive = form.selectedEmoji.someSelected
    141 		? {}
    142 		: {
    143 			disabled: true,
    144 			title: "No emoji selected, cannot perform any actions"
    145 		};
    146 
    147 	const checkListExtraProps = React.useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
    148 
    149 	return (
    150 		<div className="parsed">
    151 			<span>This {type == "statuses" ? "toot" : "account"} uses the following custom emoji, select the ones you want to copy/disable:</span>
    152 			<form onSubmit={formSubmit}>
    153 				<CheckList
    154 					field={form.selectedEmoji}
    155 					EntryComponent={EmojiEntry}
    156 					getExtraProps={checkListExtraProps}
    157 				/>
    158 
    159 				<CategorySelect
    160 					field={form.category}
    161 				/>
    162 
    163 				<div className="action-buttons row">
    164 					<MutationButton name="copy" label="Copy to local emoji" result={result} showError={false} {...buttonsInactive} />
    165 					<MutationButton name="disable" label="Disable" result={result} className="button danger" showError={false} {...buttonsInactive} />
    166 				</div>
    167 				{result.error && (
    168 					Array.isArray(result.error)
    169 						? <ErrorList errors={result.error} />
    170 						: <Error error={result.error} />
    171 				)}
    172 			</form>
    173 		</div>
    174 	);
    175 }
    176 
    177 function ErrorList({ errors }) {
    178 	return (
    179 		<div className="error">
    180 			One or multiple emoji failed to process:
    181 			{errors.map(([shortcode, err]) => (
    182 				<div key={shortcode}>
    183 					<b>{shortcode}:</b> {err}
    184 				</div>
    185 			))}
    186 		</div>
    187 	);
    188 }
    189 
    190 function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } }) {
    191 	const shortcodeField = useTextInput("shortcode", {
    192 		defaultValue: emoji.shortcode,
    193 		validator: function validateShortcode(code) {
    194 			return (emoji.checked && localEmojiCodes.has(code))
    195 				? "Shortcode already in use"
    196 				: "";
    197 		}
    198 	});
    199 
    200 	React.useEffect(() => {
    201 		if (emoji.valid != shortcodeField.valid) {
    202 			onChange({ valid: shortcodeField.valid });
    203 		}
    204 	}, [onChange, emoji.valid, shortcodeField.valid]);
    205 
    206 	React.useEffect(() => {
    207 		shortcodeField.validate();
    208 		// only need this update if it's the emoji.checked that updated, not shortcodeField
    209 		// eslint-disable-next-line react-hooks/exhaustive-deps
    210 	}, [emoji.checked]);
    211 
    212 	return (
    213 		<>
    214 			<img className="emoji" src={emoji.url} title={emoji.shortcode} />
    215 
    216 			<TextInput
    217 				field={shortcodeField}
    218 				onChange={(e) => {
    219 					shortcodeField.onChange(e);
    220 					onChange({ shortcode: e.target.value, checked: true });
    221 				}}
    222 			/>
    223 		</>
    224 	);
    225 }