mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-27 19:01: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"
|
||||
};
|
||||
|
||||
const checkListExtraProps = React.useCallback(() => ({ localEmojiCodes }), [localEmojiCodes]);
|
||||
|
||||
return (
|
||||
<div className="parsed">
|
||||
<span>This {type == "statuses" ? "toot" : "account"} uses the following custom emoji, select the ones you want to copy/disable:</span>
|
||||
<form onSubmit={formSubmit}>
|
||||
<CheckList
|
||||
field={form.selectedEmoji}
|
||||
Component={EmojiEntry}
|
||||
localEmojiCodes={localEmojiCodes}
|
||||
EntryComponent={EmojiEntry}
|
||||
getExtraProps={checkListExtraProps}
|
||||
/>
|
||||
|
||||
<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", {
|
||||
defaultValue: emoji.shortcode,
|
||||
validator: function validateShortcode(code) {
|
||||
|
@ -181,9 +183,16 @@ function EmojiEntry({ entry: emoji, localEmojiCodes, onChange }) {
|
|||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
onChange({ valid: shortcodeField.valid });
|
||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||
}, [shortcodeField.valid]);
|
||||
if (emoji.valid != shortcodeField.valid) {
|
||||
onChange({ valid: 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 (
|
||||
<>
|
||||
|
|
|
@ -189,9 +189,6 @@ function ImportList({ list, data: blockedInstances }) {
|
|||
}, [list]);
|
||||
|
||||
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 = {
|
||||
domains: useCheckListInput("domains", {
|
||||
|
@ -235,16 +232,8 @@ function ImportList({ list, data: blockedInstances }) {
|
|||
} />
|
||||
}
|
||||
|
||||
<CheckList
|
||||
<DomainCheckList
|
||||
field={form.domains}
|
||||
Component={DomainEntry}
|
||||
header={
|
||||
<>
|
||||
<b>Domain</b>
|
||||
<b></b>
|
||||
<b>{commentName}</b>
|
||||
</>
|
||||
}
|
||||
blockedInstances={blockedInstances}
|
||||
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", {
|
||||
defaultValue: entry.domain,
|
||||
validator: (value) => {
|
||||
return (entry.checked && !isValidDomain(value, { wildcard: true, allowUnicode: true }))
|
||||
? "Invalid domain"
|
||||
: "";
|
||||
}
|
||||
initValidation: domainValidationError(entry.valid),
|
||||
validator: (value) => domainValidationError(
|
||||
!entry.checked || isValidDomain(value, { wildcard: true, allowUnicode: true })
|
||||
)
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
onChange({ valid: domainField.valid });
|
||||
/* eslint-disable-next-line react-hooks/exhaustive-deps */
|
||||
}, [domainField.valid]);
|
||||
if (entry.valid != domainField.valid) {
|
||||
onChange({ valid: domainField.valid });
|
||||
}
|
||||
}, [onChange, entry.valid, 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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
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 (
|
||||
<>
|
||||
|
@ -315,8 +328,11 @@ function DomainEntry({ entry, onChange, blockedInstances, commentType }) {
|
|||
onChange({ domain: e.target.value, checked: true });
|
||||
}}
|
||||
/>
|
||||
<span id="icon">{icon}</span>
|
||||
<p>{entry[commentType]}</p>
|
||||
<span id="icon">{alreadyExists && <>
|
||||
<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");
|
||||
|
||||
module.exports = function CheckList({ field, header = "All", renderEntry }) {
|
||||
performance.mark("RENDER_CHECKLIST");
|
||||
module.exports = function CheckList({ field, header = "All", EntryComponent, getExtraProps }) {
|
||||
return (
|
||||
<div className="checkbox-list list">
|
||||
<CheckListHeader toggleAll={field.toggleAll}> {header}</CheckListHeader>
|
||||
<CheckListEntries
|
||||
entries={field.value}
|
||||
updateValue={field.onChange}
|
||||
renderEntry={renderEntry}
|
||||
EntryComponent={EntryComponent}
|
||||
getExtraProps={getExtraProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@ -47,38 +47,45 @@ function CheckListHeader({ toggleAll, children }) {
|
|||
);
|
||||
}
|
||||
|
||||
const CheckListEntries = React.memo(function CheckListEntries({ entries, renderEntry, updateValue }) {
|
||||
const deferredEntries = React.useDeferredValue(entries);
|
||||
const CheckListEntries = React.memo(
|
||||
function CheckListEntries({ entries, updateValue, EntryComponent, getExtraProps }) {
|
||||
const deferredEntries = React.useDeferredValue(entries);
|
||||
|
||||
return Object.values(deferredEntries).map((entry) => (
|
||||
<CheckListEntry
|
||||
key={entry.key}
|
||||
entry={entry}
|
||||
updateValue={updateValue}
|
||||
renderEntry={renderEntry}
|
||||
/>
|
||||
));
|
||||
});
|
||||
return Object.values(deferredEntries).map((entry) => (
|
||||
<CheckListEntry
|
||||
key={entry.key}
|
||||
entry={entry}
|
||||
updateValue={updateValue}
|
||||
EntryComponent={EntryComponent}
|
||||
getExtraProps={getExtraProps}
|
||||
/>
|
||||
));
|
||||
}
|
||||
);
|
||||
|
||||
/*
|
||||
React.memo is a performance optimization that only re-renders a CheckListEntry
|
||||
when it's props actually change, instead of every time anything
|
||||
in the list (CheckListEntries) updates
|
||||
*/
|
||||
const CheckListEntry = React.memo(function CheckListEntry({ entry, updateValue, renderEntry }) {
|
||||
const onChange = React.useCallback(
|
||||
(value) => updateValue(entry.key, value),
|
||||
[updateValue, entry.key]
|
||||
);
|
||||
const CheckListEntry = React.memo(
|
||||
function CheckListEntry({ entry, updateValue, getExtraProps, EntryComponent }) {
|
||||
const onChange = React.useCallback(
|
||||
(value) => updateValue(entry.key, value),
|
||||
[updateValue, entry.key]
|
||||
);
|
||||
|
||||
return (
|
||||
<label className="entry">
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={(e) => onChange({ checked: e.target.checked })}
|
||||
checked={entry.checked}
|
||||
/>
|
||||
{renderEntry(entry, onChange)}
|
||||
</label>
|
||||
);
|
||||
});
|
||||
const extraProps = React.useMemo(() => getExtraProps?.(entry), [getExtraProps, entry]);
|
||||
|
||||
return (
|
||||
<label className="entry">
|
||||
<input
|
||||
type="checkbox"
|
||||
onChange={(e) => onChange({ checked: e.target.checked })}
|
||||
checked={entry.checked}
|
||||
/>
|
||||
<EntryComponent entry={entry} onChange={onChange} extraProps={extraProps} />
|
||||
</label>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -81,7 +81,6 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
|||
const toggleAllRef = React.useRef(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
performance.mark("GoToSocial-useCheckListInput-useEffect-start");
|
||||
/* Updates (un)check all checkbox, based on shortcode checkboxes
|
||||
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);
|
||||
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]);
|
||||
|
||||
const reset = React.useCallback(
|
||||
|
@ -137,7 +134,8 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
|||
function selectedValues() {
|
||||
return syncpipe(state, [
|
||||
(_) => 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,
|
||||
setter: setText,
|
||||
valid,
|
||||
validate: () => setValidation(validator(text)),
|
||||
hasChanged: () => text != defaultValue
|
||||
});
|
||||
};
|
Loading…
Reference in a new issue