diff options
Diffstat (limited to 'web/source/settings/admin')
| -rw-r--r-- | web/source/settings/admin/actions.js | 1 | ||||
| -rw-r--r-- | web/source/settings/admin/emoji.js | 229 | ||||
| -rw-r--r-- | web/source/settings/admin/emoji/detail.js | 86 | ||||
| -rw-r--r-- | web/source/settings/admin/emoji/index.js | 40 | ||||
| -rw-r--r-- | web/source/settings/admin/emoji/new-emoji.js | 133 | ||||
| -rw-r--r-- | web/source/settings/admin/emoji/overview.js | 99 | ||||
| -rw-r--r-- | web/source/settings/admin/federation.js | 4 | ||||
| -rw-r--r-- | web/source/settings/admin/settings.js | 3 | 
8 files changed, 361 insertions, 234 deletions
| diff --git a/web/source/settings/admin/actions.js b/web/source/settings/admin/actions.js index d4980d021..915b8aee1 100644 --- a/web/source/settings/admin/actions.js +++ b/web/source/settings/admin/actions.js @@ -18,7 +18,6 @@  "use strict"; -const Promise = require("bluebird");  const React = require("react");  const Redux = require("react-redux"); diff --git a/web/source/settings/admin/emoji.js b/web/source/settings/admin/emoji.js deleted file mode 100644 index ad7fcab06..000000000 --- a/web/source/settings/admin/emoji.js +++ /dev/null @@ -1,229 +0,0 @@ -/* -	GoToSocial -	Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org - -	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/>. -*/ - -"use strict"; - -const Promise = require("bluebird"); -const React = require("react"); -const Redux = require("react-redux"); -const {Switch, Route, Link, Redirect, useRoute, useLocation} = require("wouter"); - -const Submit = require("../components/submit"); -const FakeToot = require("../components/fake-toot"); -const { formFields } = require("../components/form-fields"); - -const api = require("../lib/api"); -const adminActions = require("../redux/reducers/admin").actions; -const submit = require("../lib/submit"); -const BackButton = require("../components/back-button"); - -const base = "/settings/admin/custom-emoji"; - -module.exports = function CustomEmoji() { -	const dispatch = Redux.useDispatch(); -	const [loaded, setLoaded] = React.useState(false); - -	const [errorMsg, setError] = React.useState(""); - -	React.useEffect(() => { -		if (!loaded) { -			Promise.try(() => { -				return dispatch(api.admin.fetchCustomEmoji()); -			}).then(() => { -				setLoaded(true); -			}).catch((e) => { -				setLoaded(true); -				setError(e.message); -			}); -		} -	}, []); - -	if (!loaded) { -		return ( -			<> -				<h1>Custom Emoji</h1> -				Loading... -			</> -		); -	} - -	return ( -		<> -			{errorMsg.length > 0 &&  -				<div className="error accent">{errorMsg}</div> -			} -			<Switch> -				<Route path={`${base}/:emojiId`}> -					<EmojiDetailWrapped /> -				</Route> -				<EmojiOverview /> -			</Switch> -		</> -	); -}; - -function EmojiOverview() { -	return ( -		<> -			<h1>Custom Emoji</h1> -			<EmojiList/> -			<NewEmoji/> -		</> -	); -} - -const NewEmojiForm = formFields(adminActions.updateNewEmojiVal, (state) => state.admin.newEmoji); -function NewEmoji() { -	const dispatch = Redux.useDispatch(); -	const newEmojiForm = Redux.useSelector((state) => state.admin.newEmoji); - -	const [errorMsg, setError] = React.useState(""); -	const [statusMsg, setStatus] = React.useState(""); - -	const uploadEmoji = submit( -		() => dispatch(api.admin.newEmoji()), -		{ -			setStatus, setError, -			onSuccess: function() { -				URL.revokeObjectURL(newEmojiForm.image); -				return Promise.all([ -					dispatch(adminActions.updateNewEmojiVal(["image", undefined])), -					dispatch(adminActions.updateNewEmojiVal(["imageFile", undefined])), -					dispatch(adminActions.updateNewEmojiVal(["shortcode", ""])), -				]); -			} -		} -	); - -	React.useEffect(() => { -		if (newEmojiForm.shortcode.length == 0) { -			if (newEmojiForm.imageFile != undefined) { -				let [name, ext] = newEmojiForm.imageFile.name.split("."); -				dispatch(adminActions.updateNewEmojiVal(["shortcode", name])); -			} -		} -	}); - -	let emojiOrShortcode = `:${newEmojiForm.shortcode}:`; - -	if (newEmojiForm.image != undefined) { -		emojiOrShortcode = <img -			className="emoji" -			src={newEmojiForm.image} -			title={`:${newEmojiForm.shortcode}:`} -			alt={newEmojiForm.shortcode} -		/>; -	} - -	return ( -		<div> -			<h2>Add new custom emoji</h2> - -			<FakeToot> -				Look at this new custom emoji {emojiOrShortcode} isn't it cool? -			</FakeToot> - -			<NewEmojiForm.File -				id="image" -				name="Image" -				fileType="image/png,image/gif" -				showSize={true} -				maxSize={50 * 1000} -			/> - -			<NewEmojiForm.TextInput -				id="shortcode" -				name="Shortcode (without : :), must be unique on the instance" -				placeHolder="blobcat" -			/> - -			<Submit onClick={uploadEmoji} label="Upload" errorMsg={errorMsg} statusMsg={statusMsg} /> -		</div> -	); -} - -function EmojiList() { -	const emoji = Redux.useSelector((state) => state.admin.emoji); - -	return ( -		<div> -			<h2>Overview</h2> -			<div className="list emoji-list"> -				{Object.entries(emoji).map(([category, entries]) => { -					return <EmojiCategory key={category} category={category} entries={entries}/>; -				})} -			</div> -		</div> -	); -} - -function EmojiCategory({category, entries}) { -	return ( -		<div className="entry"> -			<b>{category}</b> -			<div className="emoji-group"> -				{entries.map((e) => { -					return ( -						<Link key={e.id} to={`${base}/${e.id}`}> -							{/* <Link key={e.static_url} to={`${base}`}> */} -							<a> -								<img src={e.url} alt={e.shortcode} title={`:${e.shortcode}:`}/> -							</a> -						</Link> -					); -				})} -			</div> -		</div> -	); -} - -function EmojiDetailWrapped() { -	/* We wrap the component to generate formFields with a setter depending on the domain -		 if formFields() is used inside the same component that is re-rendered with their state, -		 inputs get re-created on every change, causing them to lose focus, and bad performance -	*/ -	let [_match, {emojiId}] = useRoute(`${base}/:emojiId`); -	const emojiById = Redux.useSelector((state) => state.admin.emojiById); -	const emoji = emojiById[emojiId]; -	if (emoji == undefined) { -		return ( -			<h1><BackButton to={base}/> Custom Emoji: </h1> -		); -	} - -	function alterEmoji([key, val]) { -		return adminActions.updateDomainBlockVal([emojiId, key, val]); -	} - -	const fields = formFields(alterEmoji, (state) => state.admin.blockedInstances[emojiId]); - -	return <EmojiDetail emoji={emoji} Form={fields} />; -} - -function EmojiDetail({emoji, Form}) { -	return ( -		<div> -			<h1><BackButton to={base}/> Custom Emoji: {emoji.shortcode}</h1> -			<p> -				Editing custom emoji isn't implemented yet.<br/> -				<a target="_blank" rel="noreferrer" href="https://github.com/superseriousbusiness/gotosocial/issues/797">View implementation progress.</a> -			</p> -			<img src={emoji.url} alt={emoji.shortcode} title={`:${emoji.shortcode}:`}/> -		</div> -	); -}
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/detail.js b/web/source/settings/admin/emoji/detail.js new file mode 100644 index 000000000..cc0f8e73c --- /dev/null +++ b/web/source/settings/admin/emoji/detail.js @@ -0,0 +1,86 @@ +/* +	GoToSocial +	Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +	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/>. +*/ + +"use strict"; + +const React = require("react"); + +const { useRoute, Link, Redirect } = require("wouter"); + +const BackButton = require("../../components/back-button"); + +const query = require("../../lib/query"); + +const base = "/settings/admin/custom-emoji"; + +/* We wrap the component to generate formFields with a setter depending on the domain +	 if formFields() is used inside the same component that is re-rendered with their state, +	 inputs get re-created on every change, causing them to lose focus, and bad performance +*/ +module.exports = function EmojiDetailWrapped() { +	let [_match, {emojiId}] = useRoute(`${base}/:emojiId`); +	const {currentData: emoji, isLoading, error} = query.useGetEmojiQuery(emojiId); + +	return (<> +		{error && <div className="error accent">{error.status}: {error.data.error}</div>} +		{isLoading +			? "Loading..." +			: <EmojiDetail emoji={emoji}/> +		} +	</>); +}; + +function EmojiDetail({emoji}) { +	if (emoji == undefined) { +		return (<> +			<Link to={base}> +				<a className="button">go back</a> +			</Link> +		</>); +	} + +	return ( +		<div> +			<h1><BackButton to={base}/> Custom Emoji: {emoji.shortcode}</h1> +			<DeleteButton id={emoji.id}/> +			<p> +				Editing custom emoji isn't implemented yet.<br/> +				<a target="_blank" rel="noreferrer" href="https://github.com/superseriousbusiness/gotosocial/issues/797">View implementation progress.</a> +			</p> +			<img src={emoji.url} alt={emoji.shortcode} title={`:${emoji.shortcode}:`}/> +		</div> +	); +} + +function DeleteButton({id}) { +	// TODO: confirmation dialog? +	const [deleteEmoji, deleteResult] = query.useDeleteEmojiMutation(); + +	let text = "Delete this emoji"; +	if (deleteResult.isLoading) { +		text = "processing..."; +	} + +	if (deleteResult.isSuccess) { +		return <Redirect to={base}/>; +	} + +	return ( +		<button onClick={() => deleteEmoji(id)} disabled={deleteResult.isLoading}>{text}</button> +	); +}
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/index.js b/web/source/settings/admin/emoji/index.js new file mode 100644 index 000000000..0fcda8264 --- /dev/null +++ b/web/source/settings/admin/emoji/index.js @@ -0,0 +1,40 @@ +/* +	GoToSocial +	Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +	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/>. +*/ + +"use strict"; + +const React = require("react"); +const {Switch, Route} = require("wouter"); + +const EmojiOverview = require("./overview"); +const EmojiDetail = require("./detail"); + +const base = "/settings/admin/custom-emoji"; + +module.exports = function CustomEmoji() { +	return ( +		<> +			<Switch> +				<Route path={`${base}/:emojiId`}> +					<EmojiDetail /> +				</Route> +				<EmojiOverview /> +			</Switch> +		</> +	); +}; diff --git a/web/source/settings/admin/emoji/new-emoji.js b/web/source/settings/admin/emoji/new-emoji.js new file mode 100644 index 000000000..e5bc8893d --- /dev/null +++ b/web/source/settings/admin/emoji/new-emoji.js @@ -0,0 +1,133 @@ +/* +	GoToSocial +	Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +	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/>. +*/ + +"use strict"; + +const Promise = require('bluebird'); +const React = require("react"); + +const FakeToot = require("../../components/fake-toot"); +const MutateButton = require("../../components/mutation-button"); + +const {  +	useTextInput, +	useFileInput +} = require("../../components/form"); + +const query = require("../../lib/query"); + +module.exports = function NewEmojiForm({emoji}) { +	const emojiCodes = React.useMemo(() => { +		return new Set(emoji.map((e) => e.shortcode)); +	}, [emoji]); + +	const [addEmoji, result] = query.useAddEmojiMutation(); + +	const [onFileChange, resetFile, {image, imageURL, imageInfo}] = useFileInput("image", { +		withPreview: true, +		maxSize: 50 * 1024 +	}); + +	const [onShortcodeChange, resetShortcode, {shortcode, setShortcode, shortcodeRef}] = useTextInput("shortcode", { +		validator: function validateShortcode(code) { +			return emojiCodes.has(code) +				? "Shortcode already in use" +				: ""; +		} +	}); + +	React.useEffect(() => { +		if (shortcode.length == 0) { +			if (image != undefined) { +				let [name, _ext] = image.name.split("."); +				setShortcode(name); +			} +		} +		// eslint-disable-next-line react-hooks/exhaustive-deps +	}, [image]); + +	function uploadEmoji(e) { +		if (e) { +			e.preventDefault(); +		} + +		Promise.try(() => { +			return addEmoji({ +				image, +				shortcode +			}); +		}).then(() => { +			resetFile(); +			resetShortcode(); +		}); +	} + +	let emojiOrShortcode = `:${shortcode}:`; + +	if (imageURL != undefined) { +		emojiOrShortcode = <img +			className="emoji" +			src={imageURL} +			title={`:${shortcode}:`} +			alt={shortcode} +		/>; +	} + +	return ( +		<div> +			<h2>Add new custom emoji</h2> + +			<FakeToot> +				Look at this new custom emoji {emojiOrShortcode} isn't it cool? +			</FakeToot> + +			<form onSubmit={uploadEmoji} className="form-flex"> +				<div className="form-field file"> +					<label className="file-input button" htmlFor="image"> +						Browse +					</label> +					{imageInfo} +					<input +						className="hidden" +						type="file" +						id="image" +						name="Image" +						accept="image/png,image/gif" +						onChange={onFileChange} +					/> +				</div> + +				<div className="form-field text"> +					<label htmlFor="shortcode"> +						Shortcode, must be unique among the instance's local emoji +					</label> +					<input +						type="text" +						id="shortcode" +						name="Shortcode" +						ref={shortcodeRef} +						onChange={onShortcodeChange} +						value={shortcode} +					/> +				</div> +				 +				<MutateButton text="Upload emoji" result={result}/> +			</form> +		</div> +	); +};
\ No newline at end of file diff --git a/web/source/settings/admin/emoji/overview.js b/web/source/settings/admin/emoji/overview.js new file mode 100644 index 000000000..028276da2 --- /dev/null +++ b/web/source/settings/admin/emoji/overview.js @@ -0,0 +1,99 @@ +/* +	 GoToSocial +	 Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + +	 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/>. +*/ + +"use strict"; + +const React = require("react"); +const {Link} = require("wouter"); +const defaultValue = require('default-value'); + +const NewEmojiForm = require("./new-emoji"); + +const query = require("../../lib/query"); + +const base = "/settings/admin/custom-emoji"; + +module.exports = function EmojiOverview() { +	const { +		data: emoji, +		isLoading, +		error +	} = query.useGetAllEmojiQuery({filter: "domain:local"}); + +	return ( +		<> +			<h1>Custom Emoji</h1> +			{error &&  +				<div className="error accent">{error}</div> +			} +			{isLoading +				? "Loading..." +				: <> +					<EmojiList emoji={emoji}/> +					<NewEmojiForm emoji={emoji}/> +				</> +			} +		</> +	); +}; + +function EmojiList({emoji}) { +	const byCategory = React.useMemo(() => { +		const categories = {}; + +		emoji.forEach((emoji) => { +			let cat = defaultValue(emoji.category, "Unsorted"); +			categories[cat] = defaultValue(categories[cat], []); +			categories[cat].push(emoji); +		}); + +		return categories; +	}, [emoji]); +	 +	return ( +		<div> +			<h2>Overview</h2> +			<div className="list emoji-list"> +				{emoji.length == 0 && "No local emoji yet"} +				{Object.entries(byCategory).map(([category, entries]) => { +					return <EmojiCategory key={category} category={category} entries={entries}/>; +				})} +			</div> +		</div> +	); +} + +function EmojiCategory({category, entries}) { +	return ( +		<div className="entry"> +			<b>{category}</b> +			<div className="emoji-group"> +				{entries.map((e) => { +					return ( +						<Link key={e.id} to={`${base}/${e.id}`}> +							{/* <Link key={e.static_url} to={`${base}`}> */} +							<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/federation.js b/web/source/settings/admin/federation.js index 99f10e69e..449d2156f 100644 --- a/web/source/settings/admin/federation.js +++ b/web/source/settings/admin/federation.js @@ -50,7 +50,7 @@ module.exports = function AdminSettings() {  				return dispatch(api.admin.fetchDomainBlocks());  			});  		} -	}, []); +	}, [dispatch, loadedBlockedInstances]);  	if (!loadedBlockedInstances) {  		return ( @@ -315,7 +315,7 @@ function InstancePage({domain, Form}) {  		if (entry == undefined) {  			dispatch(api.admin.getEditableDomainBlock(domain));  		} -	}, []); +	}, [dispatch, domain, entry]);  	const [errorMsg, setError] = React.useState("");  	const [statusMsg, setStatus] = React.useState(""); diff --git a/web/source/settings/admin/settings.js b/web/source/settings/admin/settings.js index 845a1f924..eb6e8b267 100644 --- a/web/source/settings/admin/settings.js +++ b/web/source/settings/admin/settings.js @@ -18,7 +18,6 @@  "use strict"; -const Promise = require("bluebird");  const React = require("react");  const Redux = require("react-redux"); @@ -32,7 +31,7 @@ const adminActions = require("../redux/reducers/instances").actions;  const {  	TextInput,  	TextArea, -	File +	_File  } = require("../components/form-fields").formFields(adminActions.setAdminSettingsVal, (state) => state.instances.adminSettings);  module.exports = function AdminSettings() { | 
