summaryrefslogtreecommitdiff
path: root/web/source/settings/lib/form
diff options
context:
space:
mode:
Diffstat (limited to 'web/source/settings/lib/form')
-rw-r--r--web/source/settings/lib/form/bool.jsx50
-rw-r--r--web/source/settings/lib/form/check-list.jsx147
-rw-r--r--web/source/settings/lib/form/combo-box.jsx56
-rw-r--r--web/source/settings/lib/form/file.jsx91
-rw-r--r--web/source/settings/lib/form/form-with-data.jsx39
-rw-r--r--web/source/settings/lib/form/index.js46
-rw-r--r--web/source/settings/lib/form/radio.jsx51
-rw-r--r--web/source/settings/lib/form/submit.js83
-rw-r--r--web/source/settings/lib/form/text.jsx67
9 files changed, 630 insertions, 0 deletions
diff --git a/web/source/settings/lib/form/bool.jsx b/web/source/settings/lib/form/bool.jsx
new file mode 100644
index 000000000..b124abd50
--- /dev/null
+++ b/web/source/settings/lib/form/bool.jsx
@@ -0,0 +1,50 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const React = require("react");
+
+module.exports = function useBoolInput({ name, Name }, { defaultValue = false } = {}) {
+ const [value, setValue] = React.useState(defaultValue);
+
+ function onChange(e) {
+ setValue(e.target.checked);
+ }
+
+ function reset() {
+ setValue(defaultValue);
+ }
+
+ // Array / Object hybrid, for easier access in different contexts
+ return Object.assign([
+ onChange,
+ reset,
+ {
+ [name]: value,
+ [`set${Name}`]: setValue
+ }
+ ], {
+ name,
+ onChange,
+ reset,
+ value,
+ setter: setValue,
+ hasChanged: () => value != defaultValue
+ });
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/check-list.jsx b/web/source/settings/lib/form/check-list.jsx
new file mode 100644
index 000000000..c1233273d
--- /dev/null
+++ b/web/source/settings/lib/form/check-list.jsx
@@ -0,0 +1,147 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const React = require("react");
+const syncpipe = require("syncpipe");
+
+function createState(entries, uniqueKey, oldState, defaultValue) {
+ return syncpipe(entries, [
+ (_) => _.map((entry) => {
+ let key = entry[uniqueKey];
+ return [
+ key,
+ {
+ ...entry,
+ key,
+ checked: oldState[key]?.checked ?? entry.checked ?? defaultValue
+ }
+ ];
+ }),
+ (_) => Object.fromEntries(_)
+ ]);
+}
+
+function updateAllState(state, newValue) {
+ return syncpipe(state, [
+ (_) => Object.values(_),
+ (_) => _.map((entry) => [entry.key, {
+ ...entry,
+ checked: newValue
+ }]),
+ (_) => Object.fromEntries(_)
+ ]);
+}
+
+function updateState(state, key, newValue) {
+ return {
+ ...state,
+ [key]: {
+ ...state[key],
+ ...newValue
+ }
+ };
+}
+
+module.exports = function useCheckListInput({ name }, { entries, uniqueKey = "key", defaultValue = false }) {
+ const [state, setState] = React.useState({});
+
+ const [someSelected, setSomeSelected] = React.useState(false);
+ const [toggleAllState, setToggleAllState] = React.useState(0);
+ const toggleAllRef = React.useRef(null);
+
+ React.useEffect(() => {
+ /*
+ entries changed, update state,
+ re-using old state if available for key
+ */
+ setState(createState(entries, uniqueKey, state, defaultValue));
+
+ /* eslint-disable-next-line react-hooks/exhaustive-deps */
+ }, [entries]);
+
+ React.useEffect(() => {
+ /* Updates (un)check all checkbox, based on shortcode checkboxes
+ Can be 0 (not checked), 1 (checked) or 2 (indeterminate)
+ */
+ if (toggleAllRef.current == null) {
+ return;
+ }
+
+ let values = Object.values(state);
+ /* one or more boxes are checked */
+ let some = values.some((v) => v.checked);
+
+ let all = false;
+ if (some) {
+ /* there's not at least one unchecked box */
+ all = !values.some((v) => v.checked == false);
+ }
+
+ setSomeSelected(some);
+
+ if (some && !all) {
+ setToggleAllState(2);
+ toggleAllRef.current.indeterminate = true;
+ } else {
+ setToggleAllState(all ? 1 : 0);
+ toggleAllRef.current.indeterminate = false;
+ }
+ }, [state, toggleAllRef]);
+
+ function toggleAll(e) {
+ let selectAll = e.target.checked;
+
+ if (toggleAllState == 2) { // indeterminate
+ selectAll = false;
+ }
+
+ setState(updateAllState(state, selectAll));
+ setToggleAllState(selectAll);
+ }
+
+ function reset() {
+ setState(updateAllState(state, defaultValue));
+ }
+
+ function selectedValues() {
+ return syncpipe(state, [
+ (_) => Object.values(_),
+ (_) => _.filter((entry) => entry.checked)
+ ]);
+ }
+
+ return Object.assign([
+ state,
+ reset,
+ { name }
+ ], {
+ name,
+ value: state,
+ onChange: (key, newValue) => setState(updateState(state, key, newValue)),
+ selectedValues,
+ reset,
+ someSelected,
+ toggleAll: {
+ ref: toggleAllRef,
+ value: toggleAllState,
+ onChange: toggleAll
+ }
+ });
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/combo-box.jsx b/web/source/settings/lib/form/combo-box.jsx
new file mode 100644
index 000000000..3e8cea44a
--- /dev/null
+++ b/web/source/settings/lib/form/combo-box.jsx
@@ -0,0 +1,56 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const React = require("react");
+
+const { useComboboxState } = require("ariakit/combobox");
+
+module.exports = function useComboBoxInput({ name, Name }, { defaultValue } = {}) {
+ const [isNew, setIsNew] = React.useState(false);
+
+ const state = useComboboxState({
+ defaultValue,
+ gutter: 0,
+ sameWidth: true
+ });
+
+ function reset() {
+ state.setValue("");
+ }
+
+ return Object.assign([
+ state,
+ reset,
+ {
+ [name]: state.value,
+ name,
+ [`${name}IsNew`]: isNew,
+ [`set${Name}IsNew`]: setIsNew
+ }
+ ], {
+ name,
+ state,
+ value: state.value,
+ hasChanged: () => state.value != defaultValue,
+ isNew,
+ setIsNew,
+ reset
+ });
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/file.jsx b/web/source/settings/lib/form/file.jsx
new file mode 100644
index 000000000..85f23e274
--- /dev/null
+++ b/web/source/settings/lib/form/file.jsx
@@ -0,0 +1,91 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const React = require("react");
+const prettierBytes = require("prettier-bytes");
+
+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();
+
+ function onChange(e) {
+ let file = e.target.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();
+ }
+ }
+
+ function reset() {
+ URL.revokeObjectURL(imageURL);
+ setImageURL();
+ setFile();
+ setInfo();
+ }
+
+ const infoComponent = (
+ <span className="form-info">
+ {info
+ ? info
+ : initialInfo
+ }
+ </span>
+ );
+
+ // Array / Object hybrid, for easier access in different contexts
+ return Object.assign([
+ onChange,
+ reset,
+ {
+ [name]: file,
+ [`${name}URL`]: imageURL,
+ [`${name}Info`]: infoComponent,
+ }
+ ], {
+ onChange,
+ reset,
+ name,
+ value: file,
+ previewValue: imageURL,
+ hasChanged: () => file != undefined,
+ infoComponent
+ });
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/form-with-data.jsx b/web/source/settings/lib/form/form-with-data.jsx
new file mode 100644
index 000000000..a383af502
--- /dev/null
+++ b/web/source/settings/lib/form/form-with-data.jsx
@@ -0,0 +1,39 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const React = require("react");
+
+const Loading = require("../../components/loading");
+
+// 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 }) {
+ const { data, isLoading } = dataQuery(queryArg);
+
+ if (isLoading) {
+ return (
+ <div>
+ <Loading />
+ </div>
+ );
+ } else {
+ return <DataForm data={data} {...formProps} />;
+ }
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/index.js b/web/source/settings/lib/form/index.js
new file mode 100644
index 000000000..aef3bf0d2
--- /dev/null
+++ b/web/source/settings/lib/form/index.js
@@ -0,0 +1,46 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+function capitalizeFirst(str) {
+ return str.slice(0, 1).toUpperCase() + str.slice(1);
+}
+
+function makeHook(func) {
+ return (name, ...args) => func({
+ name,
+ Name: capitalizeFirst(name)
+ }, ...args);
+}
+
+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")),
+ useValue: function (name, value) {
+ return {
+ name,
+ value,
+ hasChanged: () => true // always included
+ };
+ }
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/radio.jsx b/web/source/settings/lib/form/radio.jsx
new file mode 100644
index 000000000..47ab6c726
--- /dev/null
+++ b/web/source/settings/lib/form/radio.jsx
@@ -0,0 +1,51 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const React = require("react");
+
+module.exports = function useRadioInput({ name, Name }, { defaultValue, options } = {}) {
+ const [value, setValue] = React.useState(defaultValue);
+
+ function onChange(e) {
+ setValue(e.target.value);
+ }
+
+ function reset() {
+ setValue(defaultValue);
+ }
+
+ // Array / Object hybrid, for easier access in different contexts
+ return Object.assign([
+ onChange,
+ reset,
+ {
+ [name]: value,
+ [`set${Name}`]: setValue
+ }
+ ], {
+ name,
+ onChange,
+ reset,
+ value,
+ setter: setValue,
+ options,
+ hasChanged: () => value != defaultValue
+ });
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/submit.js b/web/source/settings/lib/form/submit.js
new file mode 100644
index 000000000..6f20165a5
--- /dev/null
+++ b/web/source/settings/lib/form/submit.js
@@ -0,0 +1,83 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const Promise = require("bluebird");
+const React = require("react");
+const syncpipe = require("syncpipe");
+
+module.exports = function useFormSubmit(form, mutationQuery, { changedOnly = true } = {}) {
+ 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, setUsedAction] = React.useState();
+ return [
+ function submitForm(e) {
+ let action;
+ if (e?.preventDefault) {
+ e.preventDefault();
+ action = e.nativeEvent.submitter.name;
+ } else {
+ action = e;
+ }
+
+ if (action == "") {
+ action = undefined;
+ }
+ setUsedAction(action);
+ // transform the field definitions into an object with just their values
+ let updatedFields = [];
+ const 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.action = action;
+
+ return Promise.try(() => {
+ return runMutation(mutationData);
+ }).then((res) => {
+ if (res.error == undefined) {
+ updatedFields.forEach((field) => {
+ field.reset();
+ });
+ }
+ });
+ },
+ {
+ ...result,
+ action: usedAction
+ }
+ ];
+}; \ No newline at end of file
diff --git a/web/source/settings/lib/form/text.jsx b/web/source/settings/lib/form/text.jsx
new file mode 100644
index 000000000..70e61657c
--- /dev/null
+++ b/web/source/settings/lib/form/text.jsx
@@ -0,0 +1,67 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2023 GoToSocial Authors admin@gotosocial.org
+
+ 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/>.
+*/
+
+"use strict";
+
+const React = require("react");
+
+module.exports = function useTextInput({ name, Name }, { validator, defaultValue = "", dontReset = false } = {}) {
+ const [text, setText] = React.useState(defaultValue);
+ const [valid, setValid] = React.useState(true);
+ const textRef = React.useRef(null);
+
+ function onChange(e) {
+ let input = e.target.value;
+ setText(input);
+ }
+
+ function reset() {
+ if (!dontReset) {
+ setText(defaultValue);
+ }
+ }
+
+ React.useEffect(() => {
+ if (validator && textRef.current) {
+ let res = validator(text);
+ setValid(res == "");
+ textRef.current.setCustomValidity(res);
+ }
+ }, [text, textRef, validator]);
+
+ // Array / Object hybrid, for easier access in different contexts
+ return Object.assign([
+ onChange,
+ reset,
+ {
+ [name]: text,
+ [`${name}Ref`]: textRef,
+ [`set${Name}`]: setText,
+ [`${name}Valid`]: valid,
+ }
+ ], {
+ onChange,
+ reset,
+ name,
+ value: text,
+ ref: textRef,
+ setter: setText,
+ valid,
+ hasChanged: () => text != defaultValue
+ });
+}; \ No newline at end of file