mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Allow import/export/creation of domain allows via admin panel (#2264)
* it's happening! * aaa * fix silly whoopsie * it's working pa! it's working ma! * model report parameters * shuffle some more stuff around * getting there * oo hoo * finish tidying up for now * aaa * fix use form submit errors * peepee poo poo * aaaaa * ffff * they see me typin', they hatin' * boop * aaa * oooo * typing typing tappa tappa * almost done typing * weee * alright * push it push it real good doo doo doo doo doo doo * thingy no worky * almost done * mutation modifers not quite right * hmm * it works * view blocks + allows nicely * it works! * typia install * the old linterino * linter plz
This commit is contained in:
@@ -17,11 +17,19 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import { useState } from "react";
|
||||
import type {
|
||||
BoolFormInputHook,
|
||||
CreateHookNames,
|
||||
HookOpts,
|
||||
} from "./types";
|
||||
|
||||
const _default = false;
|
||||
module.exports = function useBoolInput({ name, Name }, { initialValue = _default }) {
|
||||
const [value, setValue] = React.useState(initialValue);
|
||||
export default function useBoolInput(
|
||||
{ name, Name }: CreateHookNames,
|
||||
{ initialValue = _default }: HookOpts<boolean>
|
||||
): BoolFormInputHook {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
function onChange(e) {
|
||||
setValue(e.target.checked);
|
||||
@@ -41,6 +49,7 @@ module.exports = function useBoolInput({ name, Name }, { initialValue = _default
|
||||
}
|
||||
], {
|
||||
name,
|
||||
Name: "",
|
||||
onChange,
|
||||
reset,
|
||||
value,
|
||||
@@ -48,4 +57,4 @@ module.exports = function useBoolInput({ name, Name }, { initialValue = _default
|
||||
hasChanged: () => value != initialValue,
|
||||
_default
|
||||
});
|
||||
};
|
||||
}
|
@@ -17,37 +17,58 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
const syncpipe = require("syncpipe");
|
||||
const { createSlice } = require("@reduxjs/toolkit");
|
||||
const { enableMapSet } = require("immer");
|
||||
import {
|
||||
useReducer,
|
||||
useRef,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from "react";
|
||||
|
||||
enableMapSet(); // for use in reducers
|
||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||
|
||||
import type {
|
||||
Checkable,
|
||||
ChecklistInputHook,
|
||||
CreateHookNames,
|
||||
HookOpts,
|
||||
} from "./types";
|
||||
|
||||
// https://immerjs.github.io/immer/installation#pick-your-immer-version
|
||||
import { enableMapSet } from "immer";
|
||||
enableMapSet();
|
||||
|
||||
interface ChecklistState {
|
||||
entries: { [k: string]: Checkable },
|
||||
selectedEntries: Set<string>,
|
||||
}
|
||||
|
||||
const initialState: ChecklistState = {
|
||||
entries: {},
|
||||
selectedEntries: new Set(),
|
||||
};
|
||||
|
||||
const { reducer, actions } = createSlice({
|
||||
name: "checklist",
|
||||
initialState: {}, // not handled by slice itself
|
||||
initialState, // not handled by slice itself
|
||||
reducers: {
|
||||
updateAll: (state, { payload: checked }) => {
|
||||
const selectedEntries = new Set();
|
||||
return {
|
||||
entries: syncpipe(state.entries, [
|
||||
(_) => Object.values(_),
|
||||
(_) => _.map((entry) => {
|
||||
if (checked) {
|
||||
selectedEntries.add(entry.key);
|
||||
}
|
||||
return [entry.key, {
|
||||
...entry,
|
||||
checked
|
||||
}];
|
||||
}),
|
||||
(_) => Object.fromEntries(_)
|
||||
]),
|
||||
selectedEntries
|
||||
};
|
||||
updateAll: (state, { payload: checked }: PayloadAction<boolean>) => {
|
||||
const selectedEntries = new Set<string>();
|
||||
const entries = Object.fromEntries(
|
||||
Object.values(state.entries).map((entry) => {
|
||||
if (checked) {
|
||||
// Cheekily add this to selected
|
||||
// entries while we're here.
|
||||
selectedEntries.add(entry.key);
|
||||
}
|
||||
|
||||
return [entry.key, { ...entry, checked } ];
|
||||
})
|
||||
);
|
||||
|
||||
return { entries, selectedEntries };
|
||||
},
|
||||
update: (state, { payload: { key, value } }) => {
|
||||
update: (state, { payload: { key, value } }: PayloadAction<{key: string, value: Checkable}>) => {
|
||||
if (value.checked !== undefined) {
|
||||
if (value.checked === true) {
|
||||
state.selectedEntries.add(key);
|
||||
@@ -61,7 +82,7 @@ const { reducer, actions } = createSlice({
|
||||
...value
|
||||
};
|
||||
},
|
||||
updateMultiple: (state, { payload }) => {
|
||||
updateMultiple: (state, { payload }: PayloadAction<Array<[key: string, value: Checkable]>>) => {
|
||||
payload.forEach(([key, value]) => {
|
||||
if (value.checked !== undefined) {
|
||||
if (value.checked === true) {
|
||||
@@ -80,43 +101,57 @@ const { reducer, actions } = createSlice({
|
||||
}
|
||||
});
|
||||
|
||||
function initialState({ entries, uniqueKey, initialValue }) {
|
||||
const selectedEntries = new Set();
|
||||
function initialHookState({
|
||||
entries,
|
||||
uniqueKey,
|
||||
initialValue,
|
||||
}: {
|
||||
entries: Checkable[],
|
||||
uniqueKey: string,
|
||||
initialValue: boolean,
|
||||
}): ChecklistState {
|
||||
const selectedEntries = new Set<string>();
|
||||
const mappedEntries = Object.fromEntries(
|
||||
entries.map((entry) => {
|
||||
const key = entry[uniqueKey];
|
||||
const checked = entry.checked ?? initialValue;
|
||||
|
||||
if (checked) {
|
||||
selectedEntries.add(key);
|
||||
} else {
|
||||
selectedEntries.delete(key);
|
||||
}
|
||||
|
||||
return [ key, { ...entry, key, checked } ];
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
entries: syncpipe(entries, [
|
||||
(_) => _.map((entry) => {
|
||||
let key = entry[uniqueKey];
|
||||
let checked = entry.checked ?? initialValue;
|
||||
|
||||
if (checked) {
|
||||
selectedEntries.add(key);
|
||||
} else {
|
||||
selectedEntries.delete(key);
|
||||
}
|
||||
|
||||
return [
|
||||
key,
|
||||
{
|
||||
...entry,
|
||||
key,
|
||||
checked
|
||||
}
|
||||
];
|
||||
}),
|
||||
(_) => Object.fromEntries(_)
|
||||
]),
|
||||
entries: mappedEntries,
|
||||
selectedEntries
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "key", initialValue = false }) {
|
||||
const [state, dispatch] = React.useReducer(reducer, null,
|
||||
() => initialState({ entries, uniqueKey, initialValue }) // initial state
|
||||
const _default: { [k: string]: Checkable } = {};
|
||||
|
||||
export default function useCheckListInput(
|
||||
/* eslint-disable no-unused-vars */
|
||||
{ name, Name }: CreateHookNames,
|
||||
{
|
||||
entries = [],
|
||||
uniqueKey = "key",
|
||||
initialValue = false,
|
||||
}: HookOpts<boolean>
|
||||
): ChecklistInputHook {
|
||||
const [state, dispatch] = useReducer(
|
||||
reducer,
|
||||
initialState,
|
||||
(_) => initialHookState({ entries, uniqueKey, initialValue }) // initial state
|
||||
);
|
||||
|
||||
const toggleAllRef = React.useRef(null);
|
||||
const toggleAllRef = useRef<any>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (toggleAllRef.current != null) {
|
||||
let some = state.selectedEntries.size > 0;
|
||||
let all = false;
|
||||
@@ -130,22 +165,22 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.selectedEntries]);
|
||||
|
||||
const reset = React.useCallback(
|
||||
const reset = useCallback(
|
||||
() => dispatch(actions.updateAll(initialValue)),
|
||||
[initialValue]
|
||||
);
|
||||
|
||||
const onChange = React.useCallback(
|
||||
const onChange = useCallback(
|
||||
(key, value) => dispatch(actions.update({ key, value })),
|
||||
[]
|
||||
);
|
||||
|
||||
const updateMultiple = React.useCallback(
|
||||
const updateMultiple = useCallback(
|
||||
(entries) => dispatch(actions.updateMultiple(entries)),
|
||||
[]
|
||||
);
|
||||
|
||||
return React.useMemo(() => {
|
||||
return useMemo(() => {
|
||||
function toggleAll(e) {
|
||||
let checked = e.target.checked;
|
||||
if (e.target.indeterminate) {
|
||||
@@ -165,7 +200,10 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
||||
reset,
|
||||
{ name }
|
||||
], {
|
||||
_default,
|
||||
hasChanged: () => true,
|
||||
name,
|
||||
Name: "",
|
||||
value: state.entries,
|
||||
onChange,
|
||||
selectedValues,
|
||||
@@ -178,4 +216,4 @@ module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "ke
|
||||
}
|
||||
});
|
||||
}, [state, reset, name, onChange, updateMultiple]);
|
||||
};
|
||||
}
|
@@ -17,13 +17,21 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import { useState } from "react";
|
||||
|
||||
const { useComboboxState } = require("ariakit/combobox");
|
||||
import { useComboboxState } from "ariakit/combobox";
|
||||
import {
|
||||
ComboboxFormInputHook,
|
||||
CreateHookNames,
|
||||
HookOpts,
|
||||
} from "./types";
|
||||
|
||||
const _default = "";
|
||||
module.exports = function useComboBoxInput({ name, Name }, { initialValue = _default }) {
|
||||
const [isNew, setIsNew] = React.useState(false);
|
||||
export default function useComboBoxInput(
|
||||
{ name, Name }: CreateHookNames,
|
||||
{ initialValue = _default }: HookOpts<string>
|
||||
): ComboboxFormInputHook {
|
||||
const [isNew, setIsNew] = useState(false);
|
||||
|
||||
const state = useComboboxState({
|
||||
defaultValue: initialValue,
|
||||
@@ -45,14 +53,15 @@ module.exports = function useComboBoxInput({ name, Name }, { initialValue = _def
|
||||
[`set${Name}IsNew`]: setIsNew
|
||||
}
|
||||
], {
|
||||
reset,
|
||||
name,
|
||||
Name: "", // Will be set by inputHook function.
|
||||
state,
|
||||
value: state.value,
|
||||
setter: (val) => state.setValue(val),
|
||||
setter: (val: string) => state.setValue(val),
|
||||
hasChanged: () => state.value != initialValue,
|
||||
isNew,
|
||||
setIsNew,
|
||||
reset,
|
||||
_default
|
||||
});
|
||||
};
|
||||
}
|
@@ -17,12 +17,19 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import { useRef, useMemo } from "react";
|
||||
|
||||
const getFormMutations = require("./get-form-mutations");
|
||||
import getFormMutations from "./get-form-mutations";
|
||||
|
||||
function parseFields(entries, length) {
|
||||
const fields = [];
|
||||
import type {
|
||||
CreateHookNames,
|
||||
HookOpts,
|
||||
FieldArrayInputHook,
|
||||
HookedForm,
|
||||
} from "./types";
|
||||
|
||||
function parseFields(entries: HookedForm[], length: number): HookedForm[] {
|
||||
const fields: HookedForm[] = [];
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (entries[i] != undefined) {
|
||||
@@ -35,23 +42,38 @@ function parseFields(entries, length) {
|
||||
return fields;
|
||||
}
|
||||
|
||||
module.exports = function useArrayInput({ name, _Name }, { initialValue, length = 0 }) {
|
||||
const fields = React.useRef({});
|
||||
export default function useArrayInput(
|
||||
{ name }: CreateHookNames,
|
||||
{
|
||||
initialValue,
|
||||
length = 0,
|
||||
}: HookOpts,
|
||||
): FieldArrayInputHook {
|
||||
const _default: HookedForm[] = Array(length);
|
||||
const fields = useRef<HookedForm[]>(_default);
|
||||
|
||||
const value = React.useMemo(() => parseFields(initialValue, length), [initialValue, length]);
|
||||
const value = useMemo(
|
||||
() => parseFields(initialValue, length),
|
||||
[initialValue, length],
|
||||
);
|
||||
|
||||
function hasUpdate() {
|
||||
return Object.values(fields.current).some((fieldSet) => {
|
||||
const { updatedFields } = getFormMutations(fieldSet, { changedOnly: true });
|
||||
return updatedFields.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
_default,
|
||||
name,
|
||||
Name: "",
|
||||
value,
|
||||
ctx: fields.current,
|
||||
maxLength: length,
|
||||
hasChanged: hasUpdate,
|
||||
selectedValues() {
|
||||
// if any form field changed, we need to re-send everything
|
||||
const hasUpdate = Object.values(fields.current).some((fieldSet) => {
|
||||
const { updatedFields } = getFormMutations(fieldSet, { changedOnly: true });
|
||||
return updatedFields.length > 0;
|
||||
});
|
||||
if (hasUpdate) {
|
||||
if (hasUpdate()) {
|
||||
return Object.values(fields.current).map((fieldSet) => {
|
||||
return getFormMutations(fieldSet, { changedOnly: false }).mutationData;
|
||||
});
|
||||
@@ -60,4 +82,4 @@ module.exports = function useArrayInput({ name, _Name }, { initialValue, length
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
@@ -17,47 +17,67 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
const prettierBytes = require("prettier-bytes");
|
||||
import React from "react";
|
||||
|
||||
module.exports = function useFileInput({ name, _Name }, {
|
||||
withPreview,
|
||||
maxSize,
|
||||
initialInfo = "no file selected"
|
||||
} = {}) {
|
||||
const [file, setFile] = React.useState();
|
||||
const [imageURL, setImageURL] = React.useState();
|
||||
const [info, setInfo] = React.useState();
|
||||
import { useState } from "react";
|
||||
import prettierBytes from "prettier-bytes";
|
||||
|
||||
function onChange(e) {
|
||||
let file = e.target.files[0];
|
||||
import type {
|
||||
CreateHookNames,
|
||||
HookOpts,
|
||||
FileFormInputHook,
|
||||
} from "./types";
|
||||
|
||||
const _default = undefined;
|
||||
export default function useFileInput(
|
||||
{ name }: CreateHookNames,
|
||||
{
|
||||
withPreview,
|
||||
maxSize,
|
||||
initialInfo = "no file selected"
|
||||
}: HookOpts<File>
|
||||
): FileFormInputHook {
|
||||
const [file, setFile] = useState<File>();
|
||||
const [imageURL, setImageURL] = useState<string>();
|
||||
const [info, setInfo] = useState<React.JSX.Element>();
|
||||
|
||||
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const files = e.target.files;
|
||||
if (!files) {
|
||||
setInfo(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
let file = files[0];
|
||||
setFile(file);
|
||||
|
||||
URL.revokeObjectURL(imageURL);
|
||||
|
||||
if (file != undefined) {
|
||||
if (withPreview) {
|
||||
setImageURL(URL.createObjectURL(file));
|
||||
}
|
||||
|
||||
let size = prettierBytes(file.size);
|
||||
if (maxSize && file.size > maxSize) {
|
||||
size = <span className="error-text">{size}</span>;
|
||||
}
|
||||
|
||||
setInfo(<>
|
||||
{file.name} ({size})
|
||||
</>);
|
||||
} else {
|
||||
setInfo();
|
||||
if (imageURL) {
|
||||
URL.revokeObjectURL(imageURL);
|
||||
}
|
||||
|
||||
if (withPreview) {
|
||||
setImageURL(URL.createObjectURL(file));
|
||||
}
|
||||
|
||||
let size = prettierBytes(file.size);
|
||||
if (maxSize && file.size > maxSize) {
|
||||
size = <span className="error-text">{size}</span>;
|
||||
}
|
||||
|
||||
setInfo(
|
||||
<>
|
||||
{file.name} ({size})
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function reset() {
|
||||
URL.revokeObjectURL(imageURL);
|
||||
setImageURL();
|
||||
setFile();
|
||||
setInfo();
|
||||
if (imageURL) {
|
||||
URL.revokeObjectURL(imageURL);
|
||||
}
|
||||
setImageURL(undefined);
|
||||
setFile(undefined);
|
||||
setInfo(undefined);
|
||||
}
|
||||
|
||||
const infoComponent = (
|
||||
@@ -82,9 +102,11 @@ module.exports = function useFileInput({ name, _Name }, {
|
||||
onChange,
|
||||
reset,
|
||||
name,
|
||||
Name: "", // Will be set by inputHook function.
|
||||
value: file,
|
||||
previewValue: imageURL,
|
||||
hasChanged: () => file != undefined,
|
||||
infoComponent
|
||||
infoComponent,
|
||||
_default,
|
||||
});
|
||||
};
|
||||
}
|
@@ -17,14 +17,31 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
const { Error } = require("../../components/error");
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
const Loading = require("../../components/loading");
|
||||
import React from "react";
|
||||
|
||||
// Wrap Form component inside component that fires the RTK Query call,
|
||||
// so Form will only be rendered when data is available to generate form-fields for
|
||||
module.exports = function FormWithData({ dataQuery, DataForm, queryArg, ...formProps }) {
|
||||
import { Error } from "../../components/error";
|
||||
import Loading from "../../components/loading";
|
||||
import { NoArg } from "../types/query";
|
||||
import { FormWithDataQuery } from "./types";
|
||||
|
||||
export interface FormWithDataProps {
|
||||
dataQuery: FormWithDataQuery,
|
||||
DataForm: ({ data, ...props }) => React.JSX.Element,
|
||||
queryArg?: any,
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap Form component inside component that fires the RTK Query call, so Form
|
||||
* will only be rendered when data is available to generate form-fields for.
|
||||
*/
|
||||
export default function FormWithData({ dataQuery, DataForm, queryArg, ...props }: FormWithDataProps) {
|
||||
if (!queryArg) {
|
||||
queryArg = NoArg;
|
||||
}
|
||||
|
||||
// Trigger provided query.
|
||||
const { data, isLoading, isError, error } = dataQuery(queryArg);
|
||||
|
||||
if (isLoading) {
|
||||
@@ -38,6 +55,6 @@ module.exports = function FormWithData({ dataQuery, DataForm, queryArg, ...formP
|
||||
<Error error={error} />
|
||||
);
|
||||
} else {
|
||||
return <DataForm data={data} {...formProps} />;
|
||||
return <DataForm data={data} {...props} />;
|
||||
}
|
||||
};
|
||||
}
|
@@ -17,29 +17,31 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const syncpipe = require("syncpipe");
|
||||
import { FormInputHook, HookedForm } from "./types";
|
||||
|
||||
export default function getFormMutations(
|
||||
form: HookedForm,
|
||||
{ changedOnly }: { changedOnly: boolean },
|
||||
) {
|
||||
const updatedFields: FormInputHook[] = [];
|
||||
const mutationData: Array<[string, any]> = [];
|
||||
|
||||
Object.values(form).forEach((field) => {
|
||||
if ("selectedValues" in field) {
|
||||
// FieldArrayInputHook.
|
||||
const selected = field.selectedValues();
|
||||
if (!changedOnly || selected.length > 0) {
|
||||
updatedFields.push(field);
|
||||
mutationData.push([field.name, selected]);
|
||||
}
|
||||
} else if (!changedOnly || field.hasChanged()) {
|
||||
updatedFields.push(field);
|
||||
mutationData.push([field.name, field.value]);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = function getFormMutations(form, { changedOnly }) {
|
||||
let updatedFields = [];
|
||||
return {
|
||||
updatedFields,
|
||||
mutationData: syncpipe(form, [
|
||||
(_) => Object.values(_),
|
||||
(_) => _.map((field) => {
|
||||
if (field.selectedValues != undefined) {
|
||||
let selected = field.selectedValues();
|
||||
if (!changedOnly || selected.length > 0) {
|
||||
updatedFields.push(field);
|
||||
return [field.name, selected];
|
||||
}
|
||||
} else if (!changedOnly || field.hasChanged()) {
|
||||
updatedFields.push(field);
|
||||
return [field.name, field.value];
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
(_) => _.filter((value) => value != null),
|
||||
(_) => Object.fromEntries(_)
|
||||
])
|
||||
mutationData: Object.fromEntries(mutationData),
|
||||
};
|
||||
};
|
||||
}
|
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
const getByDot = require("get-by-dot").default;
|
||||
|
||||
function capitalizeFirst(str) {
|
||||
return str.slice(0, 1).toUpperCase + str.slice(1);
|
||||
}
|
||||
|
||||
function selectorByKey(key) {
|
||||
if (key.includes("[")) {
|
||||
// get-by-dot does not support 'nested[deeper][key]' notation, convert to 'nested.deeper.key'
|
||||
key = key
|
||||
.replace(/\[/g, ".") // nested.deeper].key]
|
||||
.replace(/\]/g, ""); // nested.deeper.key
|
||||
}
|
||||
|
||||
return function selector(obj) {
|
||||
if (obj == undefined) {
|
||||
return undefined;
|
||||
} else {
|
||||
return getByDot(obj, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function makeHook(hookFunction) {
|
||||
return function (name, opts = {}) {
|
||||
// for dynamically generating attributes like 'setName'
|
||||
const Name = React.useMemo(() => capitalizeFirst(name), [name]);
|
||||
|
||||
const selector = React.useMemo(() => selectorByKey(name), [name]);
|
||||
const valueSelector = opts.valueSelector ?? selector;
|
||||
|
||||
opts.initialValue = React.useMemo(() => {
|
||||
if (opts.source == undefined) {
|
||||
return opts.defaultValue;
|
||||
} else {
|
||||
return valueSelector(opts.source) ?? opts.defaultValue;
|
||||
}
|
||||
}, [opts.source, opts.defaultValue, valueSelector]);
|
||||
|
||||
const hook = hookFunction({ name, Name }, opts);
|
||||
|
||||
return Object.assign(hook, {
|
||||
name, Name,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
useTextInput: makeHook(require("./text")),
|
||||
useFileInput: makeHook(require("./file")),
|
||||
useBoolInput: makeHook(require("./bool")),
|
||||
useRadioInput: makeHook(require("./radio")),
|
||||
useComboBoxInput: makeHook(require("./combo-box")),
|
||||
useCheckListInput: makeHook(require("./check-list")),
|
||||
useFieldArrayInput: makeHook(require("./field-array")),
|
||||
useValue: function (name, value) {
|
||||
return {
|
||||
name,
|
||||
value,
|
||||
hasChanged: () => true // always included
|
||||
};
|
||||
}
|
||||
};
|
114
web/source/settings/lib/form/index.ts
Normal file
114
web/source/settings/lib/form/index.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useMemo } from "react";
|
||||
import getByDot from "get-by-dot";
|
||||
|
||||
import text from "./text";
|
||||
import file from "./file";
|
||||
import bool from "./bool";
|
||||
import radio from "./radio";
|
||||
import combobox from "./combo-box";
|
||||
import checklist from "./check-list";
|
||||
import fieldarray from "./field-array";
|
||||
|
||||
import type {
|
||||
CreateHook,
|
||||
FormInputHook,
|
||||
HookOpts,
|
||||
TextFormInputHook,
|
||||
RadioFormInputHook,
|
||||
FileFormInputHook,
|
||||
BoolFormInputHook,
|
||||
ComboboxFormInputHook,
|
||||
FieldArrayInputHook,
|
||||
ChecklistInputHook,
|
||||
} from "./types";
|
||||
|
||||
function capitalizeFirst(str: string) {
|
||||
return str.slice(0, 1).toUpperCase + str.slice(1);
|
||||
}
|
||||
|
||||
function selectorByKey(key: string) {
|
||||
if (key.includes("[")) {
|
||||
// get-by-dot does not support 'nested[deeper][key]' notation, convert to 'nested.deeper.key'
|
||||
key = key
|
||||
.replace(/\[/g, ".") // nested.deeper].key]
|
||||
.replace(/\]/g, ""); // nested.deeper.key
|
||||
}
|
||||
|
||||
return function selector(obj) {
|
||||
if (obj == undefined) {
|
||||
return undefined;
|
||||
} else {
|
||||
return getByDot(obj, key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Memoized hook generator function. Take a createHook
|
||||
* function and use it to return a new FormInputHook function.
|
||||
*
|
||||
* @param createHook
|
||||
* @returns
|
||||
*/
|
||||
function inputHook(createHook: CreateHook): (_name: string, _opts: HookOpts) => FormInputHook {
|
||||
return (name: string, opts?: HookOpts): FormInputHook => {
|
||||
// for dynamically generating attributes like 'setName'
|
||||
const Name = useMemo(() => capitalizeFirst(name), [name]);
|
||||
const selector = useMemo(() => selectorByKey(name), [name]);
|
||||
const valueSelector = opts?.valueSelector?? selector;
|
||||
|
||||
if (opts) {
|
||||
opts.initialValue = useMemo(() => {
|
||||
if (opts.source == undefined) {
|
||||
return opts.defaultValue;
|
||||
} else {
|
||||
return valueSelector(opts.source) ?? opts.defaultValue;
|
||||
}
|
||||
}, [opts.source, opts.defaultValue, valueSelector]);
|
||||
}
|
||||
|
||||
const hook = createHook({ name, Name }, opts ?? {});
|
||||
return Object.assign(hook, { name, Name });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Simplest form hook type in town.
|
||||
*/
|
||||
function value<T>(name: string, initialValue: T) {
|
||||
return {
|
||||
_default: initialValue,
|
||||
name,
|
||||
Name: "",
|
||||
value: initialValue,
|
||||
hasChanged: () => true, // always included
|
||||
};
|
||||
}
|
||||
|
||||
export const useTextInput = inputHook(text) as (_name: string, _opts?: HookOpts<string>) => TextFormInputHook;
|
||||
export const useFileInput = inputHook(file) as (_name: string, _opts?: HookOpts<File>) => FileFormInputHook;
|
||||
export const useBoolInput = inputHook(bool) as (_name: string, _opts?: HookOpts<boolean>) => BoolFormInputHook;
|
||||
export const useRadioInput = inputHook(radio) as (_name: string, _opts?: HookOpts<string>) => RadioFormInputHook;
|
||||
export const useComboBoxInput = inputHook(combobox) as (_name: string, _opts?: HookOpts<string>) => ComboboxFormInputHook;
|
||||
export const useCheckListInput = inputHook(checklist) as (_name: string, _opts?: HookOpts<boolean>) => ChecklistInputHook;
|
||||
export const useFieldArrayInput = inputHook(fieldarray) as (_name: string, _opts?: HookOpts<string>) => FieldArrayInputHook;
|
||||
export const useValue = value as <T>(_name: string, _initialValue: T) => FormInputHook<T>;
|
@@ -17,11 +17,18 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import { useState } from "react";
|
||||
import { CreateHookNames, HookOpts, RadioFormInputHook } from "./types";
|
||||
|
||||
const _default = "";
|
||||
module.exports = function useRadioInput({ name, Name }, { initialValue = _default, options }) {
|
||||
const [value, setValue] = React.useState(initialValue);
|
||||
export default function useRadioInput(
|
||||
{ name, Name }: CreateHookNames,
|
||||
{
|
||||
initialValue = _default,
|
||||
options = {},
|
||||
}: HookOpts<string>
|
||||
): RadioFormInputHook {
|
||||
const [value, setValue] = useState(initialValue);
|
||||
|
||||
function onChange(e) {
|
||||
setValue(e.target.value);
|
||||
@@ -40,13 +47,14 @@ module.exports = function useRadioInput({ name, Name }, { initialValue = _defaul
|
||||
[`set${Name}`]: setValue
|
||||
}
|
||||
], {
|
||||
name,
|
||||
onChange,
|
||||
reset,
|
||||
name,
|
||||
Name: "",
|
||||
value,
|
||||
setter: setValue,
|
||||
options,
|
||||
hasChanged: () => value != initialValue,
|
||||
_default
|
||||
});
|
||||
};
|
||||
}
|
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const Promise = require("bluebird");
|
||||
const React = require("react");
|
||||
const getFormMutations = require("./get-form-mutations");
|
||||
|
||||
module.exports = function useFormSubmit(form, mutationQuery, { changedOnly = true, onFinish } = {}) {
|
||||
if (!Array.isArray(mutationQuery)) {
|
||||
throw new ("useFormSubmit: mutationQuery was not an Array. Is a valid useMutation RTK Query provided?");
|
||||
}
|
||||
const [runMutation, result] = mutationQuery;
|
||||
const usedAction = React.useRef(null);
|
||||
return [
|
||||
function submitForm(e) {
|
||||
let action;
|
||||
if (e?.preventDefault) {
|
||||
e.preventDefault();
|
||||
action = e.nativeEvent.submitter.name;
|
||||
} else {
|
||||
action = e;
|
||||
}
|
||||
|
||||
if (action == "") {
|
||||
action = undefined;
|
||||
}
|
||||
usedAction.current = action;
|
||||
// transform the field definitions into an object with just their values
|
||||
|
||||
const { mutationData, updatedFields } = getFormMutations(form, { changedOnly });
|
||||
|
||||
if (updatedFields.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutationData.action = action;
|
||||
|
||||
return Promise.try(() => {
|
||||
return runMutation(mutationData);
|
||||
}).then((res) => {
|
||||
if (onFinish) {
|
||||
return onFinish(res);
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
...result,
|
||||
action: usedAction.current
|
||||
}
|
||||
];
|
||||
};
|
140
web/source/settings/lib/form/submit.ts
Normal file
140
web/source/settings/lib/form/submit.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import getFormMutations from "./get-form-mutations";
|
||||
|
||||
import { useRef } from "react";
|
||||
|
||||
import type {
|
||||
MutationTrigger,
|
||||
UseMutationStateResult,
|
||||
} from "@reduxjs/toolkit/dist/query/react/buildHooks";
|
||||
|
||||
import type {
|
||||
FormSubmitEvent,
|
||||
FormSubmitFunction,
|
||||
FormSubmitResult,
|
||||
HookedForm,
|
||||
} from "./types";
|
||||
|
||||
interface UseFormSubmitOptions {
|
||||
changedOnly: boolean;
|
||||
onFinish?: ((_res: any) => void);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse changed values from the hooked form into a request
|
||||
* body, and submit it using the given mutation trigger.
|
||||
*
|
||||
* This function basically wraps RTK Query's submit methods to
|
||||
* work with our hooked form interface.
|
||||
*
|
||||
* An `onFinish` callback function can be provided, which will
|
||||
* be executed on a **successful** run of the given MutationTrigger,
|
||||
* with the mutation result passed into it.
|
||||
*
|
||||
* If `changedOnly` is false, then **all** fields of the given HookedForm
|
||||
* will be submitted to the mutation endpoint, not just changed ones.
|
||||
*
|
||||
* The returned function and result can be triggered and read
|
||||
* from just like an RTK Query mutation hook result would be.
|
||||
*
|
||||
* See: https://redux-toolkit.js.org/rtk-query/usage/mutations#mutation-hook-behavior
|
||||
*/
|
||||
export default function useFormSubmit(
|
||||
form: HookedForm,
|
||||
mutationQuery: readonly [MutationTrigger<any>, UseMutationStateResult<any, any>],
|
||||
opts: UseFormSubmitOptions = { changedOnly: true }
|
||||
): [ FormSubmitFunction, FormSubmitResult ] {
|
||||
if (!Array.isArray(mutationQuery)) {
|
||||
throw "useFormSubmit: mutationQuery was not an Array. Is a valid useMutation RTK Query provided?";
|
||||
}
|
||||
|
||||
const { changedOnly, onFinish } = opts;
|
||||
const [runMutation, mutationResult] = mutationQuery;
|
||||
const usedAction = useRef<FormSubmitEvent>(undefined);
|
||||
|
||||
const submitForm = async(e: FormSubmitEvent) => {
|
||||
let action: FormSubmitEvent;
|
||||
|
||||
if (typeof e === "string") {
|
||||
if (e !== "") {
|
||||
// String action name was provided.
|
||||
action = e;
|
||||
} else {
|
||||
// Empty string action name was provided.
|
||||
action = undefined;
|
||||
}
|
||||
} else if (e) {
|
||||
// Submit event action was provided.
|
||||
e.preventDefault();
|
||||
if (e.nativeEvent.submitter) {
|
||||
// We want the name of the element that was invoked to submit this form,
|
||||
// which will be something that extends HTMLElement, though we don't know
|
||||
// what at this point.
|
||||
//
|
||||
// See: https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent/submitter
|
||||
action = (e.nativeEvent.submitter as Object as { name: string }).name;
|
||||
} else {
|
||||
// No submitter defined. Fall back
|
||||
// to just use the FormSubmitEvent.
|
||||
action = e;
|
||||
}
|
||||
} else {
|
||||
// Void or null or something
|
||||
// else was provided.
|
||||
action = undefined;
|
||||
}
|
||||
|
||||
usedAction.current = action;
|
||||
|
||||
// Transform the hooked form into an object.
|
||||
const {
|
||||
mutationData,
|
||||
updatedFields,
|
||||
} = getFormMutations(form, { changedOnly });
|
||||
|
||||
// If there were no updated fields according to
|
||||
// the form parsing then there's nothing for us
|
||||
// to do, since remote and desired state match.
|
||||
if (updatedFields.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
mutationData.action = action;
|
||||
|
||||
try {
|
||||
const res = await runMutation(mutationData);
|
||||
if (onFinish) {
|
||||
onFinish(res);
|
||||
}
|
||||
} catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`caught error running mutation: ${e}`);
|
||||
}
|
||||
};
|
||||
|
||||
return [
|
||||
submitForm,
|
||||
{
|
||||
...mutationResult,
|
||||
action: usedAction.current
|
||||
}
|
||||
];
|
||||
}
|
@@ -17,26 +17,40 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const React = require("react");
|
||||
import React, {
|
||||
useState,
|
||||
useRef,
|
||||
useTransition,
|
||||
useEffect,
|
||||
} from "react";
|
||||
|
||||
import type {
|
||||
CreateHookNames,
|
||||
HookOpts,
|
||||
TextFormInputHook,
|
||||
} from "./types";
|
||||
|
||||
const _default = "";
|
||||
module.exports = function useTextInput({ name, Name }, {
|
||||
initialValue = _default,
|
||||
dontReset = false,
|
||||
validator,
|
||||
showValidation = true,
|
||||
initValidation
|
||||
} = {}) {
|
||||
|
||||
const [text, setText] = React.useState(initialValue);
|
||||
const textRef = React.useRef(null);
|
||||
export default function useTextInput(
|
||||
{ name, Name }: CreateHookNames,
|
||||
{
|
||||
initialValue = _default,
|
||||
dontReset = false,
|
||||
validator,
|
||||
showValidation = true,
|
||||
initValidation
|
||||
}: HookOpts<string>
|
||||
): TextFormInputHook {
|
||||
const [text, setText] = useState(initialValue);
|
||||
const textRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [validation, setValidation] = React.useState(initValidation ?? "");
|
||||
const [_isValidating, startValidation] = React.useTransition();
|
||||
let valid = validation == "";
|
||||
const [validation, setValidation] = useState(initValidation ?? "");
|
||||
const [_isValidating, startValidation] = useTransition();
|
||||
const valid = validation == "";
|
||||
|
||||
function onChange(e) {
|
||||
let input = e.target.value;
|
||||
function onChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const input = e.target.value;
|
||||
setText(input);
|
||||
|
||||
if (validator) {
|
||||
@@ -52,7 +66,7 @@ module.exports = function useTextInput({ name, Name }, {
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (validator && textRef.current) {
|
||||
if (showValidation) {
|
||||
textRef.current.setCustomValidity(validation);
|
||||
@@ -76,12 +90,13 @@ module.exports = function useTextInput({ name, Name }, {
|
||||
onChange,
|
||||
reset,
|
||||
name,
|
||||
Name: "", // Will be set by inputHook function.
|
||||
value: text,
|
||||
ref: textRef,
|
||||
setter: setText,
|
||||
valid,
|
||||
validate: () => setValidation(validator(text)),
|
||||
validate: () => setValidation(validator ? validator(text): ""),
|
||||
hasChanged: () => text != initialValue,
|
||||
_default
|
||||
});
|
||||
};
|
||||
}
|
264
web/source/settings/lib/form/types.ts
Normal file
264
web/source/settings/lib/form/types.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
import { ComboboxState } from "ariakit";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
Dispatch,
|
||||
RefObject,
|
||||
SetStateAction,
|
||||
SyntheticEvent,
|
||||
} from "react";
|
||||
|
||||
export interface CreateHookNames {
|
||||
name: string;
|
||||
Name: string;
|
||||
}
|
||||
|
||||
export interface HookOpts<T = any> {
|
||||
initialValue?: T,
|
||||
defaultValue?: T,
|
||||
|
||||
dontReset?: boolean,
|
||||
validator?,
|
||||
showValidation?: boolean,
|
||||
initValidation?: string,
|
||||
length?: number;
|
||||
options?: { [_: string]: string },
|
||||
withPreview?: boolean,
|
||||
maxSize?,
|
||||
initialInfo?: string;
|
||||
valueSelector?: Function,
|
||||
source?,
|
||||
|
||||
// checklist input types
|
||||
entries?: any[];
|
||||
uniqueKey?: string;
|
||||
}
|
||||
|
||||
export type CreateHook = (
|
||||
name: CreateHookNames,
|
||||
opts: HookOpts,
|
||||
) => FormInputHook;
|
||||
|
||||
export interface FormInputHook<T = any> {
|
||||
/**
|
||||
* Name of this FormInputHook, as provided
|
||||
* in the UseFormInputHook options.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* `name` with first letter capitalized.
|
||||
*/
|
||||
Name: string;
|
||||
|
||||
/**
|
||||
* Current value of this FormInputHook.
|
||||
*/
|
||||
value?: T;
|
||||
|
||||
/**
|
||||
* Default value of this FormInputHook.
|
||||
*/
|
||||
_default: T;
|
||||
|
||||
/**
|
||||
* Return true if the values of this hook is considered
|
||||
* to have been changed from the default / initial value.
|
||||
*/
|
||||
hasChanged: () => boolean;
|
||||
}
|
||||
|
||||
interface _withReset {
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
interface _withOnChange {
|
||||
onChange: ChangeEventHandler;
|
||||
}
|
||||
|
||||
interface _withSetter<T> {
|
||||
setter: Dispatch<SetStateAction<T>>;
|
||||
}
|
||||
|
||||
interface _withValidate {
|
||||
valid: boolean;
|
||||
validate: () => void;
|
||||
}
|
||||
|
||||
interface _withRef {
|
||||
ref: RefObject<HTMLElement>;
|
||||
}
|
||||
|
||||
interface _withFile {
|
||||
previewValue?: string;
|
||||
infoComponent: React.JSX.Element;
|
||||
}
|
||||
|
||||
interface _withComboboxState {
|
||||
state: ComboboxState;
|
||||
}
|
||||
|
||||
interface _withNew {
|
||||
isNew: boolean;
|
||||
setIsNew: Dispatch<SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
interface _withSelectedValues {
|
||||
selectedValues: () => {
|
||||
[_: string]: any;
|
||||
}[]
|
||||
}
|
||||
|
||||
interface _withCtx {
|
||||
ctx
|
||||
}
|
||||
|
||||
interface _withMaxLength {
|
||||
maxLength: number;
|
||||
}
|
||||
|
||||
interface _withOptions {
|
||||
options: { [_: string]: string };
|
||||
}
|
||||
|
||||
interface _withToggleAll {
|
||||
toggleAll: _withRef & _withOnChange
|
||||
}
|
||||
|
||||
interface _withSomeSelected {
|
||||
someSelected: boolean;
|
||||
}
|
||||
|
||||
interface _withUpdateMultiple {
|
||||
updateMultiple: (_entries: any) => void;
|
||||
}
|
||||
|
||||
export interface TextFormInputHook extends FormInputHook<string>,
|
||||
_withSetter<string>,
|
||||
_withOnChange,
|
||||
_withReset,
|
||||
_withValidate,
|
||||
_withRef {}
|
||||
|
||||
export interface RadioFormInputHook extends FormInputHook<string>,
|
||||
_withSetter<string>,
|
||||
_withOnChange,
|
||||
_withOptions,
|
||||
_withReset {}
|
||||
|
||||
export interface FileFormInputHook extends FormInputHook<File | undefined>,
|
||||
_withOnChange,
|
||||
_withReset,
|
||||
Partial<_withRef>,
|
||||
_withFile {}
|
||||
|
||||
export interface BoolFormInputHook extends FormInputHook<boolean>,
|
||||
_withSetter<boolean>,
|
||||
_withOnChange,
|
||||
_withReset {}
|
||||
|
||||
export interface ComboboxFormInputHook extends FormInputHook<string>,
|
||||
_withSetter<string>,
|
||||
_withComboboxState,
|
||||
_withNew,
|
||||
_withReset {}
|
||||
|
||||
export interface FieldArrayInputHook extends FormInputHook<HookedForm[]>,
|
||||
_withSelectedValues,
|
||||
_withMaxLength,
|
||||
_withCtx {}
|
||||
|
||||
export interface Checkable {
|
||||
key: string;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export interface ChecklistInputHook<T = Checkable> extends FormInputHook<{[k: string]: T}>,
|
||||
_withReset,
|
||||
_withToggleAll,
|
||||
_withSelectedValues,
|
||||
_withSomeSelected,
|
||||
_withUpdateMultiple {
|
||||
// Uses its own funky onChange handler.
|
||||
onChange: (key: any, value: any) => void
|
||||
}
|
||||
|
||||
export type AnyFormInputHook =
|
||||
FormInputHook |
|
||||
TextFormInputHook |
|
||||
RadioFormInputHook |
|
||||
FileFormInputHook |
|
||||
BoolFormInputHook |
|
||||
ComboboxFormInputHook |
|
||||
FieldArrayInputHook |
|
||||
ChecklistInputHook;
|
||||
|
||||
export interface HookedForm {
|
||||
[_: string]: AnyFormInputHook
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for FormSubmitFunction.
|
||||
*/
|
||||
export type FormSubmitEvent = (string | SyntheticEvent<HTMLFormElement, Partial<SubmitEvent>> | undefined | void)
|
||||
|
||||
|
||||
/**
|
||||
* Shadows "trigger" function for useMutation, but can also
|
||||
* be passed to onSubmit property of forms as a handler.
|
||||
*
|
||||
* See: https://redux-toolkit.js.org/rtk-query/usage/mutations#mutation-hook-behavior
|
||||
*/
|
||||
export type FormSubmitFunction = ((_e: FormSubmitEvent) => void)
|
||||
|
||||
/**
|
||||
* Shadows redux mutation hook return values.
|
||||
*
|
||||
* See: https://redux-toolkit.js.org/rtk-query/usage/mutations#frequently-used-mutation-hook-return-values
|
||||
*/
|
||||
export interface FormSubmitResult {
|
||||
/**
|
||||
* Action used to submit the form, if any.
|
||||
*/
|
||||
action: FormSubmitEvent;
|
||||
data: any;
|
||||
error: any;
|
||||
isLoading: boolean;
|
||||
isSuccess: boolean;
|
||||
isError: boolean;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shadows redux query hook return values.
|
||||
*
|
||||
* See: https://redux-toolkit.js.org/rtk-query/usage/queries#frequently-used-query-hook-return-values
|
||||
*/
|
||||
export type FormWithDataQuery = (_queryArg: any) => {
|
||||
data?: any;
|
||||
isLoading: boolean;
|
||||
isError: boolean;
|
||||
error?: any;
|
||||
}
|
Reference in New Issue
Block a user