Skip to content

Commit

Permalink
profile settings and image editor - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ajbura committed Dec 7, 2024
1 parent f67fbd1 commit c43bbea
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 34 deletions.
35 changes: 35 additions & 0 deletions src/app/components/image-editor/ImageEditor.css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { style } from '@vanilla-extract/css';
import { DefaultReset, color, config } from 'folds';

export const ImageEditor = style([
DefaultReset,
{
height: '100%',
},
]);

export const ImageEditorHeader = style([
DefaultReset,
{
paddingLeft: config.space.S200,
paddingRight: config.space.S200,
borderBottomWidth: config.borderWidth.B300,
flexShrink: 0,
gap: config.space.S200,
},
]);

export const ImageEditorContent = style([
DefaultReset,
{
backgroundColor: color.Background.Container,
color: color.Background.OnContainer,
overflow: 'hidden',
},
]);

export const Image = style({
width: '100%',
height: '100%',
objectFit: 'contain',
});
51 changes: 51 additions & 0 deletions src/app/components/image-editor/ImageEditor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import classNames from 'classnames';
import { Box, Chip, Header, Icon, IconButton, Icons, Text, as } from 'folds';
import * as css from './ImageEditor.css';

export type ImageEditorProps = {
name: string;
url: string;
requestClose: () => void;
};

export const ImageEditor = as<'div', ImageEditorProps>(
({ className, name, url, requestClose, ...props }, ref) => {
const handleApply = () => {
//
};

return (
<Box
className={classNames(css.ImageEditor, className)}
direction="Column"
{...props}
ref={ref}
>
<Header className={css.ImageEditorHeader} size="400">
<Box grow="Yes" alignItems="Center" gap="200">
<IconButton size="300" radii="300" onClick={requestClose}>
<Icon size="50" src={Icons.ArrowLeft} />
</IconButton>
<Text size="T300" truncate>
Image Editor
</Text>
</Box>
<Box shrink="No" alignItems="Center" gap="200">
<Chip variant="Primary" radii="300" onClick={handleApply}>
<Text size="B300">Save</Text>
</Chip>
</Box>
</Header>
<Box
grow="Yes"
className={css.ImageEditorContent}
justifyContent="Center"
alignItems="Center"
>
<img className={css.Image} src={url} alt={name} />
</Box>
</Box>
);
}
);
1 change: 1 addition & 0 deletions src/app/components/image-editor/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ImageEditor';
141 changes: 107 additions & 34 deletions src/app/features/settings/Account.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
import React, { useCallback, useEffect } from 'react';
import { Box, Text, IconButton, Icon, Icons, Scroll, Input, Avatar, Button, Chip } from 'folds';
import React, { useCallback, useEffect, useState } from 'react';
import {
Box,
Text,
IconButton,
Icon,
Icons,
Scroll,
Input,
Avatar,
Button,
Chip,
Overlay,
OverlayBackdrop,
OverlayCenter,
Modal,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { Page, PageContent, PageHeader } from '../../components/page';
import { SequenceCard } from '../../components/sequence-card';
import { SequenceCardStyle } from './styles.css';
import { SettingTile } from '../../components/setting-tile';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useUserProfile } from '../../hooks/useUserProfile';
import { UserProfile, useUserProfile } from '../../hooks/useUserProfile';
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { UserAvatar } from '../../components/user-avatar';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { nameInitials } from '../../utils/common';
import { copyToClipboard } from '../../utils/dom';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useFilePicker } from '../../hooks/useFilePicker';
import { useObjectURL } from '../../hooks/useObjectURL';
import { stopPropagation } from '../../utils/keyboard';
import { ImageEditor } from '../../components/image-editor';
import { ModalWide } from '../../styles/Modal.css';

function MatrixId() {
const mx = useMatrixClient();
Expand Down Expand Up @@ -39,17 +60,95 @@ function MatrixId() {
);
}

function Profile() {
type ProfileAvatarProps = {
profile: UserProfile;
userId: string;
};
function ProfileAvatar({ profile, userId }: ProfileAvatarProps) {
const mx = useMatrixClient();
const useAuthentication = useMediaAuthentication();
const userId = mx.getUserId()!;
const profile = useUserProfile(userId);

const defaultDisplayName = profile.displayName ?? getMxIdLocalPart(userId) ?? userId;
const avatarUrl = profile.avatarUrl
? mxcUrlToHttp(mx, profile.avatarUrl, useAuthentication, 96, 96, 'crop') ?? undefined
: undefined;

const [imageFile, setImageFile] = useState<File>();
const imageFileURL = useObjectURL(imageFile);

const pickFile = useFilePicker(setImageFile, false);

const handleImageCropperClose = useCallback(() => {
setImageFile(undefined);
}, []);

return (
<SettingTile
title={
<Text as="span" size="L400">
Avatar
</Text>
}
before={
<Avatar size="500" radii="300">
<UserAvatar
userId={userId}
src={avatarUrl}
renderFallback={() => <Text size="H4">{nameInitials(defaultDisplayName)}</Text>}
/>
</Avatar>
}
>
<Box gap="200">
<Button
onClick={() => pickFile('image/*')}
size="300"
variant="Secondary"
fill="Soft"
outlined
radii="300"
>
<Text size="B300">Upload</Text>
</Button>
{avatarUrl && (
<Button size="300" variant="Critical" fill="None" radii="300">
<Text size="B300">Remove</Text>
</Button>
)}

{imageFileURL && (
<Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: false,
onDeactivate: handleImageCropperClose,
clickOutsideDeactivates: true,
escapeDeactivates: stopPropagation,
}}
>
<Modal className={ModalWide} variant="Surface" size="500">
<ImageEditor
name={imageFile?.name ?? 'Unnamed'}
url={imageFileURL}
requestClose={handleImageCropperClose}
/>
</Modal>
</FocusTrap>
</OverlayCenter>
</Overlay>
)}
</Box>
</SettingTile>
);
}

function Profile() {
const mx = useMatrixClient();
const userId = mx.getUserId()!;
const profile = useUserProfile(userId);
const defaultDisplayName = profile.displayName ?? getMxIdLocalPart(userId) ?? userId;

return (
<Box direction="Column" gap="100">
<Text size="L400">Profile</Text>
Expand All @@ -59,33 +158,7 @@ function Profile() {
direction="Column"
gap="400"
>
<SettingTile
title={
<Text as="span" size="L400">
Avatar
</Text>
}
before={
<Avatar size="500" radii="300">
<UserAvatar
userId={userId}
src={avatarUrl}
renderFallback={() => <Text size="H4">{nameInitials(defaultDisplayName)}</Text>}
/>
</Avatar>
}
>
<Box gap="200">
<Button size="300" variant="Secondary" fill="Soft" outlined radii="300">
<Text size="B300">Upload</Text>
</Button>
{avatarUrl && (
<Button size="300" variant="Critical" fill="None" radii="300">
<Text size="B300">Remove</Text>
</Button>
)}
</Box>
</SettingTile>
<ProfileAvatar userId={userId} profile={profile} />
<SettingTile
title={
<Text as="span" size="L400">
Expand Down Expand Up @@ -136,7 +209,7 @@ function ContactInformation() {
<SettingTile title="Email Address" description="Email address attached to your account.">
<Box>
{emailIds?.map((email) => (
<Chip as="span" variant="Secondary" radii="Pill">
<Chip key={email.address} as="span" variant="Secondary" radii="Pill">
<Text size="T200">{email.address}</Text>
</Chip>
))}
Expand Down

0 comments on commit c43bbea

Please sign in to comment.