Implement collection limit on frontend (#38786)
This commit is contained in:
parent
d7b60a2cb6
commit
2f0db28aa4
@ -25,12 +25,9 @@ import Column from 'mastodon/features/ui/components/column';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { useAccountId } from 'mastodon/hooks/useAccountId';
|
||||
import { useAccountVisibility } from 'mastodon/hooks/useAccountVisibility';
|
||||
import {
|
||||
fetchAccountCollections,
|
||||
selectAccountCollections,
|
||||
} from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { useAccountCollections } from '../collections';
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
|
||||
@ -59,10 +56,6 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
useEffect(() => {
|
||||
if (accountId) {
|
||||
void dispatch(fetchEndorsedAccounts({ accountId }));
|
||||
|
||||
if (collectionsEnabled) {
|
||||
void dispatch(fetchAccountCollections({ accountId }));
|
||||
}
|
||||
}
|
||||
}, [accountId, dispatch]);
|
||||
|
||||
@ -73,9 +66,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
ImmutableList(),
|
||||
) as ImmutableList<string>,
|
||||
);
|
||||
const { collections, status: collectionsLoadStatus } = useAppSelector(
|
||||
(state) => selectAccountCollections(state, accountId ?? null),
|
||||
);
|
||||
const { collections, status: collectionsLoadStatus } =
|
||||
useAccountCollections(accountId);
|
||||
|
||||
const { listedCollections = [], unlistedCollections = [] } = Object.groupBy(
|
||||
collections,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
import {
|
||||
@ -12,6 +12,9 @@ import {
|
||||
useLocation,
|
||||
} from 'react-router-dom';
|
||||
|
||||
import { Callout } from '@/mastodon/components/callout';
|
||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { initialState } from '@/mastodon/initial_state';
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { Column } from 'mastodon/components/column';
|
||||
import { ColumnHeader } from 'mastodon/components/column_header';
|
||||
@ -22,8 +25,11 @@ import {
|
||||
} from 'mastodon/reducers/slices/collections';
|
||||
import { useAppDispatch, useAppSelector } from 'mastodon/store';
|
||||
|
||||
import { useAccountCollections } from '..';
|
||||
|
||||
import { CollectionAccounts } from './accounts';
|
||||
import { CollectionDetails } from './details';
|
||||
import classes from './styles.module.scss';
|
||||
|
||||
export const messages = defineMessages({
|
||||
create: {
|
||||
@ -61,11 +67,14 @@ function usePageTitle(id: string | null) {
|
||||
}
|
||||
}
|
||||
|
||||
export const userCollectionLimit = initialState?.role?.collection_limit ?? 0;
|
||||
|
||||
export const CollectionEditorPage: React.FC<{
|
||||
multiColumn?: boolean;
|
||||
}> = ({ multiColumn }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const accountId = useCurrentAccountId();
|
||||
const { id = null } = useParams<{ id?: string }>();
|
||||
const { path } = useRouteMatch();
|
||||
const collection = useAppSelector((state) =>
|
||||
@ -73,7 +82,18 @@ export const CollectionEditorPage: React.FC<{
|
||||
);
|
||||
const editorStateId = useAppSelector((state) => state.collections.editor.id);
|
||||
const isEditMode = !!id;
|
||||
const isLoading = isEditMode && !collection;
|
||||
|
||||
// When creating a new collection, we load the current account's collections
|
||||
// to determine if they're allowed to create more.
|
||||
const { collections: collectionList, status: collectionListStatus } =
|
||||
useAccountCollections(isEditMode ? null : accountId);
|
||||
|
||||
const isLoading =
|
||||
(isEditMode && !collection) ||
|
||||
(!isEditMode && collectionListStatus === 'loading');
|
||||
|
||||
const canCreateMoreCollections =
|
||||
isEditMode || collectionList.length < userCollectionLimit;
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
@ -108,7 +128,7 @@ export const CollectionEditorPage: React.FC<{
|
||||
<div className='scrollable'>
|
||||
{isLoading ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
) : canCreateMoreCollections ? (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
@ -123,6 +143,8 @@ export const CollectionEditorPage: React.FC<{
|
||||
render={() => <CollectionDetails />}
|
||||
/>
|
||||
</Switch>
|
||||
) : (
|
||||
<MaxCollectionsCallout />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -133,3 +155,21 @@ export const CollectionEditorPage: React.FC<{
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export const MaxCollectionsCallout: React.FC = () => (
|
||||
<Callout
|
||||
className={classes.maxCollectionsError}
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='collections.maximum_collection_count_reached'
|
||||
defaultMessage='You have created the maximum number of collections'
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='collections.maximum_collection_count_description'
|
||||
defaultMessage='Your server allows creation of up to {count} collections.'
|
||||
values={{ count: userCollectionLimit }}
|
||||
/>
|
||||
</Callout>
|
||||
);
|
||||
|
||||
@ -77,3 +77,7 @@
|
||||
.suggestionGroup {
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.maxCollectionsError {
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
@ -25,7 +25,12 @@ import {
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { CollectionListItem } from './components/collection_list_item';
|
||||
import { messages as editorMessages } from './editor';
|
||||
import {
|
||||
messages as editorMessages,
|
||||
MaxCollectionsCallout,
|
||||
userCollectionLimit,
|
||||
} from './editor';
|
||||
import { areCollectionsEnabled } from './utils';
|
||||
|
||||
const messages = defineMessages({
|
||||
headingMe: { id: 'column.my_collections', defaultMessage: 'My collections' },
|
||||
@ -35,24 +40,27 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
export function useAccountCollections(accountId: string | null | undefined) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && areCollectionsEnabled()) {
|
||||
void dispatch(fetchAccountCollections({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
return useAppSelector((state) => selectAccountCollections(state, accountId));
|
||||
}
|
||||
|
||||
export const Collections: React.FC<{
|
||||
multiColumn?: boolean;
|
||||
}> = ({ multiColumn }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
const me = useCurrentAccountId();
|
||||
const accountId = useAccountId();
|
||||
const account = useAccount(accountId);
|
||||
|
||||
const { collections, status } = useAppSelector((state) =>
|
||||
selectAccountCollections(state, accountId),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId) {
|
||||
void dispatch(fetchAccountCollections({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
const { collections, status } = useAccountCollections(accountId);
|
||||
|
||||
const emptyMessage =
|
||||
status === 'error' || !accountId ? (
|
||||
@ -79,6 +87,7 @@ export const Collections: React.FC<{
|
||||
</>
|
||||
);
|
||||
|
||||
const canCreateMoreCollections = collections.length < userCollectionLimit;
|
||||
const isOwnCollection = accountId === me;
|
||||
const titleMessage = isOwnCollection
|
||||
? messages.headingMe
|
||||
@ -99,7 +108,9 @@ export const Collections: React.FC<{
|
||||
iconComponent={CollectionsFilledIcon}
|
||||
multiColumn={multiColumn}
|
||||
extraButton={
|
||||
isOwnCollection && (
|
||||
isOwnCollection &&
|
||||
status === 'idle' &&
|
||||
canCreateMoreCollections && (
|
||||
<Link
|
||||
to='/collections/new'
|
||||
className='column-header__button'
|
||||
@ -113,6 +124,9 @@ export const Collections: React.FC<{
|
||||
/>
|
||||
|
||||
<Scrollable>
|
||||
{status === 'idle' && !canCreateMoreCollections && (
|
||||
<MaxCollectionsCallout />
|
||||
)}
|
||||
<ItemList emptyMessage={emptyMessage} isLoading={status === 'loading'}>
|
||||
{collections.map((item, index) => (
|
||||
<CollectionListItem
|
||||
|
||||
@ -57,6 +57,7 @@ interface Role {
|
||||
permissions: string;
|
||||
color: string;
|
||||
highlighted: boolean;
|
||||
collection_limit: number;
|
||||
}
|
||||
|
||||
interface InitialWrapstodonState {
|
||||
|
||||
@ -398,6 +398,8 @@
|
||||
"collections.manage_accounts": "Manage accounts",
|
||||
"collections.mark_as_sensitive": "Mark as sensitive",
|
||||
"collections.mark_as_sensitive_hint": "Hides the collection's description and accounts behind a content warning. The collection name will still be visible.",
|
||||
"collections.maximum_collection_count_description": "Your server allows creation of up to {count} collections.",
|
||||
"collections.maximum_collection_count_reached": "You have created the maximum number of collections",
|
||||
"collections.name_length_hint": "40 characters limit",
|
||||
"collections.new_collection": "New collection",
|
||||
"collections.no_collections_yet": "No collections yet.",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user