From 7a1e6394831fb07e303c5ed0900dfe1ea4820de5 Mon Sep 17 00:00:00 2001
From: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Wed, 24 Apr 2024 12:12:47 +0200
Subject: [chore] Refactor settings panel routing (and other fixes) (#2864)
---
.../settings/views/admin/emoji/local/detail.tsx | 142 +++++++++++++++++
.../settings/views/admin/emoji/local/new-emoji.tsx | 112 +++++++++++++
.../settings/views/admin/emoji/local/overview.tsx | 173 +++++++++++++++++++++
.../views/admin/emoji/local/use-shortcode.ts | 56 +++++++
4 files changed, 483 insertions(+)
create mode 100644 web/source/settings/views/admin/emoji/local/detail.tsx
create mode 100644 web/source/settings/views/admin/emoji/local/new-emoji.tsx
create mode 100644 web/source/settings/views/admin/emoji/local/overview.tsx
create mode 100644 web/source/settings/views/admin/emoji/local/use-shortcode.ts
(limited to 'web/source/settings/views/admin/emoji/local')
diff --git a/web/source/settings/views/admin/emoji/local/detail.tsx b/web/source/settings/views/admin/emoji/local/detail.tsx
new file mode 100644
index 000000000..2913b6c17
--- /dev/null
+++ b/web/source/settings/views/admin/emoji/local/detail.tsx
@@ -0,0 +1,142 @@
+/*
+ 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 .
+*/
+
+import React, { useEffect } from "react";
+import { Redirect, useParams } from "wouter";
+import { useComboBoxInput, useFileInput, useValue } from "../../../../lib/form";
+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";
+import { CategorySelect } from "../category-select";
+import BackButton from "../../../../components/back-button";
+
+export default function EmojiDetail() {
+ const baseUrl = useBaseUrl();
+ const params = useParams();
+ return (
+
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/web/source/settings/views/admin/emoji/local/new-emoji.tsx b/web/source/settings/views/admin/emoji/local/new-emoji.tsx
new file mode 100644
index 000000000..73e846f16
--- /dev/null
+++ b/web/source/settings/views/admin/emoji/local/new-emoji.tsx
@@ -0,0 +1,112 @@
+/*
+ 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 .
+*/
+
+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 = ;
+ } else if (shortcode.value !== undefined && shortcode.value.length > 0) {
+ emojiOrShortcode = `:${shortcode.value}:`;
+ } else {
+ emojiOrShortcode = `:your_emoji_here:`;
+ }
+
+ return (
+
+
Add new custom emoji
+
+
+ Look at this new custom emoji {emojiOrShortcode} isn't it cool?
+
+
+
+
+ );
+}
diff --git a/web/source/settings/views/admin/emoji/local/overview.tsx b/web/source/settings/views/admin/emoji/local/overview.tsx
new file mode 100644
index 000000000..b28af59f3
--- /dev/null
+++ b/web/source/settings/views/admin/emoji/local/overview.tsx
@@ -0,0 +1,173 @@
+/*
+ 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 .
+*/
+
+import React, { useMemo, useState } from "react";
+import { Link } from "wouter";
+import { matchSorter } from "match-sorter";
+import NewEmojiForm from "./new-emoji";
+import { useTextInput } from "../../../../lib/form";
+import { useEmojiByCategory } from "../category-select";
+import Loading from "../../../../components/loading";
+import { Error } from "../../../../components/error";
+import { TextInput } from "../../../../components/form/inputs";
+import { useListEmojiQuery } from "../../../../lib/query/admin/custom-emoji";
+import { CustomEmoji } from "../../../../lib/types/custom-emoji";
+
+export function EmojiOverview() {
+ const { data: emoji = [], isLoading, isError, error } = useListEmojiQuery({ filter: "domain:local" });
+
+ let content: React.JSX.Element;
+ if (isLoading) {
+ content = ;
+ } else if (isError) {
+ content = ;
+ } else {
+ content = (
+ <>
+
+
+ >
+ );
+ }
+
+ return (
+ <>
+
Local Custom Emoji
+
+ 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 Remote Emoji page.
+
+
+ Be warned! 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).
+
+ {content}
+ >
+ );
+}
+
+interface EmojiListParams {
+ emoji: CustomEmoji[];
+}
+
+function EmojiList({ emoji }: EmojiListParams) {
+ 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 { filteredEmojis, filteredCount } = useMemo(() => {
+ // Amount of emojis removed by the filter.
+ // Start with the length of the array since
+ // that's the max that can be filtered out.
+ let filteredCount = emoji.length;
+
+ // Results of the filtering.
+ const filteredEmojis: [string, CustomEmoji[]][] = [];
+
+ // Filter from emojis in this category.
+ emojiByCategory.forEach((entries, category) => {
+ const filteredEntries = matchSorter(entries, filter, {
+ keys: ["shortcode"]
+ });
+
+ if (filteredEntries.length == 0) {
+ // Nothing left in this category, don't
+ // bother adding it to filteredEmojis.
+ return;
+ }
+
+ filteredCount -= filteredEntries.length;
+ filteredEmojis.push([category, filteredEntries]);
+ });
+
+ return { filteredEmojis, filteredCount };
+ }, [filter, emojiByCategory, emoji.length]);
+
+ return (
+ <>
+
Overview
+ {emoji.length > 0
+ ? {emoji.length} custom emoji {filteredCount > 0 && `(${filteredCount} filtered)`}
+ : No custom emoji yet, you can add one below.
+ }
+
+ );
+}
+
+function EmojiPreview({ emoji }) {
+ const [ animate, setAnimate ] = useState(false);
+
+ return (
+ { setAnimate(true); }}
+ onMouseLeave={() => { setAnimate(false); }}
+ src={animate ? emoji.url : emoji.static_url}
+ alt={emoji.shortcode}
+ title={emoji.shortcode}
+ loading="lazy"
+ />
+ );
+}
diff --git a/web/source/settings/views/admin/emoji/local/use-shortcode.ts b/web/source/settings/views/admin/emoji/local/use-shortcode.ts
new file mode 100644
index 000000000..358e711b0
--- /dev/null
+++ b/web/source/settings/views/admin/emoji/local/use-shortcode.ts
@@ -0,0 +1,56 @@
+/*
+ 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 .
+*/
+
+import { useMemo } from "react";
+
+import { useTextInput } from "../../../../lib/form";
+import { useListEmojiQuery } from "../../../../lib/query/admin/custom-emoji";
+
+const shortcodeRegex = /^\w{2,30}$/;
+
+export default function useShortcode() {
+ const { data: emoji = [] } = useListEmojiQuery({
+ filter: "domain:local"
+ });
+
+ const emojiCodes = 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 "";
+ }
+ });
+}
--
cgit v1.3