mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-28 03:11:01 +00:00
update checklist components
This commit is contained in:
parent
4367960fe4
commit
79c792b832
5 changed files with 104 additions and 73 deletions
|
@ -129,14 +129,16 @@ function CopyEmojiForm({ localEmojiCodes, type, emojiList }) {
|
||||||
title: "No emoji selected, cannot perform any actions"
|
title: "No emoji selected, cannot perform any actions"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkListExtraProps = React.useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="parsed">
|
<div className="parsed">
|
||||||
<span>This {type == "statuses" ? "toot" : "account"} uses the following custom emoji, select the ones you want to copy/disable:</span>
|
<span>This {type == "statuses" ? "toot" : "account"} uses the following custom emoji, select the ones you want to copy/disable:</span>
|
||||||
<form onSubmit={formSubmit}>
|
<form onSubmit={formSubmit}>
|
||||||
<CheckList
|
<CheckList
|
||||||
field={form.selectedEmoji}
|
field={form.selectedEmoji}
|
||||||
Component={EmojiEntry}
|
EntryComponent={EmojiEntry}
|
||||||
localEmojiCodes={localEmojiCodes}
|
getExtraProps={checkListExtraProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CategorySelect
|
<CategorySelect
|
||||||
|
@ -170,7 +172,7 @@ function ErrorList({ errors }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function EmojiEntry({ entry: emoji, localEmojiCodes, onChange }) {
|
function EmojiEntry({ entry: emoji, onChange, extraProps: { localEmojiCodes } }) {
|
||||||
const shortcodeField = useTextInput("shortcode", {
|
const shortcodeField = useTextInput("shortcode", {
|
||||||
defaultValue: emoji.shortcode,
|
defaultValue: emoji.shortcode,
|
||||||
validator: function validateShortcode(code) {
|
validator: function validateShortcode(code) {
|
||||||
|
@ -181,9 +183,16 @@ function EmojiEntry({ entry: emoji, localEmojiCodes, onChange }) {
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
if (emoji.valid != shortcodeField.valid) {
|
||||||
onChange({ valid: shortcodeField.valid });
|
onChange({ valid: shortcodeField.valid });
|
||||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
}
|
||||||
}, [shortcodeField.valid]);
|
}, [onChange, emoji.valid, shortcodeField.valid]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
shortcodeField.validate();
|
||||||
|
// only need this update if it's the emoji.checked that updated, not shortcodeField
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [emoji.checked]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -189,9 +189,6 @@ function ImportList({ list, data: blockedInstances }) {
|
||||||
}, [list]);
|
}, [list]);
|
||||||
|
|
||||||
const showComment = useTextInput("showComment", { defaultValue: hasComment.type ?? "public_comment" });
|
const showComment = useTextInput("showComment", { defaultValue: hasComment.type ?? "public_comment" });
|
||||||
let commentName = "";
|
|
||||||
if (showComment.value == "public_comment") { commentName = "Public comment"; }
|
|
||||||
if (showComment.value == "private_comment") { commentName = "Private comment"; }
|
|
||||||
|
|
||||||
const form = {
|
const form = {
|
||||||
domains: useCheckListInput("domains", {
|
domains: useCheckListInput("domains", {
|
||||||
|
@ -235,16 +232,8 @@ function ImportList({ list, data: blockedInstances }) {
|
||||||
} />
|
} />
|
||||||
}
|
}
|
||||||
|
|
||||||
<CheckList
|
<DomainCheckList
|
||||||
field={form.domains}
|
field={form.domains}
|
||||||
Component={DomainEntry}
|
|
||||||
header={
|
|
||||||
<>
|
|
||||||
<b>Domain</b>
|
|
||||||
<b></b>
|
|
||||||
<b>{commentName}</b>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
blockedInstances={blockedInstances}
|
blockedInstances={blockedInstances}
|
||||||
commentType={showComment.value}
|
commentType={showComment.value}
|
||||||
/>
|
/>
|
||||||
|
@ -280,31 +269,55 @@ function ImportList({ list, data: blockedInstances }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function DomainEntry({ entry, onChange, blockedInstances, commentType }) {
|
function DomainCheckList({ field, blockedInstances, commentType }) {
|
||||||
|
const getExtraProps = React.useCallback((entry) => {
|
||||||
|
return {
|
||||||
|
comment: entry[commentType],
|
||||||
|
alreadyExists: blockedInstances[entry.domain] != undefined
|
||||||
|
};
|
||||||
|
}, [blockedInstances, commentType]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CheckList
|
||||||
|
field={field}
|
||||||
|
header={<>
|
||||||
|
<b>Domain</b>
|
||||||
|
<b></b>
|
||||||
|
<b>
|
||||||
|
{commentType == "public_comment" && "Public comment"}
|
||||||
|
{commentType == "private_comment" && "Private comment"}
|
||||||
|
</b>
|
||||||
|
</>}
|
||||||
|
EntryComponent={DomainEntry}
|
||||||
|
getExtraProps={getExtraProps}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function domainValidationError(isValid) {
|
||||||
|
return isValid ? "" : "Invalid domain";
|
||||||
|
}
|
||||||
|
|
||||||
|
function DomainEntry({ entry, onChange, extraProps: { alreadyExists, comment } }) {
|
||||||
const domainField = useTextInput("domain", {
|
const domainField = useTextInput("domain", {
|
||||||
defaultValue: entry.domain,
|
defaultValue: entry.domain,
|
||||||
validator: (value) => {
|
initValidation: domainValidationError(entry.valid),
|
||||||
return (entry.checked && !isValidDomain(value, { wildcard: true, allowUnicode: true }))
|
validator: (value) => domainValidationError(
|
||||||
? "Invalid domain"
|
!entry.checked || isValidDomain(value, { wildcard: true, allowUnicode: true })
|
||||||
: "";
|
)
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
if (entry.valid != domainField.valid) {
|
||||||
onChange({ valid: domainField.valid });
|
onChange({ valid: domainField.valid });
|
||||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
|
||||||
}, [domainField.valid]);
|
|
||||||
|
|
||||||
let icon = null;
|
|
||||||
|
|
||||||
if (blockedInstances[domainField.value] != undefined) {
|
|
||||||
icon = (
|
|
||||||
<>
|
|
||||||
<i className="fa fa-history already-blocked" aria-hidden="true" title="Domain block already exists"></i>
|
|
||||||
<span className="sr-only">Domain block already exists.</span>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
}, [onChange, entry.valid, domainField.valid]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
domainField.validate();
|
||||||
|
// only need this update if it's the entry.checked that updated, not domainField
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [entry.checked]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -315,8 +328,11 @@ function DomainEntry({ entry, onChange, blockedInstances, commentType }) {
|
||||||
onChange({ domain: e.target.value, checked: true });
|
onChange({ domain: e.target.value, checked: true });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span id="icon">{icon}</span>
|
<span id="icon">{alreadyExists && <>
|
||||||
<p>{entry[commentType]}</p>
|
<i className="fa fa-history already-blocked" aria-hidden="true" title="Domain block already exists"></i>
|
||||||
|
<span className="sr-only">Domain block already exists.</span>
|
||||||
|
</>}</span>
|
||||||
|
<p>{comment}</p>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -20,15 +20,15 @@
|
||||||
|
|
||||||
const React = require("react");
|
const React = require("react");
|
||||||
|
|
||||||
module.exports = function CheckList({ field, header = "All", renderEntry }) {
|
module.exports = function CheckList({ field, header = "All", EntryComponent, getExtraProps }) {
|
||||||
performance.mark("RENDER_CHECKLIST");
|
|
||||||
return (
|
return (
|
||||||
<div className="checkbox-list list">
|
<div className="checkbox-list list">
|
||||||
<CheckListHeader toggleAll={field.toggleAll}> {header}</CheckListHeader>
|
<CheckListHeader toggleAll={field.toggleAll}> {header}</CheckListHeader>
|
||||||
<CheckListEntries
|
<CheckListEntries
|
||||||
entries={field.value}
|
entries={field.value}
|
||||||
updateValue={field.onChange}
|
updateValue={field.onChange}
|
||||||
renderEntry={renderEntry}
|
EntryComponent={EntryComponent}
|
||||||
|
getExtraProps={getExtraProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -47,7 +47,8 @@ function CheckListHeader({ toggleAll, children }) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const CheckListEntries = React.memo(function CheckListEntries({ entries, renderEntry, updateValue }) {
|
const CheckListEntries = React.memo(
|
||||||
|
function CheckListEntries({ entries, updateValue, EntryComponent, getExtraProps }) {
|
||||||
const deferredEntries = React.useDeferredValue(entries);
|
const deferredEntries = React.useDeferredValue(entries);
|
||||||
|
|
||||||
return Object.values(deferredEntries).map((entry) => (
|
return Object.values(deferredEntries).map((entry) => (
|
||||||
|
@ -55,22 +56,27 @@ const CheckListEntries = React.memo(function CheckListEntries({ entries, renderE
|
||||||
key={entry.key}
|
key={entry.key}
|
||||||
entry={entry}
|
entry={entry}
|
||||||
updateValue={updateValue}
|
updateValue={updateValue}
|
||||||
renderEntry={renderEntry}
|
EntryComponent={EntryComponent}
|
||||||
|
getExtraProps={getExtraProps}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
React.memo is a performance optimization that only re-renders a CheckListEntry
|
React.memo is a performance optimization that only re-renders a CheckListEntry
|
||||||
when it's props actually change, instead of every time anything
|
when it's props actually change, instead of every time anything
|
||||||
in the list (CheckListEntries) updates
|
in the list (CheckListEntries) updates
|
||||||
*/
|
*/
|
||||||
const CheckListEntry = React.memo(function CheckListEntry({ entry, updateValue, renderEntry }) {
|
const CheckListEntry = React.memo(
|
||||||
|
function CheckListEntry({ entry, updateValue, getExtraProps, EntryComponent }) {
|
||||||
const onChange = React.useCallback(
|
const onChange = React.useCallback(
|
||||||
(value) => updateValue(entry.key, value),
|
(value) => updateValue(entry.key, value),
|
||||||
[updateValue, entry.key]
|
[updateValue, entry.key]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const extraProps = React.useMemo(() => getExtraProps?.(entry), [getExtraProps, entry]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<label className="entry">
|
<label className="entry">
|
||||||
<input
|
<input
|
||||||
|
@ -78,7 +84,8 @@ const CheckListEntry = React.memo(function CheckListEntry({ entry, updateValue,
|
||||||
onChange={(e) => onChange({ checked: e.target.checked })}
|
onChange={(e) => onChange({ checked: e.target.checked })}
|
||||||
checked={entry.checked}
|
checked={entry.checked}
|
||||||
/>
|
/>
|
||||||
{renderEntry(entry, onChange)}
|
<EntryComponent entry={entry} onChange={onChange} extraProps={extraProps} />
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
);
|
|
@ -81,7 +81,6 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
||||||
const toggleAllRef = React.useRef(null);
|
const toggleAllRef = React.useRef(null);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
performance.mark("GoToSocial-useCheckListInput-useEffect-start");
|
|
||||||
/* Updates (un)check all checkbox, based on shortcode checkboxes
|
/* Updates (un)check all checkbox, based on shortcode checkboxes
|
||||||
Can be 0 (not checked), 1 (checked) or 2 (indeterminate)
|
Can be 0 (not checked), 1 (checked) or 2 (indeterminate)
|
||||||
*/
|
*/
|
||||||
|
@ -108,8 +107,6 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
||||||
setToggleAllState(all ? 1 : 0);
|
setToggleAllState(all ? 1 : 0);
|
||||||
toggleAllRef.current.indeterminate = false;
|
toggleAllRef.current.indeterminate = false;
|
||||||
}
|
}
|
||||||
performance.mark("GoToSocial-useCheckListInput-useEffect-finish");
|
|
||||||
performance.measure("GoToSocial-useCheckListInput-useEffect-processed", "GoToSocial-useCheckListInput-useEffect-start", "GoToSocial-useCheckListInput-useEffect-finish");
|
|
||||||
}, [state, toggleAllRef]);
|
}, [state, toggleAllRef]);
|
||||||
|
|
||||||
const reset = React.useCallback(
|
const reset = React.useCallback(
|
||||||
|
@ -137,7 +134,8 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
||||||
function selectedValues() {
|
function selectedValues() {
|
||||||
return syncpipe(state, [
|
return syncpipe(state, [
|
||||||
(_) => Object.values(_),
|
(_) => Object.values(_),
|
||||||
(_) => _.filter((entry) => entry.checked)
|
(_) => _.filter((entry) => entry.checked),
|
||||||
|
(_) => _.map((entry) => ({ ...entry }))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,7 @@ module.exports = function useTextInput({ name, Name }, {
|
||||||
ref: textRef,
|
ref: textRef,
|
||||||
setter: setText,
|
setter: setText,
|
||||||
valid,
|
valid,
|
||||||
|
validate: () => setValidation(validator(text)),
|
||||||
hasChanged: () => text != defaultValue
|
hasChanged: () => text != defaultValue
|
||||||
});
|
});
|
||||||
};
|
};
|
Loading…
Reference in a new issue