Skip to content

Commit

Permalink
refactor(ui): remove manual layout settings and enable automatic layo…
Browse files Browse the repository at this point in the history
…ut for article card
  • Loading branch information
NekoAria committed Nov 1, 2024
1 parent 9905b96 commit 51c4583
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 71 deletions.
41 changes: 36 additions & 5 deletions src/components/Article/ArticleCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

.article-card-info {
color: var(--color-text-3);
font-size: 13px;
font-size: 0.9rem;
}

.article-card-mini-content {
Expand Down Expand Up @@ -120,7 +120,7 @@
.icon-starred {
margin-left: 8px;
color: var(--color-text-3);
font-size: 13px;
font-size: 0.9rem;
}

.thumbnail {
Expand All @@ -134,7 +134,7 @@
margin-top: 0;
margin-bottom: 8px;
color: var(--color-text-3);
font-size: 13px;
font-size: 0.9rem;
line-height: 1.5;
}

Expand All @@ -145,8 +145,19 @@
.article-card-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
align-items: flex-start;
gap: 8px;
padding-bottom: 8px;
}

.article-title {
flex: 1;
min-width: 0;
}

.published-time {
white-space: nowrap;
align-self: start;
}

.article-card-body {
Expand All @@ -159,3 +170,23 @@
flex: 1;
min-width: 0;
}

.article-card-image-container-wide {
width: 100%;
height: 160px;
margin: 8px 0;
border-radius: var(--border-radius-medium);
overflow: hidden;
box-shadow: var(--shadow);
}

.article-card-image-container-wide .thumbnail {
width: 100%;
height: 160px;
}

.article-card-image-container-wide img {
width: 100%;
height: 100%;
object-fit: cover;
}
73 changes: 49 additions & 24 deletions src/components/Article/ArticleCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,37 @@ import FeedIcon from "../ui/FeedIcon";
import ImageWithLazyLoading from "./ImageWithLazyLoading";
import "./ArticleCard.css";

