From b6afb8ae01f38d5ea2c9e72c5f5324d8d1de1c2f Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 22 Dec 2022 11:23:18 -0500 Subject: [PATCH 1/6] Add support for pinning an avatar in the Carousel --- .../features/feed-filtering/feed-carousel.tsx | 184 ++++++++++++------ .../components/placeholder-avatar.tsx | 4 +- 2 files changed, 122 insertions(+), 66 deletions(-) diff --git a/app/soapbox/features/feed-filtering/feed-carousel.tsx b/app/soapbox/features/feed-filtering/feed-carousel.tsx index 481ae138e..23f417959 100644 --- a/app/soapbox/features/feed-filtering/feed-carousel.tsx +++ b/app/soapbox/features/feed-filtering/feed-carousel.tsx @@ -1,5 +1,5 @@ import classNames from 'clsx'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { FormattedMessage } from 'react-intl'; import { replaceHomeTimeline } from 'soapbox/actions/timelines'; @@ -9,7 +9,10 @@ import { Avatar, useCarouselAvatars, useMarkAsSeen } from 'soapbox/queries/carou import { Card, HStack, Icon, Stack, Text } from '../../components/ui'; import PlaceholderAvatar from '../placeholder/components/placeholder-avatar'; -const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolean, onViewed: (account_id: string) => void }) => { +const CarouselItem = React.forwardRef(( + { avatar, seen, onViewed, onPinned }: { avatar: Avatar, seen: boolean, onViewed: (account_id: string) => void, onPinned?: (avatar: null | Avatar) => void }, + ref: any, +) => { const dispatch = useAppDispatch(); const markAsSeen = useMarkAsSeen(); @@ -28,7 +31,15 @@ const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolea if (isSelected) { dispatch(replaceHomeTimeline(null, { maxId: null }, () => setLoading(false))); + + if (onPinned) { + onPinned(null); + } } else { + if (onPinned) { + onPinned(avatar); + } + onViewed(avatar.account_id); markAsSeen.mutate(avatar.account_id); dispatch(replaceHomeTimeline(avatar.account_id, { maxId: null }, () => setLoading(false))); @@ -37,14 +48,15 @@ const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolea return (
- -
+ +
{isSelected && (
@@ -54,7 +66,7 @@ const CarouselItem = ({ avatar, seen, onViewed }: { avatar: Avatar, seen: boolea
); -}; +}); const FeedCarousel = () => { const { data: avatars, isFetching, isError } = useCarouselAvatars(); - const [cardRef, setCardRef, { width }] = useDimensions(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [_ref, setContainerRef, { width }] = useDimensions(); const [seenAccountIds, setSeenAccountIds] = useState([]); const [pageSize, setPageSize] = useState(0); const [currentPage, setCurrentPage] = useState(1); + const [pinnedAvatar, setPinnedAvatar] = useState(null); + + const avatarsToList = useMemo(() => { + const list = avatars.filter((avatar) => avatar.account_id !== pinnedAvatar?.account_id); + if (pinnedAvatar) { + return [null, ...list]; + } + + return list; + }, [avatars, pinnedAvatar]); const numberOfPages = Math.ceil(avatars.length / pageSize); - const widthPerAvatar = (cardRef?.scrollWidth || 0) / avatars.length; + const widthPerAvatar = width / (Math.floor(width / 80)); const hasNextPage = currentPage < numberOfPages && numberOfPages > 1; const hasPrevPage = currentPage > 1 && numberOfPages > 1; @@ -118,67 +141,100 @@ const FeedCarousel = () => { ); } - if (avatars.length === 0) { - return null; - } - return ( - -
- {hasPrevPage && ( -
-
- -
-
- )} +
+ +
+ +
- - {isFetching ? ( - new Array(pageSize).fill(0).map((_, idx) => ( -
- -
- )) - ) : ( - avatars.map((avatar) => ( +
+ {pinnedAvatar ? ( +
setPinnedAvatar(avatar)} /> - )) - )} - - - {hasNextPage && ( -
-
-
-
- )} -
- + ) : null} + + + {isFetching ? ( + new Array(20).fill(0).map((_, idx) => ( +
+ +
+ )) + ) : ( + avatarsToList.map((avatar: any, index) => ( +
+ {avatar === null ? ( + +
+
+
+ + ) : ( + { + setPinnedAvatar(null); + setTimeout(() => { + setPinnedAvatar(avatar); + }, 1); + }} + onViewed={markAsSeen} + /> + )} +
+ )) + )} + +
+ +
+ +
+
+
); }; diff --git a/app/soapbox/features/placeholder/components/placeholder-avatar.tsx b/app/soapbox/features/placeholder/components/placeholder-avatar.tsx index 7d9fa62f0..9d2e1a3ec 100644 --- a/app/soapbox/features/placeholder/components/placeholder-avatar.tsx +++ b/app/soapbox/features/placeholder/components/placeholder-avatar.tsx @@ -21,14 +21,14 @@ const PlaceholderAvatar: React.FC = ({ size, withText = fals }, [size]); return ( - +
{withText && ( -
+
)} ); From 51146574d4a3c3c393b4c04a9c3cbfa8f47ee4f8 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 22 Dec 2022 12:02:17 -0500 Subject: [PATCH 2/6] Fix tests --- .../__tests__/feed-carousel.test.tsx | 31 +++---------------- .../features/feed-filtering/feed-carousel.tsx | 6 +++- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx index dc34e73e0..71d1c9f29 100644 --- a/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx +++ b/app/soapbox/features/feed-filtering/__tests__/feed-carousel.test.tsx @@ -8,7 +8,7 @@ import { render, screen, waitFor } from '../../../jest/test-helpers'; import FeedCarousel from '../feed-carousel'; jest.mock('../../../hooks/useDimensions', () => ({ - useDimensions: () => [{ scrollWidth: 190 }, null, { width: 100 }], + useDimensions: () => [{ scrollWidth: 190 }, null, { width: 300 }], })); (window as any).ResizeObserver = class ResizeObserver { @@ -21,27 +21,6 @@ jest.mock('../../../hooks/useDimensions', () => ({ describe('', () => { let store: any; - describe('with "carousel" disabled', () => { - beforeEach(() => { - store = { - instance: { - version: '2.7.2 (compatible; Pleroma 2.4.52-1337-g4779199e.gleasonator+soapbox)', - pleroma: ImmutableMap({ - metadata: ImmutableMap({ - features: [], - }), - }), - }, - }; - }); - - it('should render nothing', () => { - render(, undefined, store); - - expect(screen.queryAllByTestId('feed-carousel')).toHaveLength(0); - }); - }); - describe('with "carousel" enabled', () => { beforeEach(() => { store = { @@ -167,15 +146,15 @@ describe('', () => { render(, undefined, store); await waitFor(() => { - expect(screen.getByTestId('next-page')).toBeInTheDocument(); - expect(screen.queryAllByTestId('prev-page')).toHaveLength(0); + expect(screen.getByTestId('prev-page')).toHaveAttribute('disabled'); + expect(screen.getByTestId('next-page')).not.toHaveAttribute('disabled'); }); await user.click(screen.getByTestId('next-page')); await waitFor(() => { - expect(screen.getByTestId('prev-page')).toBeInTheDocument(); - expect(screen.queryAllByTestId('next-page')).toHaveLength(0); + expect(screen.getByTestId('prev-page')).not.toHaveAttribute('disabled'); + expect(screen.getByTestId('next-page')).toHaveAttribute('disabled'); }); }); }); diff --git a/app/soapbox/features/feed-filtering/feed-carousel.tsx b/app/soapbox/features/feed-filtering/feed-carousel.tsx index 23f417959..27b23a503 100644 --- a/app/soapbox/features/feed-filtering/feed-carousel.tsx +++ b/app/soapbox/features/feed-filtering/feed-carousel.tsx @@ -83,7 +83,7 @@ const CarouselItem = React.forwardRef(( }); const FeedCarousel = () => { - const { data: avatars, isFetching, isError } = useCarouselAvatars(); + const { data: avatars, isFetching, isFetched, isError } = useCarouselAvatars(); // eslint-disable-next-line @typescript-eslint/no-unused-vars const [_ref, setContainerRef, { width }] = useDimensions(); @@ -141,6 +141,10 @@ const FeedCarousel = () => { ); } + if (isFetched && avatars.length === 0) { + return null; + } + return (
Date: Sat, 24 Dec 2022 17:04:29 -0600 Subject: [PATCH 3/6] Prepare for v3.0.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f2904ec..4b54e4397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [3.0.0] - 2022-12-25 ### Added - Editing: ability to edit posts and view edit history (on Rebased, Pleroma, and Mastodon). - Events: ability to create, view, and comment on Events (on Rebased). From d1223e487481ab01e489616006531e58b2f9b939 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 25 Dec 2022 08:16:04 -0600 Subject: [PATCH 4/6] Prepare for 3.1.x --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b54e4397..268d5caad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,16 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Added + +### Changed + +### Fixed + ## [3.0.0] - 2022-12-25 + ### Added - Editing: ability to edit posts and view edit history (on Rebased, Pleroma, and Mastodon). - Events: ability to create, view, and comment on Events (on Rebased). diff --git a/package.json b/package.json index bb73d386c..abc321f7c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "soapbox", "displayName": "Soapbox", - "version": "3.0.0", + "version": "3.1.0", "description": "Soapbox frontend for the Fediverse.", "homepage": "https://soapbox.pub/", "repository": { From b314bb7af1c1edc338d38316b316479d565e6d86 Mon Sep 17 00:00:00 2001 From: Ryle Date: Mon, 26 Dec 2022 01:36:13 +0000 Subject: [PATCH 5/6] Add additional backend URIs. /images is used by manifest.json, /favicon.ico is used by browsers to set favicons, but some bug out for bookmarks if this isn't route excepted, /apple-touch-icon.png is the default iOS/iPad file for specifying icons, /browserconfig.xml is used by Windows for metro tiles for added pages, /robots.txt is used by bots to determine if access is permitted to pages. Some bots that use Selenium break when the routing isn't exempt. --- webpack/production.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/webpack/production.ts b/webpack/production.ts index a1af5a0f9..9139cab98 100644 --- a/webpack/production.ts +++ b/webpack/production.ts @@ -131,6 +131,11 @@ const configuration: Configuration = { '/socket', '/static', '/unsubscribe', + '/images', + '/favicon.ico', + '/apple-touch-icon.png', + '/browserconfig.xml', + '/robots.txt', ]; if (backendRoutes.some(path => pathname.startsWith(path)) || pathname.endsWith('/embed')) { From 6d46abadc1850f52714b5450ca85b5749acbf408 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 27 Dec 2022 11:41:41 -0600 Subject: [PATCH 6/6] Don't serve favicon.png with ServiceWorker --- webpack/production.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/webpack/production.ts b/webpack/production.ts index 9139cab98..9ee4cf089 100644 --- a/webpack/production.ts +++ b/webpack/production.ts @@ -133,6 +133,7 @@ const configuration: Configuration = { '/unsubscribe', '/images', '/favicon.ico', + '/favicon.png', '/apple-touch-icon.png', '/browserconfig.xml', '/robots.txt',