Update Userpic component (#160)

This commit is contained in:
Ilya Y 2023-08-11 19:42:41 +03:00 committed by GitHub
parent c95907968c
commit f9c30a99cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 137 additions and 139 deletions

View File

@ -4,7 +4,7 @@ import { getPagePath } from '@nanostores/router'
import MD from './MD'
import { AuthorCard } from '../Author/AuthorCard'
import Userpic from '../Author/Userpic'
import { Userpic } from '../Author/Userpic'
import { CommentRatingControl } from './CommentRatingControl'
import { CommentDate } from './CommentDate'
import { ShowIfAuthenticated } from '../_shared/ShowIfAuthenticated'
@ -123,7 +123,8 @@ export const Comment = (props: Props) => {
fallback={
<div>
<Userpic
user={comment().createdBy as Author}
name={comment().createdBy.name}
userpic={comment().createdBy.userpic}
isBig={false}
class={clsx({
[styles.compactUserpic]: props.compact

View File

@ -1,5 +1,5 @@
import type { Author } from '../../graphql/types.gen'
import Userpic from './Userpic'
import { Userpic } from './Userpic'
import { Icon } from '../_shared/Icon'
import styles from './AuthorCard.module.scss'
import { createMemo, createSignal, For, Show } from 'solid-js'
@ -101,7 +101,8 @@ export const AuthorCard = (props: AuthorCardProps) => {
}}
>
<Userpic
user={props.author}
name={props.author.name}
userpic={props.author.userpic}
hasLink={props.hasLink}
isBig={props.isAuthorPage}
isAuthorsList={props.isAuthorsList}

View File

@ -1,65 +0,0 @@
import { Show } from 'solid-js'
import type { Author, User } from '../../graphql/types.gen'
import styles from './Userpic.module.scss'
import { clsx } from 'clsx'
interface UserpicProps {
user: Author | User
hasLink?: boolean
isBig?: boolean
class?: string
isAuthorsList?: boolean
isFeedMode?: boolean
}
export default (props: UserpicProps) => {
const letters = () => {
const names = props.user && props.user.name ? props.user.name.split(' ') : []
return names[0][0] + (names.length > 1 ? names[1][0] : '')
}
return (
<div
class={clsx(styles.circlewrap, props.class)}
classList={{
[styles.big]: props.isBig,
[styles.authorsList]: props.isAuthorsList,
[styles.feedMode]: props.isFeedMode
}}
>
<Show when={props.hasLink}>
<a href={`/author/${props.user.slug}`}>
<Show
when={props.user && props.user.userpic === ''}
fallback={
<img
src={props.user.userpic || '/icons/user-default.svg'}
alt={props.user.name || ''}
classList={{ [styles.anonymous]: !props.user.userpic }}
/>
}
>
<div class={styles.userpic}>{letters()}</div>
</Show>
</a>
</Show>
<Show when={!props.hasLink}>
<Show
when={props.user && props.user.userpic === ''}
fallback={
<img
src={props.user.userpic || '/icons/user-default.svg'}
alt={props.user.name || ''}
classList={{ [styles.anonymous]: !props.user.userpic }}
loading="lazy"
/>
}
>
<div class={styles.userpic}>{letters()}</div>
</Show>
</Show>
</div>
)
}

View File

@ -1,4 +1,4 @@
.circlewrap {
.Userpic {
align-items: baseline;
background: #f7f7f8;
border-radius: 100%;
@ -19,7 +19,7 @@
display: block;
}
.userpic {
.letters {
background-color: white;
border-radius: 50%;
border: 1.5px solid black;
@ -53,24 +53,24 @@
color: #000;
}
}
}
.big.circlewrap {
margin-right: 0;
max-width: 168px;
min-width: 168px;
height: 168px;
width: 168px;
&.big {
margin-right: 0;
max-width: 168px;
min-width: 168px;
height: 168px;
width: 168px;
@include media-breakpoint-up(md) {
margin-right: 4.8rem;
}
@include media-breakpoint-up(md) {
margin-right: 4.8rem;
}
.userpic {
font-size: 2em;
line-height: 168px;
max-width: 100%;
width: 100%;
.letters {
font-size: 2em;
line-height: 168px;
max-width: 100%;
width: 100%;
}
}
}
@ -81,16 +81,20 @@
height: 6.8rem;
width: 6.8rem;
.userpic {
.letters {
line-height: 6.4rem;
}
}
.feedMode {
.userpic {
.letters {
font-size: 0.8rem;
line-height: 14px;
min-width: 16px;
max-width: 16px;
}
}
.cursorPointer {
cursor: pointer;
}

View File

@ -0,0 +1,62 @@
import { Show } from 'solid-js'
import type { Author, User } from '../../../graphql/types.gen'
import styles from './Userpic.module.scss'
import { clsx } from 'clsx'
import { imageProxy } from '../../../utils/imageProxy'
import { ConditionalWrapper } from '../../_shared/ConditionalWrapper'
import { Loading } from '../../_shared/Loading'
type Props = {
name: string
userpic: string
class?: string
slug?: string
onClick?: () => void
loading?: boolean
isBig?: boolean
hasLink?: boolean
isAuthorsList?: boolean
isFeedMode?: boolean
}
export const Userpic = (props: Props) => {
const letters = () => {
if (!props.name) return
const names = props.name ? props.name.split(' ') : []
return names[0][0] + (names.length > 1 ? names[1][0] : '')
}
return (
<div
class={clsx(styles.Userpic, props.class, {
[styles.big]: props.isBig,
[styles.authorsList]: props.isAuthorsList,
[styles.feedMode]: props.isFeedMode,
[styles.cursorPointer]: props.onClick
})}
onClick={props.onClick}
>
<Show when={!props.loading} fallback={<Loading />}>
<ConditionalWrapper
condition={props.hasLink}
wrapper={(children) => <a href={`/author/${props.slug}`}>{children}</a>}
>
<Show
when={!props.userpic}
fallback={
<img
class={clsx({ [styles.anonymous]: !props.userpic })}
src={imageProxy(props.userpic) || '/icons/user-default.svg'}
alt={props.name || ''}
loading="lazy"
/>
}
>
<div class={styles.letters}>{letters()}</div>
</Show>
</ConditionalWrapper>
</Show>
</div>
)
}

View File

@ -0,0 +1 @@
export { Userpic } from './Userpic'

View File

@ -9,7 +9,7 @@ import { useSession } from '../../../context/session'
import { useLocalize } from '../../../context/localize'
import styles from './Sidebar.module.scss'
import { clsx } from 'clsx'
import Userpic from '../../Author/Userpic'
import { Userpic } from '../../Author/Userpic'
import { getPagePath } from '@nanostores/router'
import { router, useRouter } from '../../../stores/router'
@ -138,7 +138,10 @@ export const Sidebar = (props: FeedSidebarProps) => {
>
<div class={styles.sidebarItemName}>
<Show when={authorEntities()[authorSlug]}>
<Userpic user={authorEntities()[authorSlug]} />
<Userpic
name={authorEntities()[authorSlug].name}
userpic={authorEntities()[authorSlug].userpic}
/>
</Show>
<Show when={!authorEntities()[authorSlug]}>
<Icon name="hash" class={styles.icon} />

View File

@ -5,7 +5,7 @@ import { Icon } from '../_shared/Icon'
import { createMemo, createSignal, onCleanup, onMount, Show } from 'solid-js'
import Notifications from './Notifications'
import { ProfilePopup } from './ProfilePopup'
import Userpic from '../Author/Userpic'
import { Userpic } from '../Author/Userpic'
import { showModal, useWarningsStore } from '../../stores/ui'
import { ShowOnlyOnClient } from '../_shared/ShowOnlyOnClient'
import { useSession } from '../../context/session'
@ -216,7 +216,11 @@ export const HeaderAuth = (props: Props) => {
<div class={styles.userControlItem}>
<button class={styles.button}>
<div classList={{ entered: page().path === `/${session().user?.slug}` }}>
<Userpic user={session().user} class={styles.userpic} />
<Userpic
name={session().user.name}
userpic={session().user.userpic}
class={styles.userpic}
/>
</div>
</button>
</div>

View File

@ -12,7 +12,7 @@ import { splitToPages } from '../../../utils/splitToPages'
import styles from './Author.module.scss'
import stylesArticle from '../../Article/Article.module.scss'
import { clsx } from 'clsx'
import Userpic from '../../Author/Userpic'
import { Userpic } from '../../Author/Userpic'
import { Popup } from '../../_shared/Popup'
import { AuthorCard } from '../../Author/AuthorCard'
import { apiClient } from '../../../utils/apiClient'
@ -178,12 +178,12 @@ export const AuthorView = (props: AuthorProps) => {
<Switch>
<Match when={followers().length <= 3}>
<For each={followers().slice(0, 3)}>
{(f) => <Userpic user={f} class={styles.userpic} />}
{(f) => <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />}
</For>
</Match>
<Match when={followers().length > 3}>
<For each={followers().slice(0, 2)}>
{(f) => <Userpic user={f} class={styles.userpic} />}
{(f) => <Userpic name={f.name} userpic={f.userpic} class={styles.userpic} />}
</For>
<div class={clsx(styles.userpic, styles.subscribersCounter)}>
{followers().length}

View File

@ -0,0 +1,11 @@
import { JSX } from 'solid-js'
type Props = {
condition: boolean
wrapper: (children: JSX.Element) => JSX.Element
children: JSX.Element
}
export const ConditionalWrapper = (props: Props) => {
return props.condition ? props.wrapper(props.children) : props.children
}

View File

@ -0,0 +1 @@
export { ConditionalWrapper } from './ConditionalWrapper'

View File

@ -1,7 +1,7 @@
import type { Reaction } from '../../../graphql/types.gen'
import { Author, ReactionKind } from '../../../graphql/types.gen'
import { For, Show } from 'solid-js'
import Userpic from '../../Author/Userpic'
import type { Reaction } from '../../../graphql/types.gen'
import { ReactionKind } from '../../../graphql/types.gen'
import { Userpic } from '../../Author/Userpic'
import styles from './VotersList.module.scss'
import { clsx } from 'clsx'
@ -22,7 +22,12 @@ export const VotersList = (props: Props) => {
{(reaction) => (
<li class={styles.item}>
<div class={styles.user}>
<Userpic user={reaction.createdBy as Author} isBig={false} isAuthorsList={false} />
<Userpic
name={reaction.createdBy.name}
userpic={reaction.createdBy.userpic}
isBig={false}
isAuthorsList={false}
/>
<a href={`/author/${reaction.createdBy.slug}`}>{reaction.createdBy.name || ''}</a>
</div>
{reaction.kind === ReactionKind.Like ? (

View File

@ -13,34 +13,6 @@ h5 {
margin: 0 0 0.8rem;
}
.avatarContainer {
border-radius: 100%;
overflow: hidden;
position: relative;
height: 18rem;
width: 18rem;
}
.avatar {
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
cursor: pointer;
background: #ccc;
border: none;
object-fit: cover;
object-position: center;
}
.avatarInput {
border-radius: 100%;
cursor: pointer;
opacity: 0;
z-index: 1;
}
.multipleControls {
margin-top: 3rem;
}

View File

@ -7,13 +7,12 @@ import styles from './Settings.module.scss'
import { useProfileForm } from '../../context/profile'
import { validateUrl } from '../../utils/validateUrl'
import { createFileUploader } from '@solid-primitives/upload'
import { Loading } from '../../components/_shared/Loading'
import { useSession } from '../../context/session'
import { Button } from '../../components/_shared/Button'
import { useSnackbar } from '../../context/snackbar'
import { useLocalize } from '../../context/localize'
import { Image } from '../../components/_shared/Image'
import { handleFileUpload } from '../../utils/handleFileUpload'
import { Userpic } from '../../components/Author/Userpic'
export const ProfileSettingsPage = () => {
const { t } = useLocalize()
@ -72,6 +71,8 @@ export const ProfileSettingsPage = () => {
const [hostname, setHostname] = createSignal<string | null>(null)
onMount(() => setHostname(window?.location.host))
console.log('!!! form:', form)
return (
<PageLayout>
<Show when={form}>
@ -90,16 +91,13 @@ export const ProfileSettingsPage = () => {
<form onSubmit={handleSubmit} enctype="multipart/form-data">
<h4>{t('Userpic')}</h4>
<div class="pretty-form__item">
<div class={styles.avatarContainer}>
<Show when={!isUserpicUpdating()} fallback={<Loading />}>
<Image
class={styles.avatar}
src={form.userpic}
alt={form.name}
onClick={handleAvatarClick}
/>
</Show>
</div>
<Userpic
name={form.name}
userpic={form.userpic}
isBig={true}
onClick={handleAvatarClick}
loading={isUserpicUpdating()}
/>
</div>
<h4>{t('Name')}</h4>
<p class="description">