mirror of
https://github.com/element-hq/element-web
synced 2024-11-26 19:26:04 +03:00
Extract MiniAvatarUploader into a reusable component
This commit is contained in:
parent
0a42853a25
commit
6aeea3e38e
5 changed files with 159 additions and 84 deletions
|
@ -115,6 +115,7 @@
|
||||||
@import "./views/elements/_InfoTooltip.scss";
|
@import "./views/elements/_InfoTooltip.scss";
|
||||||
@import "./views/elements/_InlineSpinner.scss";
|
@import "./views/elements/_InlineSpinner.scss";
|
||||||
@import "./views/elements/_ManageIntegsButton.scss";
|
@import "./views/elements/_ManageIntegsButton.scss";
|
||||||
|
@import "./views/elements/_MiniAvatarUploader.scss";
|
||||||
@import "./views/elements/_PowerSelector.scss";
|
@import "./views/elements/_PowerSelector.scss";
|
||||||
@import "./views/elements/_ProgressBar.scss";
|
@import "./views/elements/_ProgressBar.scss";
|
||||||
@import "./views/elements/_QRCode.scss";
|
@import "./views/elements/_QRCode.scss";
|
||||||
|
|
|
@ -50,42 +50,8 @@ limitations under the License.
|
||||||
color: $muted-fg-color;
|
color: $muted-fg-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_HomePage_userAvatar {
|
.mx_MiniAvatarUploader {
|
||||||
position: relative;
|
|
||||||
width: min-content;
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
|
||||||
&::before, &::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
height: 26px;
|
|
||||||
width: 26px;
|
|
||||||
|
|
||||||
right: -6px;
|
|
||||||
bottom: -6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
background-color: $primary-bg-color;
|
|
||||||
border-radius: 50%;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
background-color: $secondary-fg-color;
|
|
||||||
mask-position: center;
|
|
||||||
mask-repeat: no-repeat;
|
|
||||||
mask-image: url('$(res)/img/element-icons/camera.svg');
|
|
||||||
mask-size: 16px;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mx_HomePage_userAvatar_busy::after {
|
|
||||||
background: url("$(res)/img/spinner.gif") no-repeat center;
|
|
||||||
background-size: 80%;
|
|
||||||
mask: unset;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_HomePage_default_buttons {
|
.mx_HomePage_default_buttons {
|
||||||
|
|
56
res/css/views/elements/_MiniAvatarUploader.scss
Normal file
56
res/css/views/elements/_MiniAvatarUploader.scss
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.mx_MiniAvatarUploader {
|
||||||
|
position: relative;
|
||||||
|
width: min-content;
|
||||||
|
|
||||||
|
&::before, &::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
height: 26px;
|
||||||
|
width: 26px;
|
||||||
|
|
||||||
|
right: -6px;
|
||||||
|
bottom: -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
background-color: $primary-bg-color;
|
||||||
|
border-radius: 50%;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
background-color: $secondary-fg-color;
|
||||||
|
mask-position: center;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-image: url('$(res)/img/element-icons/camera.svg');
|
||||||
|
mask-size: 16px;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mx_MiniAvatarUploader_busy::after {
|
||||||
|
background: url("$(res)/img/spinner.gif") no-repeat center;
|
||||||
|
background-size: 80%;
|
||||||
|
mask: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_MiniAvatarUploader_input {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import {useContext, useRef, useState} from "react";
|
import {useContext, useState} from "react";
|
||||||
|
|
||||||
import AutoHideScrollbar from './AutoHideScrollbar';
|
import AutoHideScrollbar from './AutoHideScrollbar';
|
||||||
import {getHomePageUrl} from "../../utils/pages";
|
import {getHomePageUrl} from "../../utils/pages";
|
||||||
|
@ -24,16 +24,13 @@ import SdkConfig from "../../SdkConfig";
|
||||||
import * as sdk from "../../index";
|
import * as sdk from "../../index";
|
||||||
import dis from "../../dispatcher/dispatcher";
|
import dis from "../../dispatcher/dispatcher";
|
||||||
import {Action} from "../../dispatcher/actions";
|
import {Action} from "../../dispatcher/actions";
|
||||||
import {Transition} from "react-transition-group";
|
|
||||||
import BaseAvatar from "../views/avatars/BaseAvatar";
|
import BaseAvatar from "../views/avatars/BaseAvatar";
|
||||||
import {OwnProfileStore} from "../../stores/OwnProfileStore";
|
import {OwnProfileStore} from "../../stores/OwnProfileStore";
|
||||||
import AccessibleButton from "../views/elements/AccessibleButton";
|
import AccessibleButton from "../views/elements/AccessibleButton";
|
||||||
import Tooltip from "../views/elements/Tooltip";
|
|
||||||
import {UPDATE_EVENT} from "../../stores/AsyncStore";
|
import {UPDATE_EVENT} from "../../stores/AsyncStore";
|
||||||
import {useEventEmitter} from "../../hooks/useEventEmitter";
|
import {useEventEmitter} from "../../hooks/useEventEmitter";
|
||||||
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
import MatrixClientContext from "../../contexts/MatrixClientContext";
|
||||||
import classNames from "classnames";
|
import MiniAvatarUploader, {AVATAR_SIZE} from "../views/elements/MiniAvatarUploader";
|
||||||
import {ENTERING} from "react-transition-group/Transition";
|
|
||||||
|
|
||||||
const onClickSendDm = () => dis.dispatch({action: 'view_create_chat'});
|
const onClickSendDm = () => dis.dispatch({action: 'view_create_chat'});
|
||||||
const onClickExplore = () => dis.fire(Action.ViewRoomDirectory);
|
const onClickExplore = () => dis.fire(Action.ViewRoomDirectory);
|
||||||
|
@ -43,11 +40,9 @@ interface IProps {
|
||||||
justRegistered?: boolean;
|
justRegistered?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const avatarSize = 52;
|
|
||||||
|
|
||||||
const getOwnProfile = (userId: string) => ({
|
const getOwnProfile = (userId: string) => ({
|
||||||
displayName: OwnProfileStore.instance.displayName || userId,
|
displayName: OwnProfileStore.instance.displayName || userId,
|
||||||
avatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(avatarSize),
|
avatarUrl: OwnProfileStore.instance.getHttpAvatarUrl(AVATAR_SIZE),
|
||||||
});
|
});
|
||||||
|
|
||||||
const UserWelcomeTop = () => {
|
const UserWelcomeTop = () => {
|
||||||
|
@ -57,56 +52,23 @@ const UserWelcomeTop = () => {
|
||||||
useEventEmitter(OwnProfileStore.instance, UPDATE_EVENT, () => {
|
useEventEmitter(OwnProfileStore.instance, UPDATE_EVENT, () => {
|
||||||
setOwnProfile(getOwnProfile(userId));
|
setOwnProfile(getOwnProfile(userId));
|
||||||
});
|
});
|
||||||
const [busy, setBusy] = useState(false);
|
|
||||||
|
|
||||||
const uploadRef = useRef<HTMLInputElement>();
|
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
<input
|
<MiniAvatarUploader
|
||||||
type="file"
|
hasAvatar={!!ownProfile.avatarUrl}
|
||||||
ref={uploadRef}
|
hasAvatarLabel={_t("Great, that'll help people know it's you")}
|
||||||
className="mx_ProfileSettings_avatarUpload"
|
noAvatarLabel={_t("Add a photo so people know it's you.")}
|
||||||
onChange={async (ev) => {
|
setAvatarUrl={url => cli.setAvatarUrl(url)}
|
||||||
if (!ev.target.files?.length) return;
|
|
||||||
setBusy(true);
|
|
||||||
const file = ev.target.files[0];
|
|
||||||
const uri = await cli.uploadContent(file);
|
|
||||||
await cli.setAvatarUrl(uri);
|
|
||||||
setBusy(false);
|
|
||||||
}}
|
|
||||||
accept="image/*"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<AccessibleButton
|
|
||||||
className={classNames("mx_HomePage_userAvatar", {
|
|
||||||
mx_HomePage_userAvatar_busy: busy,
|
|
||||||
})}
|
|
||||||
disabled={busy}
|
|
||||||
onClick={() => {
|
|
||||||
uploadRef.current.click();
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<BaseAvatar
|
<BaseAvatar
|
||||||
idName={userId}
|
idName={userId}
|
||||||
name={ownProfile.displayName}
|
name={ownProfile.displayName}
|
||||||
url={ownProfile.avatarUrl}
|
url={ownProfile.avatarUrl}
|
||||||
width={avatarSize}
|
width={AVATAR_SIZE}
|
||||||
height={avatarSize}
|
height={AVATAR_SIZE}
|
||||||
resizeMethod="crop"
|
resizeMethod="crop"
|
||||||
/>
|
/>
|
||||||
|
</MiniAvatarUploader>
|
||||||
<Transition appear in timeout={3000}>
|
|
||||||
{state => (
|
|
||||||
<Tooltip
|
|
||||||
label={ownProfile.avatarUrl || busy
|
|
||||||
? _t("Great, that'll help people know it's you")
|
|
||||||
: _t("Add a photo so people know it's you.")}
|
|
||||||
visible={state !== ENTERING}
|
|
||||||
forceOnRight
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Transition>
|
|
||||||
</AccessibleButton>
|
|
||||||
|
|
||||||
<h1>{ _t("Welcome %(name)s", { name: ownProfile.displayName }) }</h1>
|
<h1>{ _t("Welcome %(name)s", { name: ownProfile.displayName }) }</h1>
|
||||||
<h4>{ _t("Now, let's help you get started") }</h4>
|
<h4>{ _t("Now, let's help you get started") }</h4>
|
||||||
|
|
90
src/components/views/elements/MiniAvatarUploader.tsx
Normal file
90
src/components/views/elements/MiniAvatarUploader.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, {useContext, useRef, useState} from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import AccessibleButton from "./AccessibleButton";
|
||||||
|
import Tooltip from './Tooltip';
|
||||||
|
import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
|
import {useTimeout} from "../../../hooks/useTimeout";
|
||||||
|
|
||||||
|
export const AVATAR_SIZE = 52;
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
hasAvatar: boolean;
|
||||||
|
noAvatarLabel?: string;
|
||||||
|
hasAvatarLabel?: string;
|
||||||
|
setAvatarUrl(url: string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MiniAvatarUploader: React.FC<IProps> = ({ hasAvatar, hasAvatarLabel, noAvatarLabel, setAvatarUrl, children }) => {
|
||||||
|
const cli = useContext(MatrixClientContext);
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
const [hover, setHover] = useState(false);
|
||||||
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
|
useTimeout(() => {
|
||||||
|
setShow(true);
|
||||||
|
}, 3_000); // show after 3 seconds
|
||||||
|
useTimeout(() => {
|
||||||
|
setShow(false);
|
||||||
|
}, 13_000); // hide after being shown for 10 seconds
|
||||||
|
|
||||||
|
const uploadRef = useRef<HTMLInputElement>();
|
||||||
|
|
||||||
|
const label = hasAvatar || busy ? hasAvatarLabel : noAvatarLabel;
|
||||||
|
|
||||||
|
return <React.Fragment>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={uploadRef}
|
||||||
|
className="mx_MiniAvatarUploader_input"
|
||||||
|
onChange={async (ev) => {
|
||||||
|
if (!ev.target.files?.length) return;
|
||||||
|
setBusy(true);
|
||||||
|
const file = ev.target.files[0];
|
||||||
|
const uri = await cli.uploadContent(file);
|
||||||
|
await setAvatarUrl(uri);
|
||||||
|
setBusy(false);
|
||||||
|
}}
|
||||||
|
accept="image/*"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<AccessibleButton
|
||||||
|
className={classNames("mx_MiniAvatarUploader", {
|
||||||
|
mx_MiniAvatarUploader_busy: busy,
|
||||||
|
mx_MiniAvatarUploader_hasAvatar: hasAvatar,
|
||||||
|
})}
|
||||||
|
disabled={busy}
|
||||||
|
onClick={() => {
|
||||||
|
uploadRef.current.click();
|
||||||
|
}}
|
||||||
|
onMouseOver={() => setHover(true)}
|
||||||
|
onMouseLeave={() => setHover(false)}
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
label={label}
|
||||||
|
visible={!!label && (hover || show)}
|
||||||
|
forceOnRight
|
||||||
|
/>
|
||||||
|
</AccessibleButton>
|
||||||
|
</React.Fragment>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MiniAvatarUploader;
|
Loading…
Reference in a new issue