diff --git a/web/source/css/base.css b/web/source/css/base.css index 576c0829f..ed8d55d05 100644 --- a/web/source/css/base.css +++ b/web/source/css/base.css @@ -261,12 +261,16 @@ section.login { } section.error { + word-break: break-word; display: flex; flex-direction: row; align-items: center; + margin-bottom: 0.5rem; + span { font-size: 2em; } + pre { border: 1px solid #ff000080; margin-left: 1em; 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..4aaaa8994 --- /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 . +*/ + +"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 * 1000 + }); + + 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 = {shortcode}; + } + + return ( +
+

Add new custom emoji

+ + + Look at this new custom emoji {emojiOrShortcode} isn't it cool? + + +
+
+ + {imageInfo} + +
+ +
+ + +
+ + + +
+ ); +}; \ No newline at end of file diff --git a/web/source/settings/admin/emoji/overview.js b/web/source/settings/admin/emoji/overview.js index 5d116f15d..028276da2 100644 --- a/web/source/settings/admin/emoji/overview.js +++ b/web/source/settings/admin/emoji/overview.js @@ -18,14 +18,11 @@ "use strict"; -const Promise = require("bluebird"); const React = require("react"); const {Link} = require("wouter"); const defaultValue = require('default-value'); -const prettierBytes = require("prettier-bytes"); -const FakeToot = require("../../components/fake-toot"); -const MutateButton = require("../../components/mutation-button"); +const NewEmojiForm = require("./new-emoji"); const query = require("../../lib/query"); @@ -48,7 +45,7 @@ module.exports = function EmojiOverview() { ? "Loading..." : <> - + } @@ -99,160 +96,4 @@ function EmojiCategory({category, entries}) { ); -} - -function useFileInput({withPreview, maxSize}) { - const [file, setFile] = React.useState(); - const [imageURL, setImageURL] = React.useState(); - const [info, setInfo] = React.useState("no file selected"); - - function onChange(e) { - let file = e.target.files[0]; - setFile(file); - - URL.revokeObjectURL(imageURL); - - if (file != undefined) { - if (withPreview) { - setImageURL(URL.createObjectURL(file)); - } - - let size = prettierBytes(file.size); - if (maxSize && file.size > maxSize) { - size = {size}; - } - - setInfo(<> - {file.name} ({size}) - ); - } else { - setInfo("no file selected"); - } - } - - function reset() { - setFile(); - URL.revokeObjectURL(imageURL); - setInfo("no file selected"); - } - - return [ - onChange, - reset, - { - file, - imageURL, - info: {info}, - } - ]; -} - -// TODO: change form field code, maybe look into redux-final-form or similar -// or evaluate if we even need to put most of this in the store -function NewEmoji({emoji}) { - const emojiCodes = React.useMemo(() => { - return new Set(emoji.map((e) => e.shortcode)); - }, [emoji]); - const [addEmoji, result] = query.useAddEmojiMutation(); - const [onFileChange, resetFile, {file, imageURL, info}] = useFileInput({ - withPreview: true, - maxSize: 50 * 1000 - }); - - const [shortcode, setShortcode] = React.useState(""); - const shortcodeRef = React.useRef(null); - - function onShortChange(e) { - let input = e.target.value; - setShortcode(input); - } - - React.useEffect(() => { - if (emojiCodes.has(shortcode)) { - shortcodeRef.current.setCustomValidity("Shortcode already in use"); - } else { - shortcodeRef.current.setCustomValidity(""); - } - shortcodeRef.current.reportValidity(); - }, [shortcode, shortcodeRef, emojiCodes]); - - React.useEffect(() => { - if (shortcode.length == 0) { - if (file != undefined) { - let [name, _ext] = file.name.split("."); - setShortcode(name); - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [file]); - - function uploadEmoji(e) { - if (e) { - e.preventDefault(); - } - - Promise.try(() => { - return addEmoji({ - image: file, - shortcode - }); - }).then(() => { - resetFile(); - setShortcode(""); - }); - } - - let emojiOrShortcode = `:${shortcode}:`; - - if (imageURL != undefined) { - emojiOrShortcode = {shortcode}; - } - - return ( -
-

Add new custom emoji

