Add profile editor in settings
authorjamesjulich <51384945+jamesjulich@users.noreply.github.com>
Thu, 9 Sep 2021 05:47:26 +0000 (00:47 -0500)
committerjamesjulich <51384945+jamesjulich@users.noreply.github.com>
Thu, 9 Sep 2021 05:47:26 +0000 (00:47 -0500)
public/res/svg/avatar-clip.svg [new file with mode: 0644]
src/app/atoms/image-upload/ImageUpload.jsx [new file with mode: 0644]
src/app/atoms/image-upload/ImageUpload.scss [new file with mode: 0644]
src/app/molecules/profile-editor/ProfileEditor.jsx [new file with mode: 0644]
src/app/molecules/profile-editor/ProfileEditor.scss [new file with mode: 0644]
src/app/organisms/settings/Settings.jsx

diff --git a/public/res/svg/avatar-clip.svg b/public/res/svg/avatar-clip.svg
new file mode 100644 (file)
index 0000000..ffaa1a2
--- /dev/null
@@ -0,0 +1,3 @@
+<svg width="80" height="80" viewBox="0 0 80 80" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0C3.58173 0 0 3.58173 0 8V72C0 76.4183 3.58173 80 8 80H72C76.4183 80 80 76.4183 80 72V26H62C57.5817 26 54 22.4183 54 18V0H8Z" fill="#E24444"/>
+</svg>
diff --git a/src/app/atoms/image-upload/ImageUpload.jsx b/src/app/atoms/image-upload/ImageUpload.jsx
new file mode 100644 (file)
index 0000000..1393052
--- /dev/null
@@ -0,0 +1,62 @@
+import React, { useRef } from 'react';
+import PropTypes from 'prop-types';
+
+import initMatrix from '../../../client/initMatrix';
+
+import GenIC from '../../../../public/res/ic/outlined/settings.svg';
+import Avatar from '../avatar/Avatar';
+
+import RawIcon from '../system-icons/RawIcon';
+import './ImageUpload.scss';
+
+function ImageUpload({
+  text, bgColor, imageSrc, onUpload,
+}) {
+  const uploadImageRef = useRef(null);
+
+  function uploadImage(e) {
+    const file = e.target.files.item(0);
+    if (file !== null) { // TODO Add upload progress spinner
+      initMatrix.matrixClient.uploadContent(file, { onlyContentUri: false }).then((res) => {
+        if (res.content_uri !== null) {
+          onUpload({ content_uri: res.content_uri });
+        }
+      }, (err) => {
+        console.log(err); // TODO Replace with alert banner.
+      });
+    }
+  }
+
+  return (
+    <button type="button" className="img-upload-container" onClick={() => { uploadImageRef.current.click(); }}>
+      <div className="img-upload-mask">
+        <Avatar
+          imageSrc={imageSrc}
+          text={text.slice(0, 1)}
+          bgColor={bgColor}
+          size="large"
+        />
+      </div>
+      <div className="img-upload-icon">
+        <RawIcon size="small" src={GenIC} />
+      </div>
+      <input onChange={uploadImage} style={{ display: 'none' }} ref={uploadImageRef} type="file" />
+    </button>
+  );
+}
+
+ImageUpload.defaultProps = {
+  text: null,
+  bgColor: 'transparent',
+  imageSrc: null,
+  onUpload: null,
+};
+
+ImageUpload.propTypes = {
+  text: PropTypes.string,
+  bgColor: PropTypes.string,
+  imageSrc: PropTypes.string,
+  onUpload: PropTypes.func,
+};
+
+export default ImageUpload;
diff --git a/src/app/atoms/image-upload/ImageUpload.scss b/src/app/atoms/image-upload/ImageUpload.scss
new file mode 100644 (file)
index 0000000..c7118ba
--- /dev/null
@@ -0,0 +1,20 @@
+.img-upload-container {
+       display: flex;
+       flex-direction: row-reverse;
+       width: 80px;
+       height: 80px;
+}
+
+.img-upload-container:hover {
+       cursor: pointer;
+}
+
+.img-upload-mask {
+       mask: url('../../../../public/res/svg/avatar-clip.svg');
+       //width: 80px;
+}
+
+.img-upload-icon {
+  z-index: 1;
+  position: absolute;
+}
\ No newline at end of file
diff --git a/src/app/molecules/profile-editor/ProfileEditor.jsx b/src/app/molecules/profile-editor/ProfileEditor.jsx
new file mode 100644 (file)
index 0000000..818ed93
--- /dev/null
@@ -0,0 +1,66 @@
+import React, { useState, useRef } from 'react';
+import PropTypes from 'prop-types';
+
+import initMatrix from '../../../client/initMatrix';
+import colorMXID from '../../../util/colorMXID';
+
+import Button from '../../atoms/button/Button';
+import ImageUpload from '../../atoms/image-upload/ImageUpload';
+import Input from '../../atoms/input/Input';
+import Text from '../../atoms/text/Text';
+
+import './ProfileEditor.scss';
+
+// TODO Fix bug that prevents 'Save' button from enabling up until second changed.
+function ProfileEditor({
+  userId,
+}) {
+  const mx = initMatrix.matrixClient;
+  const displayNameRef = useRef(null);
+  const bgColor = colorMXID(userId);
+  const [imageSrc, updateImgSrc] = useState(mx.mxcUrlToHttp(mx.getUser(mx.getUserId()).avatarUrl));
+  const [disabled, setDisabled] = useState(true);
+
+  let username = mx.getUser(mx.getUserId()).displayName;
+
+  function handleUpload(e) {
+    mx.setAvatarUrl(e.content_uri);
+    updateImgSrc(mx.mxcUrlToHttp(e.content_uri));
+  }
+
+  function saveDisplayName() {
+    if (displayNameRef.current.value !== null && displayNameRef.current.value !== '') {
+      mx.setDisplayName(displayNameRef.current.value);
+      username = displayNameRef.current.value;
+      setDisabled(true);
+    }
+  }
+
+  function onDisplayNameInputChange() {
+    setDisabled((username === displayNameRef.current.value) || displayNameRef.current.value === '' || displayNameRef.current.value == null);
+  }
+
+  return (
+    <form className="profile-editor">
+      <ImageUpload text={username} bgColor={bgColor} imageSrc={imageSrc} onUpload={handleUpload} />
+      <div className="display-name-input-container">
+        <Text variant="b3">
+          Display name of&nbsp;
+          {mx.getUserId()}
+        </Text>
+        <Input id="display-name-input" onChange={onDisplayNameInputChange} placeholder={mx.getUser(mx.getUserId()).displayName} forwardRef={displayNameRef} />
+      </div>
+      <Button variant="primary" onClick={saveDisplayName} disabled={disabled}>Save</Button>
+    </form>
+  );
+}
+
+ProfileEditor.defaultProps = {
+  userId: null,
+};
+
+ProfileEditor.propTypes = {
+  userId: PropTypes.string,
+};
+
+export default ProfileEditor;
diff --git a/src/app/molecules/profile-editor/ProfileEditor.scss b/src/app/molecules/profile-editor/ProfileEditor.scss
new file mode 100644 (file)
index 0000000..98a453a
--- /dev/null
@@ -0,0 +1,24 @@
+.profile-editor {
+  display: flex;
+  align-items: end;
+}
+
+.img-upload-container {
+  margin-right: var(--sp-normal)
+}
+
+.display-name-input-container {
+  display: flex;
+  flex-direction: column;
+  margin-right: var(--sp-normal);
+  width: 100%;
+  max-width: 400px;
+}
+
+.display-name-input-container > .text-b3 {
+  margin-bottom: var(--sp-ultra-tight)
+}
+
+.profile-editor > .btn-primary {
+  height: 46px;
+}
\ No newline at end of file
index 8914640df195a4ace486173687c168ea3d12eff6..91be164fb67644b5b0cc9ebb8a7ea488ea72b0d2 100644 (file)
@@ -14,8 +14,10 @@ import SegmentedControls from '../../atoms/segmented-controls/SegmentedControls'
 
 import PopupWindow, { PWContentSelector } from '../../molecules/popup-window/PopupWindow';
 import SettingTile from '../../molecules/setting-tile/SettingTile';
