mirror of
1
Fork 0

refactor parse-from-toot

This commit is contained in:
f0x 2023-01-11 19:52:52 +00:00
parent beb09aa827
commit 59413c3482
6 changed files with 100 additions and 89 deletions

View File

@ -29,11 +29,15 @@ const {
useCheckListInput useCheckListInput
} = require("../../../lib/form"); } = require("../../../lib/form");
const useFormSubmit = require("../../../lib/form/submit");
const CheckList = require("../../../components/check-list"); const CheckList = require("../../../components/check-list");
const { CategorySelect } = require('../category-select'); const { CategorySelect } = require('../category-select');
const query = require("../../../lib/query"); const query = require("../../../lib/query");
const Loading = require("../../../components/loading"); const Loading = require("../../../components/loading");
const { TextInput } = require("../../../components/form/inputs");
const MutationButton = require("../../../components/form/mutation-button");
module.exports = function ParseFromToot({ emojiCodes }) { module.exports = function ParseFromToot({ emojiCodes }) {
const [searchStatus, { data, isLoading, isSuccess, error }] = query.useSearchStatusForEmojiMutation(); const [searchStatus, { data, isLoading, isSuccess, error }] = query.useSearchStatusForEmojiMutation();
@ -70,7 +74,9 @@ module.exports = function ParseFromToot({ emojiCodes }) {
function submitSearch(e) { function submitSearch(e) {
e.preventDefault(); e.preventDefault();
searchStatus(url); if (url.trim().length != 0) {
searchStatus(url);
}
} }
return ( return (
@ -91,7 +97,7 @@ module.exports = function ParseFromToot({ emojiCodes }) {
/> />
<button className="button-inline" disabled={isLoading}> <button className="button-inline" disabled={isLoading}>
<i className={[ <i className={[
"fa", "fa fa-fw",
(isLoading (isLoading
? "fa-refresh fa-spin" ? "fa-refresh fa-spin"
: "fa-search") : "fa-search")
@ -99,7 +105,6 @@ module.exports = function ParseFromToot({ emojiCodes }) {
<span className="sr-only">Search</span> <span className="sr-only">Search</span>
</button> </button>
</div> </div>
{isLoading && <Loading />}
{error && <div className="error">{error.data.error}</div>} {error && <div className="error">{error.data.error}</div>}
</div> </div>
</form> </form>
@ -119,22 +124,25 @@ function CopyEmojiForm({ localEmojiCodes, type, domain, emojiList }) {
const [categoryState, resetCategory, { category }] = useComboBoxInput("category"); const [categoryState, resetCategory, { category }] = useComboBoxInput("category");
const buttonsInactive = emojiCheckList.someSelected
? {}
: {
disabled: true,
title: "No emoji selected, cannot perform any actions"
};
function submit(action) { function submit(action) {
Promise.try(() => { Promise.try(() => {
setError(null); setError(null);
const selectedShortcodes = syncpipe(emojiCheckList.value, [ const selectedShortcodes = emojiCheckList.selectedValues.map(([shortcode, entry]) => {
(_) => Object.entries(_), if (action == "copy" && !entry.valid) {
(_) => _.filter(([_shortcode, entry]) => entry.checked), throw `One or more selected emoji have non-unique shortcodes (${shortcode}), unselect them or pick a different local shortcode`;
(_) => _.map(([shortcode, entry]) => { }
if (action == "copy" && !entry.valid) { return {
throw `One or more selected emoji have non-unique shortcodes (${shortcode}), unselect them or pick a different local shortcode`; shortcode,
} localShortcode: entry.shortcode
return { };
shortcode, });
localShortcode: entry.shortcode
};
})
]);
return patchRemoteEmojis({ return patchRemoteEmojis({
action, action,
@ -166,15 +174,6 @@ function CopyEmojiForm({ localEmojiCodes, type, domain, emojiList }) {
Component={EmojiEntry} Component={EmojiEntry}
localEmojiCodes={localEmojiCodes} localEmojiCodes={localEmojiCodes}
/> />
{/* {emojiList.map((emoji) => (
<EmojiEntry
key={emoji.shortcode}
emoji={emoji}
localEmojiCodes={localEmojiCodes}
updateEmoji={(value) => updateEmoji(emoji.shortcode, value)}
checked={emojiState[emoji.shortcode].checked}
/>
))} */}
<CategorySelect <CategorySelect
value={category} value={category}
@ -182,8 +181,8 @@ function CopyEmojiForm({ localEmojiCodes, type, domain, emojiList }) {
/> />
<div className="action-buttons row"> <div className="action-buttons row">
<button disabled={!emojiCheckList.someSelected} onClick={() => submit("copy")}>{patchResult.isLoading ? "Processing..." : "Copy to local emoji"}</button> <MutationButton label="Copy to local emoji" type="button" result={patchResult} {...buttonsInactive} />
<button disabled={!emojiCheckList.someSelected} onClick={() => submit("disable")} className="danger">{patchResult.isLoading ? "Processing..." : "Disable"}</button> <MutationButton label="Disable" type="button" result={patchResult} className="button danger" {...buttonsInactive} />
</div> </div>
{err && <div className="error"> {err && <div className="error">
{err} {err}
@ -196,7 +195,7 @@ function CopyEmojiForm({ localEmojiCodes, type, domain, emojiList }) {
} }
function EmojiEntry({ entry: emoji, localEmojiCodes, onChange }) { function EmojiEntry({ entry: emoji, localEmojiCodes, onChange }) {
const [onShortcodeChange, _resetShortcode, { shortcode, shortcodeRef, shortcodeValid }] = useTextInput("shortcode", { const shortcodeField = useTextInput("shortcode", {
defaultValue: emoji.shortcode, defaultValue: emoji.shortcode,
validator: function validateShortcode(code) { validator: function validateShortcode(code) {
return (emoji.checked && localEmojiCodes.has(code)) return (emoji.checked && localEmojiCodes.has(code))
@ -206,24 +205,20 @@ function EmojiEntry({ entry: emoji, localEmojiCodes, onChange }) {
}); });
React.useEffect(() => { React.useEffect(() => {
onChange({ valid: shortcodeValid }); onChange({ valid: shortcodeField.valid });
/* eslint-disable-next-line react-hooks/exhaustive-deps */ /* eslint-disable-next-line react-hooks/exhaustive-deps */
}, [shortcodeValid]); }, [shortcodeField.valid]);
return ( return (
<> <>
<img className="emoji" src={emoji.url} title={emoji.shortcode} /> <img className="emoji" src={emoji.url} title={emoji.shortcode} />
<input <TextInput
type="text" field={shortcodeField}
id="shortcode"
name="Shortcode"
ref={shortcodeRef}
onChange={(e) => { onChange={(e) => {
onShortcodeChange(e); shortcodeField.onChange(e);
onChange({ shortcode: e.target.value, checked: true }); onChange({ shortcode: e.target.value, checked: true });
}} }}
value={shortcode}
/> />
</> </>
); );

View File

@ -22,7 +22,7 @@ const React = require("react");
module.exports = function CheckList({ field, Component, ...componentProps }) { module.exports = function CheckList({ field, Component, ...componentProps }) {
return ( return (
<div className="checkbox-list"> <div className="checkbox-list list">
<label className="header"> <label className="header">
<input <input
ref={field.toggleAll.ref} ref={field.toggleAll.ref}
@ -46,10 +46,10 @@ module.exports = function CheckList({ field, Component, ...componentProps }) {
function CheckListEntry({ entry, onChange, Component, componentProps }) { function CheckListEntry({ entry, onChange, Component, componentProps }) {
return ( return (
<label className="row"> <label className="entry">
<input <input
type="checkbox" type="checkbox"
onChange={(e) => onChange({ checked: e.target.value })} onChange={(e) => onChange({ checked: e.target.checked })}
checked={entry.checked} checked={entry.checked}
/> />
<Component entry={entry} onChange={onChange} {...componentProps} /> <Component entry={entry} onChange={onChange} {...componentProps} />

View File

@ -23,8 +23,6 @@ const React = require("react");
module.exports = function MutationButton({ label, result, disabled, ...inputProps }) { module.exports = function MutationButton({ label, result, disabled, ...inputProps }) {
let iconClass = ""; let iconClass = "";
console.log(label, result);
if (result.isLoading) { if (result.isLoading) {
iconClass = "fa-spin fa-refresh"; iconClass = "fa-spin fa-refresh";
} else if (result.isSuccess) { } else if (result.isSuccess) {
@ -36,7 +34,7 @@ module.exports = function MutationButton({ label, result, disabled, ...inputProp
<section className="error">{result.error.status}: {result.error.data.error}</section> <section className="error">{result.error.status}: {result.error.data.error}</section>
} }
<button type="submit" disabled={result.isLoading || disabled} {...inputProps}> <button type="submit" disabled={result.isLoading || disabled} {...inputProps}>
<i className={`fa fa-fw ${iconClass}`} aria-hidden="true"></i> <i className={`fa fa-fw with-text ${iconClass}`} aria-hidden="true"></i>
{result.isLoading {result.isLoading
? "Processing..." ? "Processing..."
: label : label

View File

@ -120,6 +120,13 @@ module.exports = function useCheckListInput({ name, Name }, { entries, uniqueKey
setState(updateAllState(state, defaultValue)); setState(updateAllState(state, defaultValue));
} }
function selectedValues() {
return syncpipe(state, [
(_) => Object.values(_),
(_) => _.filter((entry) => entry.checked)
]);
}
return Object.assign([ return Object.assign([
state, state,
reset, reset,
@ -128,6 +135,7 @@ module.exports = function useCheckListInput({ name, Name }, { entries, uniqueKey
name, name,
value: state, value: state,
onChange: (key, newValue) => setState(updateState(state, key, newValue)), onChange: (key, newValue) => setState(updateState(state, key, newValue)),
selectedValues,
reset, reset,
someSelected, someSelected,
toggleAll: { toggleAll: {

View File

@ -32,16 +32,16 @@ const endpoints = (build) => ({
...params ...params
} }
}), }),
providesTags: (res) => providesTags: (res) =>
res res
? [...res.map((emoji) => ({type: "Emojis", id: emoji.id})), {type: "Emojis", id: "LIST"}] ? [...res.map((emoji) => ({ type: "Emojis", id: emoji.id })), { type: "Emojis", id: "LIST" }]
: [{type: "Emojis", id: "LIST"}] : [{ type: "Emojis", id: "LIST" }]
}), }),
getEmoji: build.query({ getEmoji: build.query({
query: (id) => ({ query: (id) => ({
url: `/api/v1/admin/custom_emojis/${id}` url: `/api/v1/admin/custom_emojis/${id}`
}), }),
providesTags: (res, error, id) => [{type: "Emojis", id}] providesTags: (res, error, id) => [{ type: "Emojis", id }]
}), }),
addEmoji: build.mutation({ addEmoji: build.mutation({
query: (form) => { query: (form) => {
@ -49,16 +49,17 @@ const endpoints = (build) => ({
method: "POST", method: "POST",
url: `/api/v1/admin/custom_emojis`, url: `/api/v1/admin/custom_emojis`,
asForm: true, asForm: true,
body: form body: form,
discardEmpty: true
}; };
}, },
invalidatesTags: (res) => invalidatesTags: (res) =>
res res
? [{type: "Emojis", id: "LIST"}, {type: "Emojis", id: res.id}] ? [{ type: "Emojis", id: "LIST" }, { type: "Emojis", id: res.id }]
: [{type: "Emojis", id: "LIST"}] : [{ type: "Emojis", id: "LIST" }]
}), }),
editEmoji: build.mutation({ editEmoji: build.mutation({
query: ({id, ...patch}) => { query: ({ id, ...patch }) => {
return { return {
method: "PATCH", method: "PATCH",
url: `/api/v1/admin/custom_emojis/${id}`, url: `/api/v1/admin/custom_emojis/${id}`,
@ -69,17 +70,17 @@ const endpoints = (build) => ({
} }
}; };
}, },
invalidatesTags: (res) => invalidatesTags: (res) =>
res res
? [{type: "Emojis", id: "LIST"}, {type: "Emojis", id: res.id}] ? [{ type: "Emojis", id: "LIST" }, { type: "Emojis", id: res.id }]
: [{type: "Emojis", id: "LIST"}] : [{ type: "Emojis", id: "LIST" }]
}), }),
deleteEmoji: build.mutation({ deleteEmoji: build.mutation({
query: (id) => ({ query: (id) => ({
method: "DELETE", method: "DELETE",
url: `/api/v1/admin/custom_emojis/${id}` url: `/api/v1/admin/custom_emojis/${id}`
}), }),
invalidatesTags: (res, error, id) => [{type: "Emojis", id}] invalidatesTags: (res, error, id) => [{ type: "Emojis", id }]
}), }),
searchStatusForEmoji: build.mutation({ searchStatusForEmoji: build.mutation({
query: (url) => ({ query: (url) => ({
@ -88,7 +89,7 @@ const endpoints = (build) => ({
}), }),
transformResponse: (res) => { transformResponse: (res) => {
/* Parses search response, prioritizing a toot result, /* Parses search response, prioritizing a toot result,
and returns referenced custom emoji and returns referenced custom emoji
*/ */
let type; let type;
@ -112,7 +113,7 @@ const endpoints = (build) => ({
} }
}), }),
patchRemoteEmojis: build.mutation({ patchRemoteEmojis: build.mutation({
queryFn: ({action, domain, list, category}, api, _extraOpts, baseQuery) => { queryFn: ({ action, domain, list, category }, api, _extraOpts, baseQuery) => {
const data = []; const data = [];
const errors = []; const errors = [];
@ -166,8 +167,8 @@ const endpoints = (build) => ({
} }
}); });
}, },
invalidatesTags: () => [{type: "Emojis", id: "LIST"}] invalidatesTags: () => [{ type: "Emojis", id: "LIST" }]
}) })
}); });
module.exports = base.injectEndpoints({endpoints}); module.exports = base.injectEndpoints({ endpoints });

View File

@ -50,7 +50,7 @@ section {
h2 { h2 {
margin: 0; margin: 0;
margin-bottom: 0.5rem; margin-top: 0.1rem;
} }
&:only-child { &:only-child {
@ -360,8 +360,25 @@ span.form-info {
.list { .list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
max-height: 40rem;
overflow: auto; &.scrolling {
max-height: 40rem;
overflow: auto;
}
.header, .entry {
padding: 0.5rem;
}
.header {
border: 0.1rem solid transparent; /* for alignment with .entry border padding */
background: $gray2;
display: flex;
}
input[type=checkbox] {
margin-left: 0.5rem;
}
.entry { .entry {
display: flex; display: flex;
@ -372,17 +389,23 @@ span.form-info {
&:nth-child(even) { &:nth-child(even) {
background: $settings-entry-alternate-bg; background: $settings-entry-alternate-bg;
} }
&:hover { &:hover {
background: $settings-entry-hover-bg; background: $settings-entry-hover-bg;
} }
&:active, &:focus, &:hover { &:active, &:focus, &:hover {
border-color: $fg-accent; border-color: $fg-accent;
} }
} }
} }
.checkbox-list {
.header, .entry {
gap: 1rem;
}
}
.instance-list { .instance-list {
.filter { .filter {
display: flex; display: flex;
@ -407,13 +430,16 @@ span.form-info {
justify-content: space-between; justify-content: space-between;
} }
.checkbox-list { .emoji-list {
background: $settings-entry-bg; background: $settings-entry-bg;
.entry { .entry {
padding: 0.5rem;
flex-direction: column; flex-direction: column;
b {
padding-left: 0.4rem;
}
.emoji-group { .emoji-group {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -555,6 +581,7 @@ span.form-info {
.row { .row {
display: flex; display: flex;
gap: 0.5rem;
} }
.emoji-detail { .emoji-detail {
@ -612,8 +639,8 @@ span.form-info {
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
& > span { span {
margin-bottom: -1rem; margin-bottom: -0.5rem;
} }
.action-buttons { .action-buttons {
@ -621,27 +648,9 @@ span.form-info {
} }
.checkbox-list { .checkbox-list {
display: flex; .entry {
flex-direction: column;
& > * {
gap: 1rem;
align-items: center;
padding: 0.5rem 1rem;
}
.header {
background: $gray2;
display: flex;
}
.row {
display: grid; display: grid;
grid-template-columns: auto auto 1fr; grid-template-columns: auto auto 1fr;
&:hover {
background: $settings-entry-hover-bg;
}
} }
.emoji { .emoji {
@ -672,7 +681,7 @@ span.form-info {
} }
} }
button .fa-fw { button .fa.with-text {
margin-left: -1.28571429em; margin-left: -1.28571429em;
} }