refactor(site): Normalize avatar components (#5860)

This commit is contained in:
Bruno Quaresma
2023-01-26 00:54:53 +00:00
committed by GitHub
parent 233492b75d
commit e7b8318b87
26 changed files with 221 additions and 311 deletions
+4
View File
@@ -96,6 +96,10 @@ rules:
message:
"Use path imports to avoid pulling in unused modules. See:
https://material-ui.com/guides/minimizing-bundle-size/"
- name: "@material-ui/core/Avatar"
message:
"You should use the Avatar component provided on
components/Avatar/Avatar"
no-unused-vars: "off"
"object-curly-spacing": "off"
react-hooks/exhaustive-deps: warn
@@ -0,0 +1,61 @@
import { Story } from "@storybook/react"
import { Avatar, AvatarIcon, AvatarProps } from "./Avatar"
import PauseIcon from "@material-ui/icons/PauseOutlined"
export default {
title: "components/Avatar",
component: Avatar,
}
const Template: Story<AvatarProps> = (args: AvatarProps) => <Avatar {...args} />
export const Letter = Template.bind({})
Letter.args = {
children: "Coder",
}
export const LetterXL = Template.bind({})
LetterXL.args = {
children: "Coder",
size: "xl",
}
export const LetterDarken = Template.bind({})
LetterDarken.args = {
children: "Coder",
colorScheme: "darken",
}
export const Image = Template.bind({})
Image.args = {
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
}
export const ImageXL = Template.bind({})
ImageXL.args = {
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
size: "xl",
}
export const MuiIcon = Template.bind({})
MuiIcon.args = {
children: <PauseIcon />,
}
export const MuiIconDarken = Template.bind({})
MuiIconDarken.args = {
children: <PauseIcon />,
colorScheme: "darken",
}
export const MuiIconXL = Template.bind({})
MuiIconXL.args = {
children: <PauseIcon />,
size: "xl",
}
export const AvatarIconDarken = Template.bind({})
AvatarIconDarken.args = {
children: <AvatarIcon src="/icon/database.svg" />,
colorScheme: "darken",
}
+77
View File
@@ -0,0 +1,77 @@
// This is the only place MuiAvatar can be used
// eslint-disable-next-line no-restricted-imports -- Read above
import MuiAvatar, {
AvatarProps as MuiAvatarProps,
} from "@material-ui/core/Avatar"
import { makeStyles } from "@material-ui/core/styles"
import { FC } from "react"
import { combineClasses } from "util/combineClasses"
import { firstLetter } from "./firstLetter"
export type AvatarProps = MuiAvatarProps & {
size?: "md" | "xl"
colorScheme?: "light" | "darken"
fitImage?: boolean
}
export const Avatar: FC<AvatarProps> = ({
size = "md",
colorScheme = "light",
fitImage,
className,
children,
...muiProps
}) => {
const styles = useStyles()
return (
<MuiAvatar
{...muiProps}
className={combineClasses([
className,
styles[size],
styles[colorScheme],
fitImage && styles.fitImage,
])}
>
{/* If the children is a string, we always want to render the first letter */}
{typeof children === "string" ? firstLetter(children) : children}
</MuiAvatar>
)
}
/**
* Use it to make an img element behaves like a MaterialUI Icon component
*/
export const AvatarIcon: FC<{ src: string }> = ({ src }) => {
const styles = useStyles()
return <img src={src} alt="" className={styles.avatarIcon} />
}
const useStyles = makeStyles((theme) => ({
// Size styles
// Just use the default value from theme
md: {},
xl: {
width: theme.spacing(6),
height: theme.spacing(6),
fontSize: theme.spacing(3),
},
// Colors
// Just use the default value from theme
light: {},
darken: {
background: theme.palette.divider,
color: theme.palette.text.primary,
},
// Avatar icon
avatarIcon: {
maxWidth: "50%",
},
// Fit image
fitImage: {
"& .MuiAvatar-img": {
objectFit: "contain",
},
},
}))
@@ -16,16 +16,9 @@ Example.args = {
subtitle: "coder@coder.com",
}
export const WithHighlightTitle = Template.bind({})
WithHighlightTitle.args = {
export const WithImage = Template.bind({})
WithImage.args = {
title: "coder",
subtitle: "coder@coder.com",
highlightTitle: true,
}
export const WithLink = Template.bind({})
WithLink.args = {
title: "coder",
subtitle: "coder@coder.com",
link: "/users/coder",
src: "https://avatars.githubusercontent.com/u/95932066?s=200&v=4",
}
+23 -44
View File
@@ -1,71 +1,50 @@
import Avatar from "@material-ui/core/Avatar"
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
import { Avatar } from "components/Avatar/Avatar"
import { FC, PropsWithChildren } from "react"
import { Link as RouterLink } from "react-router-dom"
import { firstLetter } from "../../util/firstLetter"
import {
TableCellData,
TableCellDataPrimary,
TableCellDataSecondary,
} from "../TableCellData/TableCellData"
import { Stack } from "components/Stack/Stack"
import { makeStyles } from "@material-ui/core/styles"
export interface AvatarDataProps {
title: string
subtitle?: string
highlightTitle?: boolean
link?: string
src?: string
avatar?: React.ReactNode
}
export const AvatarData: FC<PropsWithChildren<AvatarDataProps>> = ({
title,
subtitle,
link,
highlightTitle,
src,
avatar,
}) => {
const styles = useStyles()
if (!avatar) {
avatar = <Avatar>{firstLetter(title)}</Avatar>
avatar = <Avatar src={src}>{title}</Avatar>
}
return (
<div className={styles.root}>
<div className={styles.avatarWrapper}>{avatar}</div>
<Stack spacing={1.5} direction="row" alignItems="center">
{avatar}
{link ? (
<Link to={link} underline="none" component={RouterLink}>
<TableCellData>
<TableCellDataPrimary highlight={highlightTitle}>
{title}
</TableCellDataPrimary>
{subtitle && (
<TableCellDataSecondary>{subtitle}</TableCellDataSecondary>
)}
</TableCellData>
</Link>
) : (
<TableCellData>
<TableCellDataPrimary highlight={highlightTitle}>
{title}
</TableCellDataPrimary>
{subtitle && (
<TableCellDataSecondary>{subtitle}</TableCellDataSecondary>
)}
</TableCellData>
)}
</div>
<Stack spacing={0}>
<span className={styles.title}>{title}</span>
{subtitle && <span className={styles.subtitle}>{subtitle}</span>}
</Stack>
</Stack>
)
}
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
alignItems: "center",
title: {
color: theme.palette.text.primary,
fontWeight: 600,
},
avatarWrapper: {
marginRight: theme.spacing(1.5),
subtitle: {
fontSize: 12,
color: theme.palette.text.secondary,
lineHeight: "140%",
marginTop: 2,
maxWidth: 540,
},
}))
@@ -1,4 +1,3 @@
import Avatar from "@material-ui/core/Avatar"
import Badge from "@material-ui/core/Badge"
import { Theme, useTheme, withStyles } from "@material-ui/core/styles"
import { FC } from "react"
@@ -8,6 +7,7 @@ import DeleteOutlined from "@material-ui/icons/DeleteOutlined"
import { WorkspaceBuild, WorkspaceTransition } from "api/typesGenerated"
import { getDisplayWorkspaceBuildStatus } from "util/workspace"
import { PaletteIndex } from "theme/palettes"
import { Avatar, AvatarProps } from "components/Avatar/Avatar"
interface StylesBadgeProps {
type: PaletteIndex
@@ -25,27 +25,9 @@ const StyledBadge = withStyles((theme) => ({
},
}))(Badge)
interface StyledAvatarProps {
size?: number
}
const StyledAvatar = withStyles((theme) => ({
root: {
background: theme.palette.divider,
color: theme.palette.text.primary,
border: `2px solid ${theme.palette.divider}`,
width: ({ size }: StyledAvatarProps) => size,
height: ({ size }: StyledAvatarProps) => size,
"& svg": {
width: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
height: ({ size }: StyledAvatarProps) => (size ? size / 2 : 18),
},
},
}))(Avatar)
export interface BuildAvatarProps extends StyledAvatarProps {
export interface BuildAvatarProps {
build: WorkspaceBuild
size?: AvatarProps["size"]
}
const iconByTransition: Record<WorkspaceTransition, JSX.Element> = {
@@ -71,9 +53,9 @@ export const BuildAvatar: FC<BuildAvatarProps> = ({ build, size }) => {
}}
badgeContent={<div></div>}
>
<StyledAvatar size={size}>
<Avatar size={size} colorScheme="darken">
{iconByTransition[build.transition]}
</StyledAvatar>
</Avatar>
</StyledBadge>
)
}
@@ -1,9 +1,8 @@
import Avatar from "@material-ui/core/Avatar"
import { Avatar } from "components/Avatar/Avatar"
import Badge from "@material-ui/core/Badge"
import { withStyles } from "@material-ui/core/styles"
import Group from "@material-ui/icons/Group"
import { FC } from "react"
import { firstLetter } from "util/firstLetter"
const StyledBadge = withStyles((theme) => ({
badge: {
@@ -38,7 +37,7 @@ export const GroupAvatar: FC<GroupAvatarProps> = ({ name, avatarURL }) => {
}}
badgeContent={<Group />}
>
<Avatar src={avatarURL}>{firstLetter(name)}</Avatar>
<Avatar src={avatarURL}>{name}</Avatar>
</StyledBadge>
)
}
@@ -1,11 +1,9 @@
import Avatar from "@material-ui/core/Avatar"
import { makeStyles } from "@material-ui/core/styles"
import { Avatar, AvatarIcon } from "components/Avatar/Avatar"
import { FC } from "react"
import { WorkspaceResource } from "../../api/typesGenerated"
const FALLBACK_ICON = "/icon/widgets.svg"
// NOTE @jsjoeio, @BrunoQuaresma
// These resources (i.e. docker_image, kubernetes_deployment) map to Terraform
// resource types. These are the most used ones and are based on user usage.
// We may want to update from time-to-time.
@@ -37,18 +35,10 @@ export type ResourceAvatarProps = { resource: WorkspaceResource }
export const ResourceAvatar: FC<ResourceAvatarProps> = ({ resource }) => {
const hasIcon = resource.icon && resource.icon !== ""
const avatarSrc = hasIcon ? resource.icon : getIconPathResource(resource.type)
const styles = useStyles()
return <Avatar className={styles.resourceAvatar} src={avatarSrc} />
return (
<Avatar colorScheme="darken">
<AvatarIcon src={avatarSrc} />
</Avatar>
)
}
const useStyles = makeStyles((theme) => ({
resourceAvatar: {
backgroundColor: theme.palette.divider,
"& img": {
width: 18,
height: 18,
},
},
}))
@@ -1,43 +0,0 @@
import { makeStyles } from "@material-ui/core/styles"
import { ReactNode, FC, PropsWithChildren } from "react"
import { Stack } from "../Stack/Stack"
interface StyleProps {
highlight?: boolean
}
export const TableCellData: FC<{ children: ReactNode }> = ({ children }) => {
return <Stack spacing={0}>{children}</Stack>
}
export const TableCellDataPrimary: FC<
PropsWithChildren<{ highlight?: boolean }>
> = ({ children, highlight }) => {
const styles = useStyles({ highlight })
return <span className={styles.primary}>{children}</span>
}
export const TableCellDataSecondary: FC<PropsWithChildren<unknown>> = ({
children,
}) => {
const styles = useStyles({})
return <span className={styles.secondary}>{children}</span>
}
const useStyles = makeStyles((theme) => ({
primary: {
color: ({ highlight }: StyleProps) =>
highlight ? theme.palette.text.primary : theme.palette.text.secondary,
fontWeight: ({ highlight }: StyleProps) => (highlight ? 600 : undefined),
},
secondary: {
fontSize: 12,
color: theme.palette.text.secondary,
lineHeight: "140%",
marginTop: 2,
maxWidth: 540,
},
}))
@@ -1,4 +1,3 @@
import Avatar from "@material-ui/core/Avatar"
import Button from "@material-ui/core/Button"
import Link from "@material-ui/core/Link"
import { makeStyles } from "@material-ui/core/styles"
@@ -19,7 +18,6 @@ import {
useParams,
} from "react-router-dom"
import { combineClasses } from "util/combineClasses"
import { firstLetter } from "util/firstLetter"
import {
TemplateContext,
templateMachine,
@@ -29,6 +27,7 @@ import { Stack } from "components/Stack/Stack"
import { Permissions } from "xServices/auth/authXService"
import { Loader } from "components/Loader/Loader"
import { usePermissions } from "hooks/usePermissions"
import { Avatar } from "components/Avatar/Avatar"
const Language = {
settingsButton: "Settings",
@@ -139,17 +138,12 @@ export const TemplateLayout: FC<{ children?: JSX.Element }> = ({
}
>
<Stack direction="row" spacing={3} className={styles.pageTitle}>
<div>
{hasIcon ? (
<div className={styles.iconWrapper}>
<img src={template.icon} alt="" />
</div>
) : (
<Avatar className={styles.avatar}>
{firstLetter(template.name)}
</Avatar>
)}
</div>
{hasIcon ? (
<Avatar size="xl" src={template.icon} variant="square" fitImage />
) : (
<Avatar size="xl">{template.name}</Avatar>
)}
<div>
<PageHeaderTitle>
{template.display_name.length > 0
@@ -212,11 +206,6 @@ export const useStyles = makeStyles((theme) => {
pageTitle: {
alignItems: "center",
},
avatar: {
width: theme.spacing(6),
height: theme.spacing(6),
fontSize: theme.spacing(3),
},
iconWrapper: {
width: theme.spacing(6),
height: theme.spacing(6),
@@ -1,36 +0,0 @@
import Avatar from "@material-ui/core/Avatar"
import { makeStyles } from "@material-ui/core/styles"
import { User } from "api/typesGenerated"
import { FC } from "react"
import { firstLetter } from "../../util/firstLetter"
export const AutocompleteAvatar: FC<{ user: User }> = ({ user }) => {
const styles = useStyles()
return (
<div className={styles.avatarContainer}>
{user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : (
<Avatar>{firstLetter(user.username)}</Avatar>
)}
</div>
)
}
export const useStyles = makeStyles((theme) => {
return {
avatarContainer: {
margin: "0px 10px",
},
avatar: {
width: theme.spacing(4.5),
height: theme.spacing(4.5),
borderRadius: "100%",
},
}
})
@@ -4,12 +4,12 @@ import TextField from "@material-ui/core/TextField"
import Autocomplete from "@material-ui/lab/Autocomplete"
import { useMachine } from "@xstate/react"
import { User } from "api/typesGenerated"
import { Avatar } from "components/Avatar/Avatar"
import { AvatarData } from "components/AvatarData/AvatarData"
import debounce from "just-debounce-it"
import { ChangeEvent, FC, useEffect, useState } from "react"
import { combineClasses } from "util/combineClasses"
import { searchUserMachine } from "xServices/users/searchUserXService"
import { AutocompleteAvatar } from "./AutocompleteAvatar"
export type UserAutocompleteProps = {
value: User | null
@@ -77,16 +77,7 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
<AvatarData
title={option.username}
subtitle={option.email}
highlightTitle
avatar={
option.avatar_url ? (
<img
className={styles.avatar}
alt={`${option.username}'s Avatar`}
src={option.avatar_url}
/>
) : null
}
src={option.avatar_url}
/>
)}
options={searchResults}
@@ -103,8 +94,8 @@ export const UserAutocomplete: FC<UserAutocompleteProps> = ({
InputProps={{
...params.InputProps,
onChange: handleFilterChange,
startAdornment: (
<>{showAvatar && value && <AutocompleteAvatar user={value} />}</>
startAdornment: showAvatar && value && (
<Avatar src={value.avatar_url}>{value.username}</Avatar>
),
endAdornment: (
<>
@@ -145,12 +136,6 @@ export const useStyles = makeStyles<Theme, styleProps>((theme) => {
padding: `${theme.spacing(0, 0.5, 0, 0.5)} !important`,
},
}),
avatar: {
width: theme.spacing(4.5),
height: theme.spacing(4.5),
borderRadius: "100%",
},
}
})
+7 -10
View File
@@ -1,25 +1,22 @@
import Avatar from "@material-ui/core/Avatar"
import { Avatar } from "components/Avatar/Avatar"
import { FC } from "react"
import { firstLetter } from "../../util/firstLetter"
export interface UserAvatarProps {
username: string
className?: string
avatarURL?: string
// It is needed to work with the AvatarGroup so it can pass the
// MuiAvatarGroup-avatar className
className?: string
}
export const UserAvatar: FC<UserAvatarProps> = ({
username,
className,
avatarURL,
className,
}) => {
return (
<Avatar className={className} title={username}>
{avatarURL ? (
<img alt={`${username}'s Avatar`} src={avatarURL} width="100%" />
) : (
firstLetter(username)
)}
<Avatar title={username} src={avatarURL} className={className}>
{username}
</Avatar>
)
}
@@ -77,16 +77,7 @@ export const UserOrGroupAutocomplete: React.FC<
<AvatarData
title={isOptionGroup ? option.name : option.username}
subtitle={isOptionGroup ? getGroupSubtitle(option) : option.email}
highlightTitle
avatar={
!isOptionGroup && option.avatar_url ? (
<img
className={styles.avatar}
alt={`${option.username}'s Avatar`}
src={option.avatar_url}
/>
) : null
}
src={option.avatar_url}
/>
)
}}
@@ -137,11 +128,5 @@ export const useStyles = makeStyles((theme) => {
padding: `${theme.spacing(0, 0.5, 0, 0.5)} !important`,
},
},
avatar: {
width: theme.spacing(4.5),
height: theme.spacing(4.5),
borderRadius: "100%",
},
}
})
@@ -110,16 +110,7 @@ export const UsersTableBody: FC<
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : null
}
src={user.avatar_url}
/>
</TableCell>
<TableCell>
@@ -216,11 +207,6 @@ const useStyles = makeStyles((theme) => ({
suspended: {
color: theme.palette.text.secondary,
},
avatar: {
width: theme.spacing(4.5),
height: theme.spacing(4.5),
borderRadius: "100%",
},
rolePill: {
backgroundColor: theme.palette.background.paperLight,
borderColor: theme.palette.divider,
+5 -8
View File
@@ -23,6 +23,7 @@ import {
WorkspaceBuildProgress,
} from "components/WorkspaceBuildProgress/WorkspaceBuildProgress"
import { AgentRow } from "components/Resources/AgentRow"
import { Avatar } from "components/Avatar/Avatar"
export enum WorkspaceErrors {
GET_RESOURCES_ERROR = "getResourcesError",
@@ -151,10 +152,11 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
>
<Stack direction="row" spacing={3} alignItems="center">
{hasTemplateIcon && (
<img
alt=""
<Avatar
size="xl"
src={workspace.template_icon}
className={styles.templateIcon}
variant="square"
fitImage
/>
)}
<div>
@@ -267,11 +269,6 @@ export const useStyles = makeStyles((theme) => {
width: "100%",
},
templateIcon: {
width: theme.spacing(6),
height: theme.spacing(6),
},
timelineContents: {
margin: 0,
},
@@ -11,6 +11,7 @@ import { getDisplayWorkspaceTemplateName } from "util/workspace"
import { LastUsed } from "../LastUsed/LastUsed"
import { Workspace } from "api/typesGenerated"
import { OutdatedHelpTooltip } from "components/Tooltips/OutdatedHelpTooltip"
import { Avatar } from "components/Avatar/Avatar"
export const WorkspacesRow: FC<{
workspace: Workspace
@@ -35,15 +36,12 @@ export const WorkspacesRow: FC<{
>
<TableCell>
<AvatarData
highlightTitle
title={workspace.name}
subtitle={workspace.owner_name}
avatar={
hasTemplateIcon ? (
<div className={styles.templateIconWrapper}>
<img alt="" src={workspace.template_icon} />
</div>
) : undefined
hasTemplateIcon && (
<Avatar src={workspace.template_icon} variant="square" fitImage />
)
}
/>
</TableCell>
@@ -1,9 +1,8 @@
import Avatar from "@material-ui/core/Avatar"
import { makeStyles } from "@material-ui/core/styles"
import { Template, TemplateExample } from "api/typesGenerated"
import { Avatar } from "components/Avatar/Avatar"
import { Stack } from "components/Stack/Stack"
import { FC } from "react"
import { firstLetter } from "util/firstLetter"
export interface SelectedTemplateProps {
template: Template | TemplateExample
@@ -19,13 +18,8 @@ export const SelectedTemplate: FC<SelectedTemplateProps> = ({ template }) => {
className={styles.template}
alignItems="center"
>
<div className={styles.templateIcon}>
{template.icon === "" ? (
<Avatar>{firstLetter(template.name)}</Avatar>
) : (
<img src={template.icon} alt="" />
)}
</div>
<Avatar src={template.icon}>{template.name}</Avatar>
<Stack direction="column" spacing={0.5}>
<span className={styles.templateName}>
{"display_name" in template && template.display_name.length > 0
@@ -58,13 +52,4 @@ const useStyles = makeStyles((theme) => ({
fontSize: 14,
color: theme.palette.text.secondary,
},
templateIcon: {
width: theme.spacing(4),
lineHeight: 1,
"& img": {
width: "100%",
},
},
}))
-1
View File
@@ -173,7 +173,6 @@ export const GroupPage: React.FC = () => {
<AvatarData
title={member.username}
subtitle={member.email}
highlightTitle
/>
</TableCell>
<TableCell width="1%">
@@ -144,7 +144,6 @@ export const GroupsPageView: FC<GroupsPageViewProps> = ({
}
title={group.name}
subtitle={`${group.members.length} members`}
highlightTitle
/>
</TableCell>
@@ -249,7 +249,6 @@ export const TemplatePermissionsPageView: FC<
}
title={group.name}
subtitle={getGroupSubtitle(group)}
highlightTitle
/>
</TableCell>
<TableCell>
@@ -296,16 +295,7 @@ export const TemplatePermissionsPageView: FC<
<AvatarData
title={user.username}
subtitle={user.email}
highlightTitle
avatar={
user.avatar_url ? (
<img
className={styles.avatar}
alt={`${user.username}'s Avatar`}
src={user.avatar_url}
/>
) : null
}
src={user.avatar_url}
/>
</TableCell>
<TableCell>
@@ -363,12 +353,6 @@ export const useStyles = makeStyles((theme) => {
width: 100,
},
avatar: {
width: theme.spacing(4.5),
height: theme.spacing(4.5),
borderRadius: "100%",
},
updateSelect: {
margin: 0,
// Set a fixed width for the select. It avoids selects having different sizes
@@ -41,6 +41,7 @@ import { Template } from "api/typesGenerated"
import { combineClasses } from "util/combineClasses"
import { colors } from "theme/colors"
import ArrowForwardOutlined from "@material-ui/icons/ArrowForwardOutlined"
import { Avatar } from "components/Avatar/Avatar"
export const Language = {
developerCount: (activeCount: number): string => {
@@ -97,13 +98,8 @@ const TemplateRow: FC<{ template: Template }> = ({ template }) => {
: template.name
}
subtitle={template.description}
highlightTitle
avatar={
hasIcon && (
<div className={styles.templateIconWrapper}>
<img alt="" src={template.icon} />
</div>
)
hasIcon && <Avatar src={template.icon} variant="square" fitImage />
}
/>
</TableCell>
@@ -34,7 +34,7 @@ export const WorkspaceBuildPageView: FC<WorkspaceBuildPageViewProps> = ({
{build && (
<PageHeader>
<Stack direction="row" alignItems="center" spacing={3}>
<BuildAvatar build={build} size={48} />
<BuildAvatar build={build} size="xl" />
<div>
<PageHeaderTitle>Build #{build.build_number}</PageHeaderTitle>
<PageHeaderSubtitle condensed>
+4
View File
@@ -30,6 +30,10 @@ export const getOverrides = ({
width: 36,
height: 36,
fontSize: 18,
"& .MuiSvgIcon-root": {
width: "50%",
},
},
colorDefault: {
backgroundColor: colors.gray[6],