- - - Look at this new custom emoji {emojiOrShortcode} isn't it cool? - - -
-
- - {info} - -
- -
- - -
- - - -
- ); } \ No newline at end of file diff --git a/web/source/settings/components/form-fields.jsx b/web/source/settings/components/form-fields.jsx index cb402c3b2..6393b1d5c 100644 --- a/web/source/settings/components/form-fields.jsx +++ b/web/source/settings/components/form-fields.jsx @@ -119,7 +119,7 @@ module.exports = { field = ( <> - + {file ? file.name : "no file selected"} {size} {/* remove */} diff --git a/web/source/settings/components/form/file.jsx b/web/source/settings/components/form/file.jsx new file mode 100644 index 000000000..7c34f3393 --- /dev/null +++ b/web/source/settings/components/form/file.jsx @@ -0,0 +1,78 @@ +/* + 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 . +*/ + +"use strict"; + +const React = require("react"); +const prettierBytes = require("prettier-bytes"); + +module.exports = function useFileInput({name, Name}, { + withPreview, + maxSize, + initialInfo = "no file selected" +}) { + const [file, setFile] = React.useState(); + const [imageURL, setImageURL] = React.useState(); + const [info, setInfo] = React.useState(); + + function onChange(e) { + let file = e.target.files[0]; + setFile(file); + + URL.revokeObjectURL(imageURL); + + if (file != undefined) { + if (withPreview) { + setImageURL(URL.createObjectURL(file)); + } + + let size = prettierBytes(file.size); + if (maxSize && file.size > maxSize) { + size = {size}; + } + + setInfo(<> + {file.name} ({size}) + ); + } else { + setInfo(); + } + } + + function reset() { + URL.revokeObjectURL(imageURL); + setImageURL(); + setFile(); + setInfo(); + } + + return [ + onChange, + reset, + { + [name]: file, + [`${name}URL`]: imageURL, + [`${name}Info`]: + {info + ? info + : initialInfo + } + + } + ]; +}; \ No newline at end of file diff --git a/web/source/settings/components/form/index.js b/web/source/settings/components/form/index.js new file mode 100644 index 000000000..5edc52364 --- /dev/null +++ b/web/source/settings/components/form/index.js @@ -0,0 +1,36 @@ +/* + 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 . +*/ + +"use strict"; + +function capitalizeFirst(str) { + return str.slice(0,1).toUpperCase()+str.slice(1); +} + +function makeHook(func) { + return (name, ...args) => func({ + name, + Name: capitalizeFirst(name) + }, + ...args); +} + +module.exports = { + useTextInput: makeHook(require("./text")), + useFileInput: makeHook(require("./file")) +}; \ No newline at end of file diff --git a/web/source/settings/components/form/text.jsx b/web/source/settings/components/form/text.jsx new file mode 100644 index 000000000..a392239dc --- /dev/null +++ b/web/source/settings/components/form/text.jsx @@ -0,0 +1,56 @@ +/* + 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 . +*/ + +"use strict"; + +const React = require("react"); + +module.exports = function useTextInput({name, Name}, {validator} = {}) { + const [text, setText] = React.useState(""); + const textRef = React.useRef(null); + + function onChange(e) { + let input = e.target.value; + setText(input); + + if (validator) { + validator(input); + } + } + + function reset() { + setText(""); + } + + React.useEffect(() => { + if (validator) { + textRef.current.setCustomValidity(validator(text)); + textRef.current.reportValidity(); + } + }, [text, textRef, validator]); + + return [ + onChange, + reset, + { + [name]: text, + [`${name}Ref`]: textRef, + [`set${Name}`]: setText + } + ]; +}; \ No newline at end of file diff --git a/web/source/settings/components/mutation-button.jsx b/web/source/settings/components/mutation-button.jsx index f79bab2d2..9a8c9d089 100644 --- a/web/source/settings/components/mutation-button.jsx +++ b/web/source/settings/components/mutation-button.jsx @@ -29,7 +29,7 @@ module.exports = function MutateButton({text, result}) { return (
{result.error && -
{result.error.status}: {result.error.error}
+
{result.error.status}: {result.error.data.error}
} div { flex-direction: column; justify-content: center; - div.form-field { - width: 100%; - display: flex; - - span { - flex: 1 1 auto; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - padding: 0.3rem 0; - } - } - h3 { margin-top: 0; margin-bottom: 0.5rem; @@ -369,6 +356,20 @@ section.with-sidebar > div { font-weight: bold; } +.form-field.file { + width: 100%; + display: flex; +} + + +span.form-info { + flex: 1 1 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 0.3rem 0; +} + .list { display: flex; flex-direction: column;