Make the profile form autosave
This commit is contained in:
		
					parent
					
						
							
								85380c8142
							
						
					
				
			
			
				commit
				
					
						ae40dea7ec
					
				
			
		
					 3 changed files with 38 additions and 49 deletions
				
			
		| 
						 | 
					@ -72,6 +72,7 @@ interface InputFieldProps {
 | 
				
			||||||
  autoCorrect?: string;
 | 
					  autoCorrect?: string;
 | 
				
			||||||
  autoCapitalize?: string;
 | 
					  autoCapitalize?: string;
 | 
				
			||||||
  value?: string;
 | 
					  value?: string;
 | 
				
			||||||
 | 
					  defaultValue?: string;
 | 
				
			||||||
  placeholder?: string;
 | 
					  placeholder?: string;
 | 
				
			||||||
  defaultChecked?: boolean;
 | 
					  defaultChecked?: boolean;
 | 
				
			||||||
  onChange?: (event: ChangeEvent) => void;
 | 
					  onChange?: (event: ChangeEvent) => void;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
 | 
				
			||||||
limitations under the License.
 | 
					limitations under the License.
 | 
				
			||||||
*/
 | 
					*/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { ChangeEvent, useCallback, useState } from "react";
 | 
					import React, { useCallback, useEffect, useRef } from "react";
 | 
				
			||||||
import { MatrixClient } from "matrix-js-sdk/src/client";
 | 
					import { MatrixClient } from "matrix-js-sdk/src/client";
 | 
				
			||||||
import { useTranslation } from "react-i18next";
 | 
					import { useTranslation } from "react-i18next";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Button } from "../button";
 | 
					 | 
				
			||||||
import { useProfile } from "../profile/useProfile";
 | 
					import { useProfile } from "../profile/useProfile";
 | 
				
			||||||
import { FieldRow, InputField, ErrorMessage } from "../input/Input";
 | 
					import { FieldRow, InputField, ErrorMessage } from "../input/Input";
 | 
				
			||||||
import { AvatarInputField } from "../input/AvatarInputField";
 | 
					import { AvatarInputField } from "../input/AvatarInputField";
 | 
				
			||||||
