Compare commits

...

4 commits

6 changed files with 142 additions and 94 deletions

View file

@ -148,15 +148,15 @@ type GTSCaches struct {
// Tag provides access to the gtsmodel Tag database cache.
Tag StructCache[*gtsmodel.Tag]
// ThreadMute provides access to the gtsmodel ThreadMute database cache.
ThreadMute StructCache[*gtsmodel.ThreadMute]
// Token provides access to the gtsmodel Token database cache.
Token StructCache[*gtsmodel.Token]
// Tombstone provides access to the gtsmodel Tombstone database cache.
Tombstone StructCache[*gtsmodel.Tombstone]
// ThreadMute provides access to the gtsmodel ThreadMute database cache.
ThreadMute StructCache[*gtsmodel.ThreadMute]
// User provides access to the gtsmodel User database cache.
User StructCache[*gtsmodel.User]

View file

@ -172,6 +172,8 @@ func totalOfRatios() float64 {
return 0 +
config.GetCacheAccountMemRatio() +
config.GetCacheAccountNoteMemRatio() +
config.GetCacheAccountSettingsMemRatio() +
config.GetCacheAccountStatsMemRatio() +
config.GetCacheApplicationMemRatio() +
config.GetCacheBlockMemRatio() +
config.GetCacheBlockIDsMemRatio() +
@ -179,17 +181,21 @@ func totalOfRatios() float64 {
config.GetCacheClientMemRatio() +
config.GetCacheEmojiMemRatio() +
config.GetCacheEmojiCategoryMemRatio() +
config.GetCacheFilterMemRatio() +
config.GetCacheFilterKeywordMemRatio() +
config.GetCacheFilterStatusMemRatio() +
config.GetCacheFollowMemRatio() +
config.GetCacheFollowIDsMemRatio() +
config.GetCacheFollowRequestMemRatio() +
config.GetCacheFollowRequestIDsMemRatio() +
config.GetCacheInReplyToIDsMemRatio() +
config.GetCacheInstanceMemRatio() +
config.GetCacheInReplyToIDsMemRatio() +
config.GetCacheListMemRatio() +
config.GetCacheListEntryMemRatio() +
config.GetCacheMarkerMemRatio() +
config.GetCacheMediaMemRatio() +
config.GetCacheMentionMemRatio() +
config.GetCacheMoveMemRatio() +
config.GetCacheNotificationMemRatio() +
config.GetCachePollMemRatio() +
config.GetCachePollVoteMemRatio() +

View file

@ -17,7 +17,9 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { SerializedError } from "@reduxjs/toolkit";
import { FetchBaseQueryError } from "@reduxjs/toolkit/query";
import React, { ReactNode } from "react";
function ErrorFallback({ error, resetErrorBoundary }) {
return (
@ -44,39 +46,59 @@ function ErrorFallback({ error, resetErrorBoundary }) {
);
}
function Error({ error }) {
/* eslint-disable-next-line no-console */
console.error("Rendering error:", error);
let message;
interface GtsError {
error: string;
error_description?: string;
}
if (error.data != undefined) { // RTK Query error with data
if (error.status) {
message = (<>
<b>{error.status}:</b> {error.data.error}
{error.data.error_description &&
<p>
{error.data.error_description}
</p>
}
</>);
} else {
message = error.data.error;
}
} else if (error.name != undefined || error.type != undefined) { // JS error
message = (<>
<b>{error.type && error.name}:</b> {error.message}
</>);
} else if (error.status && typeof error.error == "string") {
message = (<>
<b>{error.status}:</b> {error.error}
</>);
interface ErrorProps {
error: FetchBaseQueryError | SerializedError | Error | undefined;
reset?: () => void;
}
function Error({ error, reset }: ErrorProps) {
if (error === undefined) {
return null;
}
/* eslint-disable-next-line no-console */
console.error("caught error: ", error);
let message: ReactNode;
if ("status" in error) {
// RTK Query error with data.
const gtsError = error.data as GtsError;
const errMsg = gtsError.error_description ?? gtsError.error;
message = <>Code {error.status} {errMsg}</> ;
} else {
message = error.message ?? error;
// SerializedError or Error.
const errMsg = JSON.stringify(error);
message = (
<>
<b>{error.name}:</b> {errMsg}
</>
);
}
let className = "error";
if (reset) {
className += " with-dismiss";
}
return (
<div className="error">
{message}
<div className={className}>
<span>{message}</span>
{ reset &&
<span
className="dismiss"
onClick={reset}
role="button"
tabIndex={0}
>
<span>Dismiss</span>
<i className="fa fa-fw fa-close" aria-hidden="true" />
</span>
}
</div>
);
}

View file

@ -51,9 +51,9 @@ export default function MutationButton({
}
return (
<div className={wrapperClassName}>
<div className={wrapperClassName ? wrapperClassName : "mutation-button"}>
{(showError && targetsThisButton && result.error) &&
<Error error={result.error} />
<Error error={result.error} reset={result.reset} />
}
<button
type="submit"

View file

@ -257,33 +257,33 @@ input, select, textarea {
overflow: auto;
margin: 0;
}
&.with-dismiss {
display: flex;
gap: 1rem;
justify-content: space-between;
align-items: center;
.dismiss {
display: flex;
flex-shrink: 0;
align-items: center;
align-self: stretch;
gap: 0.25rem;
}
}
}
.mutation-button {
display: flex;
flex-direction: column;
gap: 1rem;
}
.hidden {
display: none;
}
.messagebutton, .messagebutton > div {
display: flex;
align-items: center;
flex-wrap: wrap;
div.padded {
margin-left: 1rem;
}
button, .button {
white-space: nowrap;
margin-right: 1rem;
}
}
.messagebutton > div {
button, .button {
margin-top: 1rem;
}
}
.notImplemented {
border: 2px solid rgb(70, 79, 88);
background: repeating-linear-gradient(

View file

@ -17,7 +17,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useMemo, useEffect } from "react";
import React, { useMemo, useEffect, ReactNode } from "react";
import { useFileInput, useComboBoxInput } from "../../../../lib/form";
import useShortcode from "./use-shortcode";
import useFormSubmit from "../../../../lib/form/submit";
@ -29,50 +29,71 @@ import { useAddEmojiMutation } from "../../../../lib/query/admin/custom-emoji";
import { useInstanceV1Query } from "../../../../lib/query/gts-api";
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 form = {
shortcode: useShortcode(),
image: useFileInput("image", {
withPreview: true,
maxSize: emojiMaxSize
}),
category: useComboBoxInput("category"),
};
const category = useComboBoxInput("category");
const [submitForm, result] = useFormSubmit({
shortcode, image, category
}, useAddEmojiMutation());
const [submitForm, result] = useFormSubmit(
form,
useAddEmojiMutation(),
{
changedOnly: false,
// On submission, reset form values
// no matter what the result was.
onFinish: (_res) => {
form.shortcode.reset();
form.image.reset();
form.category.reset();
}
},
);
useEffect(() => {
if (shortcode.value === undefined || shortcode.value.length == 0) {
if (image.value != undefined) {
let [name, _ext] = image.value.name.split(".");
shortcode.setter(name);
if (
form.shortcode.value === undefined ||
form.shortcode.value.length == 0
) {
if (form.image.value !== undefined) {
// Suggest shortcode based on filename.
let [name, _ext] = form.image.value.name.split(".");
form.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]);
// 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
}, [form.image.value]);
let emojiOrShortcode;
if (image.previewValue != undefined) {
emojiOrShortcode = <img
className="emoji"
src={image.previewValue}
title={`:${shortcode.value}:`}
alt={shortcode.value}
/>;
} else if (shortcode.value !== undefined && shortcode.value.length > 0) {
emojiOrShortcode = `:${shortcode.value}:`;
let emojiOrShortcode: ReactNode;
if (form.image.previewValue !== undefined) {
emojiOrShortcode = (
<img
className="emoji"
src={form.image.previewValue}
title={`:${form.shortcode.value}:`}
alt={form.shortcode.value}
/>
);
} else if (
form.shortcode.value !== undefined &&
form.shortcode.value.length > 0
) {
emojiOrShortcode = `:${form.shortcode.value}:`;
} else {
emojiOrShortcode = `:your_emoji_here:`;
}
@ -87,22 +108,21 @@ export default function NewEmojiForm() {
<form onSubmit={submitForm} className="form-flex">
<FileInput
field={image}
field={form.image}
accept="image/png,image/gif,image/webp"
/>
<TextInput
field={shortcode}
field={form.shortcode}
label="Shortcode, must be unique among the instance's local emoji"
/>
<CategorySelect
field={category}
children={[]}
field={form.category}
/>
<MutationButton
disabled={image.previewValue === undefined}
disabled={form.image.previewValue === undefined || form.shortcode.value?.length === 0}
label="Upload emoji"
result={result}
/>