From 938328cd077d40b75e0834d56ff8d43ad035fd2b Mon Sep 17 00:00:00 2001
From: f0x52 <f0x@cthu.lu>
Date: Thu, 29 Sep 2022 12:02:41 +0200
Subject: [frontend] Unified panels (#812)

* settings panel restructuring

* clean up old Gin handlers

* colorscheme redesign, some other small css tweaks

* basic router layout, error boundary

* colorscheme redesign, some other small css tweaks

* kebab-case consistency

* superfluous padding on applist

* remove unused consts

* redux, whitespace changes..

* use .jsx extensions for components

* login flow up till app registration

* full redux oauth implementation, with basic error handling

* split oauth api functions

* oauth api revocation handling

* basic profile change submission

* move old dir

* profile overview

* fix keeping track of the wrong instance url (for different instance/api domains)

* use redux state for profile form

* delete old/index.js, old/basic.js, fully implemented

* implement old/user/profile.js

* implement password change

* remove debug logging

* support future api for removing files

* customize profile css

* remove unneeded wrapper components

* restructure form fields

* start on admin pages

* admin panel settings

* admin settings panel

* remove old/admin files

* add top-level redirect

* refactor/cleanup forms

* only do API checks on logged-in state

* admin-status based routing

* federation block routing

* federation blocks

* upgrade dependencies

* react 18 changes

* media cleanup

* fix useEffect hooks

* remove unused require

* custom emoji base

* emoji uploader

* delete last old panel files

* sidebar styling, remove unused page

* refactor submit functions

* fix sidebar boxshadow-border

* fix old css variables

* fix fake-toot avatar

* fix non-square emoji

* fix user settings redux keys

* properly get admin account contact from instance response

* Account.source default values

* source.status_format key

* mobile responsiveness

* mobile element tweaks

* proper redirect after removing block

* add redirects for old setting panel urls

* deletes

* fix mobile overflow

* clean up debug logging calls
---
 .../settings-panel/components/form-fields.jsx      | 167 +++++++++++++++++++++
 1 file changed, 167 insertions(+)
 create mode 100644 web/source/settings-panel/components/form-fields.jsx

(limited to 'web/source/settings-panel/components/form-fields.jsx')

diff --git a/web/source/settings-panel/components/form-fields.jsx b/web/source/settings-panel/components/form-fields.jsx
new file mode 100644
index 000000000..cb402c3b2
--- /dev/null
+++ b/web/source/settings-panel/components/form-fields.jsx
@@ -0,0 +1,167 @@
+/*
+	GoToSocial
+	Copyright (C) 2021-2022 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 Redux = require("react-redux");
+const d = require("dotty");
+const prettierBytes = require("prettier-bytes");
+
+function eventListeners(dispatch, setter, obj) {
+	return {
+		onTextChange: function (key) {
+			return function (e) {
+				dispatch(setter([key, e.target.value]));
+			};
+		},
+		
+		onCheckChange: function (key) {
+			return function (e) {
+				dispatch(setter([key, e.target.checked]));
+			};
+		},
+		
+		onFileChange: function (key, withPreview) {
+			return function (e) {
+				let file = e.target.files[0];
+				if (withPreview) {
+					let old = d.get(obj, key);
+					if (old != undefined) {
+						URL.revokeObjectURL(old); // no error revoking a non-Object URL as provided by instance
+					}
+					let objectURL = URL.createObjectURL(file);
+					dispatch(setter([key, objectURL]));
+				}
+				dispatch(setter([`${key}File`, file]));
+			};
+		}
+	};
+}
+
+function get(state, id, defaultVal) {
+	let value;
+	if (id.includes(".")) {
+		value = d.get(state, id);
+	} else {
+		value = state[id];
+	}
+	if (value == undefined) {
+		value = defaultVal;
+	}
+	return value;
+}
+
+// function removeFile(name) {
+// 	return function(e) {
+// 		e.preventDefault();
+// 		dispatch(user.setProfileVal([name, ""]));
+// 		dispatch(user.setProfileVal([`${name}File`, ""]));
+// 	};
+// }
+
+module.exports = {
+	formFields: function formFields(setter, selector) {
+		function FormField({
+			type, id, name, className="", placeHolder="", fileType="", children=null,
+			options=null, inputProps={}, withPreview=true, showSize=false, maxSize=Infinity
+		}) {
+			const dispatch = Redux.useDispatch();
+			let state = Redux.useSelector(selector);
+			let {
+				onTextChange,
+				onCheckChange,
+				onFileChange
+			} = eventListeners(dispatch, setter, state);
+
+			let field;
+			let defaultLabel = true;
+			if (type == "text") {
+				field = <input type="text" id={id} value={get(state, id, "")} placeholder={placeHolder} className={className} onChange={onTextChange(id)} {...inputProps}/>;
+			} else if (type == "textarea") {
+				field = <textarea type="text" id={id} value={get(state, id, "")} placeholder={placeHolder} className={className} onChange={onTextChange(id)} rows={8} {...inputProps}/>;
+			} else if (type == "checkbox") {
+				field = <input type="checkbox" id={id} checked={get(state, id, false)} className={className} onChange={onCheckChange(id)} {...inputProps}/>;
+			} else if (type == "select") {
+				field = (
+					<select id={id} value={get(state, id, "")} className={className} onChange={onTextChange(id)} {...inputProps}>
+						{options}
+					</select>
+				);
+			} else if (type == "file") {
+				defaultLabel = false;
+				let file = get(state, `${id}File`);
+
+				let size = null;
+				if (showSize && file) {
+					size = `(${prettierBytes(file.size)})`;
+
+					if (file.size > maxSize) {
+						size = <span className="error-text">{size}</span>;
+					}
+				}
+
+				field = (
+					<>
+						<label htmlFor={id} className="file-input button">Browse</label>
+						<span>
+							{file ? file.name : "no file selected"} {size}
+						</span>
+						{/* <a onClick={removeFile("header")}>remove</a> */}
+						<input className="hidden" id={id} type="file" accept={fileType} onChange={onFileChange(id, withPreview)}  {...inputProps}/>
+					</>
+				);
+			} else {
+				defaultLabel = false;
+				field = `unsupported FormField ${type}, this is a developer error`;
+			}
+
+			let label = <label htmlFor={id}>{name}</label>;
+			return (
+				<div className={`form-field ${type}`}>
+					{defaultLabel ? label : null}	{field}
+					{children}
+				</div>
+			);
+		}
+
+		return {
+			TextInput: function(props) {
+				return <FormField type="text" {...props} />;
+			},
+	
+			TextArea: function(props) {
+				return <FormField type="textarea" {...props} />;
+			},
+	
+			Checkbox: function(props) {
+				return <FormField type="checkbox" {...props} />;
+			},
+	
+			Select: function(props) {
+				return <FormField type="select" {...props} />;
+			},
+	
+			File: function(props) {
+				return <FormField type="file" {...props} />;
+			},
+		};
+	},
+
+	eventListeners
+};
\ No newline at end of file
-- 
cgit v1.2.3