+import ProfileEditor from '../../molecules/profile-editor/ProfileEditor';
 import ImportE2ERoomKeys from '../../molecules/import-e2e-room-keys/ImportE2ERoomKeys';
 
+import GenIC from '../../../../public/res/ic/outlined/settings.svg';
 import SunIC from '../../../../public/res/ic/outlined/sun.svg';
 import LockIC from '../../../../public/res/ic/outlined/lock.svg';
 import InfoIC from '../../../../public/res/ic/outlined/info.svg';
@@ -23,6 +25,19 @@ import CrossIC from '../../../../public/res/ic/outlined/cross.svg';
 
 import CinnySVG from '../../../../public/res/svg/cinny.svg';
 
+function GeneralSection() {
+  return (
+    <div className="settings-content">
+      <SettingTile
+        title="Profile"
+        content={(
+          <ProfileEditor userId={initMatrix.matrixClient.getUserId()} />
+        )}
+      />
+    </div>
+  );
+}
+
 function AppearanceSection() {
   const [, updateState] = useState({});
 
@@ -104,6 +119,12 @@ function AboutSection() {
 
 function Settings({ isOpen, onRequestClose }) {
   const settingSections = [{
+    name: 'General',
+    iconSrc: GenIC,
+    render() {
+      return <GeneralSection />;
+    },
+  }, {
     name: 'Appearance',
     iconSrc: SunIC,
     render() {