const ArticleCardImage = ({ entry, isThumbnail, setHasError }) => {
const imageSize = isThumbnail
? { width: "80px", height: "80px" }
: { width: "100%", height: "160px" };
const ASPECT_RATIO_THRESHOLD = 4 / 3;

const ArticleCardImage = ({
entry,
setHasError,
isWideImage,
onLoadComplete,
}) => {
const imageSize = isWideImage
? { width: "100%", height: "100%" }
: { width: "80px", height: "80px" };

const handleImageLoad = (e) => {
const img = e.target;
if (img) {
const aspectRatio = img.naturalWidth / img.naturalHeight;
onLoadComplete(aspectRatio);
}
};

return (
<div className={isThumbnail ? "thumbnail" : "cover-image"}>
<div className={"thumbnail"}>
<ImageWithLazyLoading
alt={entry.id}
borderRadius={isThumbnail ? "2px" : undefined}
borderRadius={"2px"}
src={entry.imgSrc}
status={entry.status}
width={imageSize.width}
height={imageSize.height}
setHasError={setHasError}
onLoad={handleImageLoad}
/>
</div>
);
Expand All @@ -40,17 +56,22 @@ const extractTextFromHtml = (html) => {
return div.textContent || div.innerText || "";
};

const ArticleCardContent = ({ entry, showFeedIcon, mini, children }) => {
const ArticleCardContent = ({ entry, showFeedIcon, children }) => {
const { showDetailedRelativeTime, showEstimatedReadingTime } =
useStore(settingsState);

const [hasError, setHasError] = useState(false);
const [isWideImage, setIsWideImage] = useState(false);

const contentClass = classNames({
"article-card-mini-content": mini,
"article-card-mini-content-padding": mini && showFeedIcon,
"article-card-mini-content": true,
"article-card-mini-content-padding": showFeedIcon,
});

const handleImageLoadComplete = (aspectRatio) => {
setIsWideImage(aspectRatio >= ASPECT_RATIO_THRESHOLD);
};

return (
<div
className={contentClass}
Expand All @@ -60,23 +81,20 @@ const ArticleCardContent = ({ entry, showFeedIcon, mini, children }) => {
opacity: entry.status === "unread" ? 1 : 0.5,
}}
>
<div className={mini ? "article-card-mini-content-text" : ""}>
<div className="article-card-mini-content-text">
<div className="article-card-header">
<div className="article-card-meta">
<Typography.Text
className="article-card-info"
className="article-card-info article-title"
style={{ lineHeight: "1em" }}
>
{showFeedIcon && (
<FeedIcon
feed={entry.feed}
className={mini ? "feed-icon-mini" : "feed-icon"}
/>
<FeedIcon feed={entry.feed} className="feed-icon-mini" />
)}
{entry.feed.title}
</Typography.Text>
<Typography.Text
className="article-card-info"
className="article-card-info published-time"
style={{ lineHeight: "1em" }}
>
{generateRelativeTime(
Expand All @@ -101,6 +119,16 @@ const ArticleCardContent = ({ entry, showFeedIcon, mini, children }) => {
{entry.title}
</Typography.Ellipsis>
</div>
{entry.imgSrc && !hasError && isWideImage && (
<div className="article-card-image-container-wide">
<ArticleCardImage
entry={entry}
setHasError={setHasError}
isWideImage={isWideImage}
onLoadComplete={handleImageLoadComplete}
/>
</div>
)}
<div className="article-card-body">
<div className="article-card-content">
<Typography.Text
Expand Down Expand Up @@ -130,12 +158,13 @@ const ArticleCardContent = ({ entry, showFeedIcon, mini, children }) => {
{entry.starred && <IconStarFill className="icon-starred" />}
</Typography.Text>
</div>
{entry.imgSrc && !hasError && (
{entry.imgSrc && !hasError && !isWideImage && (
<div className="article-card-image-container-mini">
<ArticleCardImage
entry={entry}
isThumbnail={mini}
setHasError={setHasError}
isWideImage={isWideImage}
onLoadComplete={handleImageLoadComplete}
/>
</div>
)}
Expand All @@ -146,7 +175,7 @@ const ArticleCardContent = ({ entry, showFeedIcon, mini, children }) => {
);
};

const ArticleCard = ({ entry, handleEntryClick, mini, children }) => {
const ArticleCard = ({ entry, handleEntryClick, children }) => {
const { markReadOnScroll, showFeedIcon } = useStore(settingsState);
const { activeContent } = useStore(contentState);

Expand Down Expand Up @@ -196,11 +225,7 @@ const ArticleCard = ({ entry, handleEntryClick, mini, children }) => {
>
<Card.Meta
description={
<ArticleCardContent
entry={entry}
showFeedIcon={showFeedIcon}
mini={mini}
>
<ArticleCardContent entry={entry} showFeedIcon={showFeedIcon}>
{children}
</ArticleCardContent>
}
Expand Down
9 changes: 2 additions & 7 deletions src/components/Article/ArticleList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ const LoadMoreComponent = ({ getEntries }) => {

const ArticleList = forwardRef(
({ getEntries, handleEntryClick, cardsRef }, ref) => {
const { layout, pageSize } = useStore(settingsState);
const isCompactLayout = layout === "small";
const { pageSize } = useStore(settingsState);

const { isArticleListReady, loadMoreVisible } = useStore(contentState);
const filteredEntries = useStore(filteredEntriesState);
Expand All @@ -78,10 +77,7 @@ const ArticleList = forwardRef(
const virtualizer = useVirtualizer({
count: filteredEntries.length,
getScrollElement: () => cardsRef.current,
estimateSize: useCallback(
() => (isCompactLayout ? 120 : 280),
[isCompactLayout],
),
estimateSize: useCallback(() => 160, []),
overscan: 5,
});
const virtualItems = virtualizer.getVirtualItems();
Expand Down Expand Up @@ -126,7 +122,6 @@ const ArticleList = forwardRef(
<ArticleCard
entry={filteredEntries[item.index]}
handleEntryClick={handleEntryClick}
mini={isCompactLayout}
>
<Ripple color="var(--color-text-4)" duration={1000} />
</ArticleCard>
Expand Down
9 changes: 8 additions & 1 deletion src/components/Article/ImageWithLazyLoading.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const ImageWithLazyLoading = ({
status,
width,
setHasError,
onLoad,
}) => {
const [isLoaded, setIsLoaded] = useState(false);

Expand All @@ -21,7 +22,12 @@ const ImageWithLazyLoading = ({
onChange: (inView) => {
if (inView && !isLoaded) {
const image = new Image();
image.onload = () => setIsLoaded(true);
image.onload = () => {
setIsLoaded(true);
if (onLoad) {
onLoad(image);
}
};
image.onerror = () => setHasError(true);
image.src = src;
}
Expand Down Expand Up @@ -55,6 +61,7 @@ const ImageWithLazyLoading = ({
objectFit: "cover",
borderRadius,
}}
onLoad={onLoad}
/>
) : (
<div className="skeleton-container">
Expand Down
13 changes: 3 additions & 10 deletions src/components/Article/LoadingCards.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,10 @@ import { Card, Skeleton } from "@arco-design/web-react";

import { useStore } from "@nanostores/react";
import { contentState } from "../../store/contentState";
import { settingsState } from "../../store/settingsState";
import "./LoadingCards.css";

const LoadingCard = ({ layout, isArticleListReady }) => (
<Card
className="card-style"
cover={layout === "large" ? <div className="card-cover-style" /> : null}
>
const LoadingCard = ({ isArticleListReady }) => (
<Card className="card-style" cover={null}>
<Card.Meta
description={
<Skeleton
Expand All @@ -23,17 +19,14 @@ const LoadingCard = ({ layout, isArticleListReady }) => (
);

const LoadingCards = () => {
const { layout } = useStore(settingsState);
const { isArticleListReady } = useStore(contentState);
const cardCount = layout === "large" ? 2 : 4;

return (
!isArticleListReady &&
Array.from({ length: cardCount }, (_, i) => (
Array.from({ length: 4 }, (_, i) => (
<LoadingCard
// biome-ignore lint/suspicious/noArrayIndexKey: <explanation>
key={i}
layout={layout}
isArticleListReady={isArticleListReady}
/>
))
Expand Down
15 changes: 0 additions & 15 deletions src/components/Settings/Appearance.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const Appearance = () => {
articleWidth,
fontSize,
fontFamily,
layout,
showDetailedRelativeTime,
showEstimatedReadingTime,
showFeedIcon,
Expand Down Expand Up @@ -101,20 +100,6 @@ const Appearance = () => {

<Divider />

<SettingItem
title={polyglot.t("appearance.compact_article_card_label")}
description={polyglot.t("appearance.compact_article_card_description")}
>
<Switch
checked={layout === "small"}
onChange={(value) =>
handleConfigChange({ layout: value ? "small" : "large" })
}
/>
</SettingItem>

<Divider />

<SettingItem
title={polyglot.t("appearance.show_detailed_relative_time_label")}
description={polyglot.t(
Expand Down
2 changes: 0 additions & 2 deletions src/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,6 @@
"theme_color_label": "Theme color",
"theme_color_description": "Choose your theme color",
"theme_color_aria_label": "Change theme color to %{color}",
"compact_article_card_label": "Compact article card",
"compact_article_card_description": "Use small thumbnail in article card",
"show_detailed_relative_time_label": "Show detailed relative time",
"show_detailed_relative_time_description": "Show detailed relative time in article card",
"show_estimated_reading_time_label": "Show estimated reading time",
Expand Down
2 changes: 0 additions & 2 deletions src/locales/es-ES.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,6 @@
"theme_color_label": "Color del tema",
"theme_color_description": "Escoger su color del tema",
"theme_color_aria_label": "Cambiar el color del tema a %{color}",
"compact_article_card_label": "Compactar la tarjeta de artículo",
"compact_article_card_description": "Utilizar la miniatura pequeña en la tarjeta de artículo",
"show_detailed_relative_time_label": "Mostrar tiempo relativo detallado",
"show_detailed_relative_time_description": "Mostrar tiempo relativo detallado en la tarjeta de artículo",
"show_estimated_reading_time_label": "Mostrar tiempo estimado de lectura",
Expand Down
2 changes: 0 additions & 2 deletions src/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,6 @@
"theme_color_label": "Couleur du thème",
"theme_color_description": "Choisir votre couleur de thème",
"theme_color_aria_label": "Changer la couleur du thème en %{color}",
"compact_article_card_label": "Carte d'article compacte",
"compact_article_card_description": "Utiliser une miniature petite dans la carte d'article",
"show_detailed_relative_time_label": "Afficher le temps relatif détaillé",
"show_detailed_relative_time_description": "Afficher le temps relatif détaillé dans la carte d'article",
"show_estimated_reading_time_label": "Afficher le temps de lecture estimé",
Expand Down
2 changes: 0 additions & 2 deletions src/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,6 @@
"theme_color_label": "主题颜色",
"theme_color_description": "选择您的主题颜色",
"theme_color_aria_label": "将主题颜色更改为 %{color}",
"compact_article_card_label": "紧凑文章卡片",
"compact_article_card_description": "使用小缩略图显示文章卡片",
"show_detailed_relative_time_label": "显示详细相对时间",
"show_detailed_relative_time_description": "在文章卡片中显示详细相对时间",
"show_estimated_reading_time_label": "显示预计阅读时间",
Expand Down
1 change: 0 additions & 1 deletion src/store/settingsState.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const defaultValue = {
fontSize: 1.05,
homePage: "all",
language: getBrowserLanguage(),
layout: "large",
markReadOnScroll: false,
orderBy: "created_at",
orderDirection: "desc",
Expand Down

0 comments on commit 51c4583

Please sign in to comment.