| 
						 | 
					@ -29,52 +28,47 @@ interface Props {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
export function ProfileSettingsTab({ client }: Props) {
 | 
					export function ProfileSettingsTab({ client }: Props) {
 | 
				
			||||||
  const { t } = useTranslation();
 | 
					  const { t } = useTranslation();
 | 
				
			||||||
  const {
 | 
					  const { error, displayName, avatarUrl, saveProfile } = useProfile(client);
 | 
				
			||||||
    error,
 | 
					 | 
				
			||||||
    loading,
 | 
					 | 
				
			||||||
    displayName: initialDisplayName,
 | 
					 | 
				
			||||||
    avatarUrl,
 | 
					 | 
				
			||||||
    saveProfile,
 | 
					 | 
				
			||||||
  } = useProfile(client);
 | 
					 | 
				
			||||||
  const [displayName, setDisplayName] = useState(initialDisplayName || "");
 | 
					 | 
				
			||||||
  const [removeAvatar, setRemoveAvatar] = useState(false);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onRemoveAvatar = useCallback(() => {
 | 
					  const formRef = useRef<HTMLFormElement | null>(null);
 | 
				
			||||||
    setRemoveAvatar(true);
 | 
					
 | 
				
			||||||
 | 
					  const formChanged = useRef(false);
 | 
				
			||||||
 | 
					  const onFormChange = useCallback(() => {
 | 
				
			||||||
 | 
					    formChanged.current = true;
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onChangeDisplayName = useCallback(
 | 
					  const removeAvatar = useRef(false);
 | 
				
			||||||
    (e: ChangeEvent<HTMLInputElement>) => {
 | 
					  const onRemoveAvatar = useCallback(() => {
 | 
				
			||||||
      setDisplayName(e.target.value);
 | 
					    removeAvatar.current = true;
 | 
				
			||||||
    },
 | 
					    formChanged.current = true;
 | 
				
			||||||
    [setDisplayName]
 | 
					  }, []);
 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onSubmit = useCallback(
 | 
					  useEffect(() => {
 | 
				
			||||||
    (e) => {
 | 
					    const form = formRef.current!;
 | 
				
			||||||
      e.preventDefault();
 | 
					    return () => {
 | 
				
			||||||
      const data = new FormData(e.target);
 | 
					      if (formChanged.current) {
 | 
				
			||||||
 | 
					        const data = new FormData(form);
 | 
				
			||||||
        const displayNameDataEntry = data.get("displayName");
 | 
					        const displayNameDataEntry = data.get("displayName");
 | 
				
			||||||
      const avatar: File | string = data.get("avatar");
 | 
					        const avatar = data.get("avatar");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const avatarSize =
 | 
					        const avatarSize =
 | 
				
			||||||
        typeof avatar == "string" ? avatar.length : avatar.size;
 | 
					          typeof avatar == "string" ? avatar.length : avatar?.size ?? 0;
 | 
				
			||||||
        const displayName =
 | 
					        const displayName =
 | 
				
			||||||
          typeof displayNameDataEntry == "string"
 | 
					          typeof displayNameDataEntry == "string"
 | 
				
			||||||
            ? displayNameDataEntry
 | 
					            ? displayNameDataEntry
 | 
				
			||||||
          : displayNameDataEntry.name;
 | 
					            : displayNameDataEntry?.name ?? null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        saveProfile({
 | 
					        saveProfile({
 | 
				
			||||||
          displayName,
 | 
					          displayName,
 | 
				
			||||||
          avatar: avatar && avatarSize > 0 ? avatar : undefined,
 | 
					          avatar: avatar && avatarSize > 0 ? avatar : undefined,
 | 
				
			||||||
        removeAvatar: removeAvatar && (!avatar || avatarSize === 0),
 | 
					          removeAvatar: removeAvatar.current && (!avatar || avatarSize === 0),
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
    },
 | 
					      }
 | 
				
			||||||
    [saveProfile, removeAvatar]
 | 
					    };
 | 
				
			||||||
  );
 | 
					  }, [saveProfile]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <form onSubmit={onSubmit} className={styles.content}>
 | 
					    <form onChange={onFormChange} ref={formRef} className={styles.content}>
 | 
				
			||||||
      <FieldRow className={styles.avatarFieldRow}>
 | 
					      <FieldRow className={styles.avatarFieldRow}>
 | 
				
			||||||
        <AvatarInputField
 | 
					        <AvatarInputField
 | 
				
			||||||
          id="avatar"
 | 
					          id="avatar"
 | 
				
			||||||
| 
						 | 
					@ -92,7 +86,7 @@ export function ProfileSettingsTab({ client }: Props) {
 | 
				
			||||||
          label={t("Username")}
 | 
					          label={t("Username")}
 | 
				
			||||||
          type="text"
 | 
					          type="text"
 | 
				
			||||||
          disabled
 | 
					          disabled
 | 
				
			||||||
          value={client.getUserId()}
 | 
					          value={client.getUserId()!}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </FieldRow>
 | 
					      </FieldRow>
 | 
				
			||||||
      <FieldRow>
 | 
					      <FieldRow>
 | 
				
			||||||
| 
						 | 
					@ -104,8 +98,7 @@ export function ProfileSettingsTab({ client }: Props) {
 | 
				
			||||||
          required
 | 
					          required
 | 
				
			||||||
          autoComplete="off"
 | 
					          autoComplete="off"
 | 
				
			||||||
          placeholder={t("Display name")}
 | 
					          placeholder={t("Display name")}
 | 
				
			||||||
          value={displayName}
 | 
					          defaultValue={displayName}
 | 
				
			||||||
          onChange={onChangeDisplayName}
 | 
					 | 
				
			||||||
          data-testid="profile_displayname"
 | 
					          data-testid="profile_displayname"
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </FieldRow>
 | 
					      </FieldRow>
 | 
				
			||||||
| 
						 | 
					@ -114,11 +107,6 @@ export function ProfileSettingsTab({ client }: Props) {
 | 
				
			||||||
          <ErrorMessage error={error} />
 | 
					          <ErrorMessage error={error} />
 | 
				
			||||||
        </FieldRow>
 | 
					        </FieldRow>
 | 
				
			||||||
      )}
 | 
					      )}
 | 
				
			||||||
      <FieldRow rightAlign>
 | 
					 | 
				
			||||||
        <Button type="submit" disabled={loading}>
 | 
					 | 
				
			||||||
          {loading ? t("Saving…") : t("Save")}
 | 
					 | 
				
			||||||
        </Button>
 | 
					 | 
				
			||||||
      </FieldRow>
 | 
					 | 
				
			||||||
    </form>
 | 
					    </form>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,8 +25,8 @@ import React, {
 | 
				
			||||||
  ReactNode,
 | 
					  ReactNode,
 | 
				
			||||||
  useRef,
 | 
					  useRef,
 | 
				
			||||||
} from "react";
 | 
					} from "react";
 | 
				
			||||||
import { useClient } from "../ClientContext";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useClient } from "../ClientContext";
 | 
				
			||||||
import { getNamedDevices } from "../media-utils";
 | 
					import { getNamedDevices } from "../media-utils";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface MediaHandlerContextInterface {
 | 
					export interface MediaHandlerContextInterface {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue