[Glitch] Display public collections on profile "Featured tab"
Port 440466c246a6ad372ce2c66eb56acc678ff600c4 to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
83b4a7845c
commit
ae6dbbcc53
@ -17,8 +17,15 @@ import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_col
|
|||||||
import Column from 'flavours/glitch/features/ui/components/column';
|
import Column from 'flavours/glitch/features/ui/components/column';
|
||||||
import { useAccountId } from 'flavours/glitch/hooks/useAccountId';
|
import { useAccountId } from 'flavours/glitch/hooks/useAccountId';
|
||||||
import { useAccountVisibility } from 'flavours/glitch/hooks/useAccountVisibility';
|
import { useAccountVisibility } from 'flavours/glitch/hooks/useAccountVisibility';
|
||||||
|
import {
|
||||||
|
fetchAccountCollections,
|
||||||
|
selectAccountCollections,
|
||||||
|
} from 'flavours/glitch/reducers/slices/collections';
|
||||||
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
import { useAppDispatch, useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
|
import { CollectionListItem } from '../collections/detail/collection_list_item';
|
||||||
|
import { areCollectionsEnabled } from '../collections/utils';
|
||||||
|
|
||||||
import { EmptyMessage } from './components/empty_message';
|
import { EmptyMessage } from './components/empty_message';
|
||||||
import { FeaturedTag } from './components/featured_tag';
|
import { FeaturedTag } from './components/featured_tag';
|
||||||
import type { TagMap } from './components/featured_tag';
|
import type { TagMap } from './components/featured_tag';
|
||||||
@ -42,6 +49,9 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
if (accountId) {
|
if (accountId) {
|
||||||
void dispatch(fetchFeaturedTags({ accountId }));
|
void dispatch(fetchFeaturedTags({ accountId }));
|
||||||
void dispatch(fetchEndorsedAccounts({ accountId }));
|
void dispatch(fetchEndorsedAccounts({ accountId }));
|
||||||
|
if (areCollectionsEnabled()) {
|
||||||
|
void dispatch(fetchAccountCollections({ accountId }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [accountId, dispatch]);
|
}, [accountId, dispatch]);
|
||||||
|
|
||||||
@ -64,6 +74,14 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
ImmutableList(),
|
ImmutableList(),
|
||||||
) as ImmutableList<string>,
|
) as ImmutableList<string>,
|
||||||
);
|
);
|
||||||
|
const { collections, status } = useAppSelector((state) =>
|
||||||
|
selectAccountCollections(state, accountId ?? null),
|
||||||
|
);
|
||||||
|
const publicCollections = collections.filter(
|
||||||
|
// This filter only applies when viewing your own profile, where the endpoint
|
||||||
|
// returns all collections, but we hide unlisted ones here to avoid confusion
|
||||||
|
(item) => item.discoverable,
|
||||||
|
);
|
||||||
|
|
||||||
if (accountId === null) {
|
if (accountId === null) {
|
||||||
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
||||||
@ -101,6 +119,25 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
|||||||
{accountId && (
|
{accountId && (
|
||||||
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
|
<AccountHeader accountId={accountId} hideTabs={forceEmptyState} />
|
||||||
)}
|
)}
|
||||||
|
{publicCollections.length > 0 && status === 'idle' && (
|
||||||
|
<>
|
||||||
|
<h4 className='column-subheading'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='account.featured.collections'
|
||||||
|
defaultMessage='Collections'
|
||||||
|
/>
|
||||||
|
</h4>
|
||||||
|
<section>
|
||||||
|
{publicCollections.map((item, index) => (
|
||||||
|
<CollectionListItem
|
||||||
|
key={item.id}
|
||||||
|
collection={item}
|
||||||
|
withoutBorder={index === publicCollections.length - 1}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{!featuredTags.isEmpty() && (
|
{!featuredTags.isEmpty() && (
|
||||||
<>
|
<>
|
||||||
<h4 className='column-subheading'>
|
<h4 className='column-subheading'>
|
||||||
|
|||||||
@ -2,15 +2,17 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-inline: 10px;
|
padding-inline: 16px;
|
||||||
padding-inline-end: 5px;
|
|
||||||
border-bottom: 1px solid var(--color-border-primary);
|
&:not(.wrapperWithoutBorder) {
|
||||||
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding: 15px 5px;
|
padding-block: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
|
|||||||
@ -67,13 +67,18 @@ export const CollectionMetaData: React.FC<{
|
|||||||
|
|
||||||
export const CollectionListItem: React.FC<{
|
export const CollectionListItem: React.FC<{
|
||||||
collection: ApiCollectionJSON;
|
collection: ApiCollectionJSON;
|
||||||
}> = ({ collection }) => {
|
withoutBorder?: boolean;
|
||||||
|
}> = ({ collection, withoutBorder }) => {
|
||||||
const { id, name } = collection;
|
const { id, name } = collection;
|
||||||
const linkId = useId();
|
const linkId = useId();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
className={classNames(classes.wrapper, 'focusable')}
|
className={classNames(
|
||||||
|
classes.wrapper,
|
||||||
|
'focusable',
|
||||||
|
withoutBorder && classes.wrapperWithoutBorder,
|
||||||
|
)}
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
aria-labelledby={linkId}
|
aria-labelledby={linkId}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { useCallback, useMemo } from 'react';
|
|||||||
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { matchPath } from 'react-router';
|
||||||
|
|
||||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||||
import MoreVertIcon from '@/material-icons/400-24px/more_vert.svg?react';
|
import MoreVertIcon from '@/material-icons/400-24px/more_vert.svg?react';
|
||||||
import { openModal } from 'flavours/glitch/actions/modal';
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
@ -9,6 +11,7 @@ import type { ApiCollectionJSON } from 'flavours/glitch/api_types/collections';
|
|||||||
import { Dropdown } from 'flavours/glitch/components/dropdown_menu';
|
import { Dropdown } from 'flavours/glitch/components/dropdown_menu';
|
||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
import { me } from 'flavours/glitch/initial_state';
|
import { me } from 'flavours/glitch/initial_state';
|
||||||
|
import type { MenuItem } from 'flavours/glitch/models/dropdown_menu';
|
||||||
import { useAppDispatch } from 'flavours/glitch/store';
|
import { useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import { messages as editorMessages } from '../editor';
|
import { messages as editorMessages } from '../editor';
|
||||||
@ -70,7 +73,7 @@ export const CollectionMenu: React.FC<{
|
|||||||
|
|
||||||
const menu = useMemo(() => {
|
const menu = useMemo(() => {
|
||||||
if (isOwnCollection) {
|
if (isOwnCollection) {
|
||||||
const commonItems = [
|
const commonItems: MenuItem[] = [
|
||||||
{
|
{
|
||||||
text: intl.formatMessage(editorMessages.manageAccounts),
|
text: intl.formatMessage(editorMessages.manageAccounts),
|
||||||
to: `/collections/${id}/edit`,
|
to: `/collections/${id}/edit`,
|
||||||
@ -97,17 +100,31 @@ export const CollectionMenu: React.FC<{
|
|||||||
return commonItems;
|
return commonItems;
|
||||||
}
|
}
|
||||||
} else if (ownerAccount) {
|
} else if (ownerAccount) {
|
||||||
return [
|
const items: MenuItem[] = [
|
||||||
{
|
|
||||||
text: intl.formatMessage(messages.viewOtherCollections),
|
|
||||||
to: `/@${ownerAccount.acct}/featured`,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
{
|
{
|
||||||
text: intl.formatMessage(messages.report),
|
text: intl.formatMessage(messages.report),
|
||||||
action: openReportModal,
|
action: openReportModal,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
const featuredCollectionsPath = `/@${ownerAccount.acct}/featured`;
|
||||||
|
// Don't show menu link to featured collections while on that very page
|
||||||
|
if (
|
||||||
|
!matchPath(location.pathname, {
|
||||||
|
path: featuredCollectionsPath,
|
||||||
|
exact: true,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
items.unshift(
|
||||||
|
...[
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.viewOtherCollections),
|
||||||
|
to: featuredCollectionsPath,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
|
|
||||||
|
import { useRelationship } from '@/flavours/glitch/hooks/useRelationship';
|
||||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||||
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
||||||
import { showAlert } from 'flavours/glitch/actions/alerts';
|
import { showAlert } from 'flavours/glitch/actions/alerts';
|
||||||
@ -123,6 +124,28 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const CollectionAccountItem: React.FC<{
|
||||||
|
accountId: string | undefined;
|
||||||
|
collectionOwnerId: string;
|
||||||
|
}> = ({ accountId, collectionOwnerId }) => {
|
||||||
|
const relationship = useRelationship(accountId);
|
||||||
|
|
||||||
|
if (!accountId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// When viewing your own collection, only show the Follow button
|
||||||
|
// for accounts you're not following (anymore).
|
||||||
|
// Otherwise, always show the follow button in its various states.
|
||||||
|
const withoutButton =
|
||||||
|
accountId === me ||
|
||||||
|
!relationship ||
|
||||||
|
(collectionOwnerId === me &&
|
||||||
|
(relationship.following || relationship.requested));
|
||||||
|
|
||||||
|
return <Account minimal={withoutButton} withMenu={false} id={accountId} />;
|
||||||
|
};
|
||||||
|
|
||||||
export const CollectionDetailPage: React.FC<{
|
export const CollectionDetailPage: React.FC<{
|
||||||
multiColumn?: boolean;
|
multiColumn?: boolean;
|
||||||
}> = ({ multiColumn }) => {
|
}> = ({ multiColumn }) => {
|
||||||
@ -163,11 +186,13 @@ export const CollectionDetailPage: React.FC<{
|
|||||||
collection ? <CollectionHeader collection={collection} /> : null
|
collection ? <CollectionHeader collection={collection} /> : null
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{collection?.items.map(({ account_id }) =>
|
{collection?.items.map(({ account_id }) => (
|
||||||
account_id ? (
|
<CollectionAccountItem
|
||||||
<Account key={account_id} minimal id={account_id} />
|
key={account_id}
|
||||||
) : null,
|
accountId={account_id}
|
||||||
)}
|
collectionOwnerId={collection.account_id}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
|
|
||||||
<Helmet>
|
<Helmet>
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import { Icon } from 'flavours/glitch/components/icon';
|
|||||||
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
import ScrollableList from 'flavours/glitch/components/scrollable_list';
|
||||||
import {
|
import {
|
||||||
fetchAccountCollections,
|
fetchAccountCollections,
|
||||||
selectMyCollections,
|
selectAccountCollections,
|
||||||
} from 'flavours/glitch/reducers/slices/collections';
|
} from 'flavours/glitch/reducers/slices/collections';
|
||||||
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
import { useAppSelector, useAppDispatch } from 'flavours/glitch/store';
|
||||||
|
|
||||||
@ -31,7 +31,9 @@ export const Collections: React.FC<{
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const me = useAppSelector((state) => state.meta.get('me') as string);
|
const me = useAppSelector((state) => state.meta.get('me') as string);
|
||||||
const { collections, status } = useAppSelector(selectMyCollections);
|
const { collections, status } = useAppSelector((state) =>
|
||||||
|
selectAccountCollections(state, me),
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void dispatch(fetchAccountCollections({ accountId: me }));
|
void dispatch(fetchAccountCollections({ accountId: me }));
|
||||||
|
|||||||
@ -229,14 +229,16 @@ interface AccountCollectionQuery {
|
|||||||
collections: ApiCollectionJSON[];
|
collections: ApiCollectionJSON[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const selectMyCollections = createAppSelector(
|
export const selectAccountCollections = createAppSelector(
|
||||||
[
|
[
|
||||||
(state) => state.meta.get('me') as string,
|
(_, accountId: string | null) => accountId,
|
||||||
(state) => state.collections.accountCollections,
|
(state) => state.collections.accountCollections,
|
||||||
(state) => state.collections.collections,
|
(state) => state.collections.collections,
|
||||||
],
|
],
|
||||||
(me, collectionsByAccountId, collectionsMap) => {
|
(accountId, collectionsByAccountId, collectionsMap) => {
|
||||||
const myCollectionsQuery = collectionsByAccountId[me];
|
const myCollectionsQuery = accountId
|
||||||
|
? collectionsByAccountId[accountId]
|
||||||
|
: null;
|
||||||
|
|
||||||
if (!myCollectionsQuery) {
|
if (!myCollectionsQuery) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user