Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add profile editor in settings #91

Merged
merged 7 commits into from
Sep 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions src/app/molecules/image-upload/ImageUpload.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState, useRef } from 'react';
import PropTypes from 'prop-types';
import './ImageUpload.scss';

import initMatrix from '../../../client/initMatrix';

import Text from '../../atoms/text/Text';
import Avatar from '../../atoms/avatar/Avatar';
import Spinner from '../../atoms/spinner/Spinner';

function ImageUpload({
text, bgColor, imageSrc, onUpload, onRequestRemove,
}) {
const [uploadPromise, setUploadPromise] = useState(null);
const uploadImageRef = useRef(null);

async function uploadImage(e) {
const file = e.target.files.item(0);
if (file === null) return;
try {
const uPromise = initMatrix.matrixClient.uploadContent(file, { onlyContentUri: false });
setUploadPromise(uPromise);

const res = await uPromise;
if (typeof res?.content_uri === 'string') onUpload(res.content_uri);
setUploadPromise(null);
} catch {
setUploadPromise(null);
}
uploadImageRef.current.value = null;
}

function cancelUpload() {
initMatrix.matrixClient.cancelUpload(uploadPromise);
setUploadPromise(null);
uploadImageRef.current.value = null;
}

return (
<div className="img-upload__wrapper">
<button
type="button"
className="img-upload"
onClick={() => {
if (uploadPromise !== null) return;
uploadImageRef.current.click();
}}
>
<Avatar
imageSrc={imageSrc}
text={text.slice(0, 1)}
bgColor={bgColor}
size="large"
/>
<div className={`img-upload__process ${uploadPromise === null ? ' img-upload__process--stopped' : ''}`}>
{uploadPromise === null && <Text variant="b3">Upload</Text>}
{uploadPromise !== null && <Spinner size="small" />}
</div>
</button>
{ (typeof imageSrc === 'string' || uploadPromise !== null) && (
<button
className="img-upload__btn-cancel"
type="button"
onClick={uploadPromise === null ? onRequestRemove : cancelUpload}
>
<Text variant="b3">{uploadPromise ? 'Cancel' : 'Remove'}</Text>
</button>
)}
<input onChange={uploadImage} style={{ display: 'none' }} ref={uploadImageRef} type="file" />
</div>
);
}

ImageUpload.defaultProps = {
text: null,
bgColor: 'transparent',
imageSrc: null,
};

ImageUpload.propTypes = {
text: PropTypes.string,
bgColor: PropTypes.string,
imageSrc: PropTypes.string,
onUpload: PropTypes.func.isRequired,
onRequestRemove: PropTypes.func.isRequired,
};

export default ImageUpload;
50 changes: 50 additions & 0 deletions src/app/molecules/image-upload/ImageUpload.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
.img-upload__wrapper {
display: flex;
flex-direction: column;
align-items: center;
}

.img-upload {
display: flex;
cursor: pointer;
position: relative;

&__process {
width: 100%;
height: 100%;
border-radius: var(--bo-radius);
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, .6);

position: absolute;
left: 0;
right: 0;
z-index: 1;
& .text {
text-transform: uppercase;
font-weight: 600;
color: white;
}
&--stopped {
display: none;
}
& .donut-spinner {
border-color: rgb(255, 255, 255, .3);
border-left-color: white;
}
}
&:hover .img-upload__process--stopped {
display: flex;
}


&__btn-cancel {
margin-top: var(--sp-extra-tight);
cursor: pointer;
& .text {
color: var(--tc-danger-normal)
}
}
}
90 changes: 90 additions & 0 deletions src/app/organisms/profile-editor/ProfileEditor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
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 '../../molecules/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 [avatarSrc, setAvatarSrc] = useState(mx.mxcUrlToHttp(mx.getUser(mx.getUserId()).avatarUrl, 80, 80, 'crop') || null);
const [disabled, setDisabled] = useState(true);

let username = mx.getUser(mx.getUserId()).displayName;

// Sets avatar URL and updates the avatar component in profile editor to reflect new upload
function handleAvatarUpload(url) {
if (url === null) {
if (confirm('Are you sure you want to remove avatar?')) {
mx.setAvatarUrl('');
setAvatarSrc(null);
}
return;
}
mx.setAvatarUrl(url);
setAvatarSrc(mx.mxcUrlToHttp(url, 80, 80, 'crop'));
}

function saveDisplayName() {
const newDisplayName = displayNameRef.current.value;
if (newDisplayName !== null && newDisplayName !== username) {
mx.setDisplayName(newDisplayName);
username = newDisplayName;
setDisabled(true);
}
}

function onDisplayNameInputChange() {
setDisabled(username === displayNameRef.current.value || displayNameRef.current.value == null);
}
function cancelDisplayNameChanges() {
displayNameRef.current.value = username;
onDisplayNameInputChange();
}

return (
<form
className="profile-editor"
onSubmit={(e) => { e.preventDefault(); saveDisplayName(); }}
>
<ImageUpload
text={username}
bgColor={bgColor}
imageSrc={avatarSrc}
onUpload={handleAvatarUpload}
onRequestRemove={() => handleAvatarUpload(null)}
/>
<div className="profile-editor__input-wrapper">
<Input
label={`Display name of ${mx.getUserId()}`}
onChange={onDisplayNameInputChange}
value={mx.getUser(mx.getUserId()).displayName}
forwardRef={displayNameRef}
/>
<Button variant="primary" type="submit" disabled={disabled}>Save</Button>
<Button onClick={cancelDisplayNameChanges}>Cancel</Button>
</div>
</form>
);
}

ProfileEditor.defaultProps = {
userId: null,
};

ProfileEditor.propTypes = {
userId: PropTypes.string,
};

export default ProfileEditor;
30 changes: 30 additions & 0 deletions src/app/organisms/profile-editor/ProfileEditor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.profile-editor {
display: flex;
align-items: flex-start;
}

.profile-editor__input-wrapper {
flex: 1;
min-width: 0;
margin-top: 10px;

display: flex;
align-items: flex-end;
flex-wrap: wrap;

& > .input-container {
flex: 1;
}
& > button {
height: 46px;
margin-top: var(--sp-normal);
}

& > * {
margin-left: var(--sp-normal);
[dir=rtl] & {
margin-left: 0;
margin-right: var(--sp-normal);
}
}
}
22 changes: 22 additions & 0 deletions src/app/organisms/settings/Settings.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,29 @@ import PopupWindow, { PWContentSelector } from '../../molecules/popup-window/Pop
import SettingTile from '../../molecules/setting-tile/SettingTile';
import ImportE2ERoomKeys from '../../molecules/import-e2e-room-keys/ImportE2ERoomKeys';

import ProfileEditor from '../profile-editor/ProfileEditor';

import SettingsIC 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';
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=""
content={(
<ProfileEditor userId={initMatrix.matrixClient.getUserId()} />
)}
/>
</div>
);
}

function AppearanceSection() {
const [, updateState] = useState({});

Expand Down Expand Up @@ -104,6 +120,12 @@ function AboutSection() {

function Settings({ isOpen, onRequestClose }) {
const settingSections = [{
name: 'General',
iconSrc: SettingsIC,
render() {
return <GeneralSection />;
},
}, {
name: 'Appearance',
iconSrc: SunIC,
render() {
Expand Down