Skip to content

Commit

Permalink
feat: refresh communities list (#1766)
Browse files Browse the repository at this point in the history
also optimized virtual scrolling
  • Loading branch information
aeharding authored Dec 5, 2024
1 parent 8c181e2 commit 1cd3e61
Show file tree
Hide file tree
Showing 9 changed files with 464 additions and 216 deletions.
54 changes: 31 additions & 23 deletions src/features/community/list/AlphabetJump.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ const SpecialSection = {

type SpecialSectionType = (typeof SpecialSection)[keyof typeof SpecialSection];

type JumpItem = SpecialSectionType | string;
type JumpValue = SpecialSectionType | string;

type JumpItem = [JumpValue, number];

const SECTIONS = [
<IonIcon icon={menuOutline} key={0} />,
Expand Down Expand Up @@ -51,40 +53,46 @@ const SIMPLIFIED_SECTIONS = SECTIONS.reduce<typeof SECTIONS>(
[],
);

const LOOKUP: Record<string, SpecialSectionType> = {
Moderator: SpecialSection.Moderated,
Favorites: SpecialSection.Favorited,
} as const;

function mapSeparatorsToJumpSections(
separators: { label: string; index: number }[],
): JumpItem[] {
return compact(
separators.map(({ label, index }) => [LOOKUP[label] ?? label, index]),
);
}

interface AlphabetJumpProps {
virtuaRef: RefObject<VListHandle>;
hasModerated: boolean;
hasFavorited: boolean;
letters: string[];
separators: { label: string; index: number }[];
}

export default function AlphabetJump({
virtuaRef,
hasFavorited,
hasModerated,
letters,
separators,
}: AlphabetJumpProps) {
const containerElTopRef = useRef<DOMRect | undefined>();
const scrollViewRef = useRef<HTMLElement | undefined>();

const jumpTableLookup = useMemo(
() =>
buildJumpToTable(
compact([
SpecialSection.Home,
hasFavorited ? SpecialSection.Favorited : undefined,
hasModerated ? SpecialSection.Moderated : undefined,
...letters,
]),
compact([
SpecialSection.Home,
SpecialSection.Favorited,
SpecialSection.Moderated,
...alphabetUpperCase,
"#",
]),
compact(mapSeparatorsToJumpSections(separators)),
compact(
[
SpecialSection.Home,
SpecialSection.Favorited,
SpecialSection.Moderated,
...alphabetUpperCase,
"#",
].map((value, index) => [value, index]),
),
),
[hasFavorited, hasModerated, letters],
[separators],
);

const containerRef = useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -150,8 +158,8 @@ function buildJumpToTable(partial: JumpItem[], all: JumpItem[]): number[] {
let lastFound = 0;

for (let i = 0; i < all.length; i++) {
const foundIndex = partial.findIndex((p) => p === all[i]);
if (foundIndex !== -1) lastFound = foundIndex;
const foundItem = partial.find((p) => p[0] === all[i]![0]);
if (foundItem) lastFound = foundItem[1];
jumpToTable.push(lastFound);
}

Expand Down
66 changes: 47 additions & 19 deletions src/features/community/list/GuestCommunitiesList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import { IonRefresher, IonRefresherContent } from "@ionic/react";
import { Community } from "lemmy-js-client";
import { useEffect, useState } from "react";
import {
useEffect,
experimental_useEffectEvent as useEffectEvent,
useState,
} from "react";

import { clientSelector } from "#/features/auth/authSelectors";
import { CenteredSpinner } from "#/features/shared/CenteredSpinner";
import { isSafariFeedHackEnabled } from "#/routes/pages/shared/FeedContent";
import { useAppSelector } from "#/store";

import { CommunitiesListProps } from "./CommunitiesList";
Expand All @@ -19,29 +25,51 @@ const SHOW_LOCAL_ONLY = ["lemmynsfw.com"];
export default function GuestCommunitiesList({ actor }: CommunitiesListProps) {
const [communities, setCommunities] = useState<Community[] | undefined>();
const client = useAppSelector(clientSelector);
const [isListAtTop, setIsListAtTop] = useState(true);

useEffect(() => {
(async () => {
async function update() {
let communities;

try {
({ communities } = await client.listCommunities({
type_: SHOW_LOCAL_ONLY.includes(actor) ? "Local" : "All",
sort: "TopAll",
limit: 50,
}));
} catch (error) {
setCommunities(undefined);
throw error;
}

setCommunities(communities.map((c) => c.community));
}

const updateEvent = useEffectEvent(update);

useEffect(() => {
setCommunities(undefined);

let communities;

try {
({ communities } = await client.listCommunities({
type_: SHOW_LOCAL_ONLY.includes(actor) ? "Local" : "All",
sort: "TopAll",
limit: 50,
}));
} catch (error) {
setCommunities(undefined);
throw error;
}

setCommunities(communities.map((c) => c.community));
})();
updateEvent();
}, [client, actor]);

if (communities === undefined) return <CenteredSpinner />;

return <ResolvedCommunitiesList communities={communities} actor={actor} />;
return (
<>
<IonRefresher
slot="fixed"
onIonRefresh={(e) => {
updateEvent().finally(() => e.detail.complete());
}}
disabled={isSafariFeedHackEnabled && !isListAtTop}
>
<IonRefresherContent />
</IonRefresher>
<ResolvedCommunitiesList
communities={communities}
actor={actor}
onListAtTopChange={setIsListAtTop}
/>
</>
);
}
51 changes: 51 additions & 0 deletions src/features/community/list/Item.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { IonItemDivider } from "@ionic/react";

import { useAppSelector } from "#/store";

import CommunityListItem from "./items/CommunityListItem";
import SpecialItem from "./items/SpecialItem";
import { ItemType } from "./ResolvedCommunitiesList";

import sharedStyles from "#/features/shared/shared.module.css";

interface ItemProps {
item: ItemType;
actor: string;
line: boolean | undefined;
}

export default function Item({ item, actor, line }: ItemProps) {
const favorites = useAppSelector((state) => state.community.favorites);

switch (item.type) {
case "separator":
return (
<IonItemDivider className={sharedStyles.maxWidth}>
{item.value}
</IonItemDivider>
);
case "all":
case "home":
case "local":
case "mod":
return (
<SpecialItem
type={item.type}
actor={actor}
line={line}
className={sharedStyles.maxWidth}
/>
);
case "community":
case "favorite":
return (
<CommunityListItem
community={item.value}
favorites={favorites}
removeAction="follow"
line={line}
className={sharedStyles.maxWidth}
/>
);
}
}
35 changes: 29 additions & 6 deletions src/features/community/list/LoggedInCommunitiesList.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
import { IonRefresher, IonRefresherContent } from "@ionic/react";
import { compact, uniqBy } from "es-toolkit";
import { CommunityFollowerView, CommunityView } from "lemmy-js-client";
import { useState } from "react";

import { useAppSelector } from "#/store";
import { getSite } from "#/features/auth/siteSlice";
import { isSafariFeedHackEnabled } from "#/routes/pages/shared/FeedContent";
import { useAppDispatch, useAppSelector } from "#/store";

import { CommunitiesListProps } from "./CommunitiesList";
import ResolvedCommunitiesList from "./ResolvedCommunitiesList";

export default function LoggedInCommunitiesList(props: CommunitiesListProps) {
const dispatch = useAppDispatch();
const follows = useAppSelector(
(state) => state.site.response?.my_user?.follows,
);

const communityByHandle = useAppSelector(
(state) => state.community.communityByHandle,
);

const [isListAtTop, setIsListAtTop] = useState(true);

async function refresh() {
await dispatch(getSite());
}

return (
<ResolvedCommunitiesList
{...props}
communities={buildCommunities(follows, communityByHandle)}
/>
<>
<IonRefresher
slot="fixed"
onIonRefresh={(e) => {
refresh().finally(() => e.detail.complete());
}}
disabled={isSafariFeedHackEnabled && !isListAtTop}
>
<IonRefresherContent />
</IonRefresher>

<ResolvedCommunitiesList
{...props}
communities={buildCommunities(follows, communityByHandle)}
onListAtTopChange={setIsListAtTop}
/>
</>
);
}

Expand Down
10 changes: 5 additions & 5 deletions src/features/community/list/ResolvedCommunitiesList.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@
}
}

.ionList {
height: 100%;
}

.vList {
.virtualizerScrollView {
display: block;
overflow-y: auto;
contain: strict;
width: 100%;
height: 100%;

&::-webkit-scrollbar {
Expand Down
Loading

0 comments on commit 1cd3e61

Please sign in to comment.