kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Groups: add group gallery
rodzic
901ff57e13
commit
ce0557546a
|
@ -3,5 +3,6 @@ export enum Entities {
|
||||||
GROUPS = 'Groups',
|
GROUPS = 'Groups',
|
||||||
GROUP_RELATIONSHIPS = 'GroupRelationships',
|
GROUP_RELATIONSHIPS = 'GroupRelationships',
|
||||||
GROUP_MEMBERSHIPS = 'GroupMemberships',
|
GROUP_MEMBERSHIPS = 'GroupMemberships',
|
||||||
RELATIONSHIPS = 'Relationships'
|
RELATIONSHIPS = 'Relationships',
|
||||||
|
STATUSES = 'Statuses',
|
||||||
}
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
|
import LoadMore from 'soapbox/components/load-more';
|
||||||
|
import MissingIndicator from 'soapbox/components/missing-indicator';
|
||||||
|
import { Column, Spinner } from 'soapbox/components/ui';
|
||||||
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
import { useGroup, useGroupMedia } from 'soapbox/hooks/api';
|
||||||
|
|
||||||
|
import MediaItem from '../account-gallery/components/media-item';
|
||||||
|
|
||||||
|
import type { Attachment, Status } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
const GroupGallery = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const { id: groupId } = useParams<{ id: string }>();
|
||||||
|
|
||||||
|
const { group, isLoading: groupIsLoading } = useGroup(groupId);
|
||||||
|
|
||||||
|
const {
|
||||||
|
entities: statuses,
|
||||||
|
fetchNextPage,
|
||||||
|
isLoading,
|
||||||
|
hasNextPage,
|
||||||
|
} = useGroupMedia(groupId);
|
||||||
|
|
||||||
|
const attachments = statuses.reduce<Attachment[]>((result, status) => {
|
||||||
|
result.push(...status.media_attachments.map((a) => a.set('status', status)));
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenMedia = (attachment: Attachment) => {
|
||||||
|
if (attachment.type === 'video') {
|
||||||
|
dispatch(openModal('VIDEO', { media: attachment, status: attachment.status, account: attachment.account }));
|
||||||
|
} else {
|
||||||
|
const media = (attachment.status as Status).media_attachments;
|
||||||
|
const index = media.findIndex((x) => x.id === attachment.id);
|
||||||
|
|
||||||
|
dispatch(openModal('MEDIA', { media, index, status: attachment.status }));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLoading || groupIsLoading) {
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
<Spinner />
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!group) {
|
||||||
|
return (
|
||||||
|
<MissingIndicator />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={group.display_name} transparent withHeader={false}>
|
||||||
|
<div role='feed' className='grid grid-cols-2 gap-2 sm:grid-cols-3'>
|
||||||
|
{attachments.map((attachment) => (
|
||||||
|
<MediaItem
|
||||||
|
key={`${attachment.status.id}+${attachment.id}`}
|
||||||
|
attachment={attachment}
|
||||||
|
onOpenMedia={handleOpenMedia}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{(!isLoading && attachments.length === 0) && (
|
||||||
|
<div className='empty-column-indicator'>
|
||||||
|
<FormattedMessage id='account_gallery.none' defaultMessage='No media to show.' />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(hasNextPage && !isLoading) && (
|
||||||
|
<LoadMore className='my-auto' visible={!isLoading} onClick={fetchNextPage} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isLoading && (
|
||||||
|
<div className='slist__append'>
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GroupGallery;
|
|
@ -116,6 +116,7 @@ import {
|
||||||
EventInformation,
|
EventInformation,
|
||||||
EventDiscussion,
|
EventDiscussion,
|
||||||
Events,
|
Events,
|
||||||
|
GroupGallery,
|
||||||
Groups,
|
Groups,
|
||||||
GroupsDiscover,
|
GroupsDiscover,
|
||||||
GroupsPopular,
|
GroupsPopular,
|
||||||
|
@ -297,6 +298,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||||
{features.groupsPending && <WrappedRoute path='/groups/pending-requests' exact page={GroupsPendingPage} component={PendingGroupRequests} content={children} />}
|
{features.groupsPending && <WrappedRoute path='/groups/pending-requests' exact page={GroupsPendingPage} component={PendingGroupRequests} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id' exact page={GroupPage} component={GroupTimeline} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:id' exact page={GroupPage} component={GroupTimeline} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/members' exact page={GroupPage} component={GroupMembers} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:id/members' exact page={GroupPage} component={GroupMembers} content={children} />}
|
||||||
|
{features.groups && <WrappedRoute path='/groups/:id/media' publicRoute={!authenticatedProfile} component={GroupGallery} page={GroupPage} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/manage' exact page={DefaultPage} component={ManageGroup} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:id/manage' exact page={DefaultPage} component={ManageGroup} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/manage/edit' exact page={DefaultPage} component={EditGroup} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:id/manage/edit' exact page={DefaultPage} component={EditGroup} content={children} />}
|
||||||
{features.groups && <WrappedRoute path='/groups/:id/manage/blocks' exact page={DefaultPage} component={GroupBlockedMembers} content={children} />}
|
{features.groups && <WrappedRoute path='/groups/:id/manage/blocks' exact page={DefaultPage} component={GroupBlockedMembers} content={children} />}
|
||||||
|
|
|
@ -590,6 +590,10 @@ export function GroupMembershipRequests() {
|
||||||
return import(/* webpackChunkName: "features/groups" */'../../group/group-membership-requests');
|
return import(/* webpackChunkName: "features/groups" */'../../group/group-membership-requests');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function GroupGallery() {
|
||||||
|
return import(/* webpackChunkName: "features/groups" */'../../group/group-gallery');
|
||||||
|
}
|
||||||
|
|
||||||
export function CreateGroupModal() {
|
export function CreateGroupModal() {
|
||||||
return import(/* webpackChunkName: "features/groups" */'../components/modals/manage-group-modal/create-group-modal');
|
return import(/* webpackChunkName: "features/groups" */'../components/modals/manage-group-modal/create-group-modal');
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Entities } from 'soapbox/entity-store/entities';
|
||||||
|
import { useEntities } from 'soapbox/entity-store/hooks';
|
||||||
|
import { useApi } from 'soapbox/hooks/useApi';
|
||||||
|
import { statusSchema } from 'soapbox/schemas/status';
|
||||||
|
|
||||||
|
function useGroupMedia(groupId: string) {
|
||||||
|
const api = useApi();
|
||||||
|
|
||||||
|
return useEntities([Entities.STATUSES, 'groupMedia', groupId], () => {
|
||||||
|
return api.get(`/api/v1/timelines/group/${groupId}?only_media=true`);
|
||||||
|
}, { schema: statusSchema });
|
||||||
|
}
|
||||||
|
|
||||||
|
export { useGroupMedia };
|
|
@ -11,6 +11,7 @@ export { useCancelMembershipRequest } from './groups/useCancelMembershipRequest'
|
||||||
export { useCreateGroup, type CreateGroupParams } from './groups/useCreateGroup';
|
export { useCreateGroup, type CreateGroupParams } from './groups/useCreateGroup';
|
||||||
export { useDeleteGroup } from './groups/useDeleteGroup';
|
export { useDeleteGroup } from './groups/useDeleteGroup';
|
||||||
export { useDemoteGroupMember } from './groups/useDemoteGroupMember';
|
export { useDemoteGroupMember } from './groups/useDemoteGroupMember';
|
||||||
|
export { useGroupMedia } from './groups/useGroupMedia';
|
||||||
export { useGroup, useGroups } from './groups/useGroups';
|
export { useGroup, useGroups } from './groups/useGroups';
|
||||||
export { useGroupMembershipRequests } from './groups/useGroupMembershipRequests';
|
export { useGroupMembershipRequests } from './groups/useGroupMembershipRequests';
|
||||||
export { useGroupSearch } from './groups/useGroupSearch';
|
export { useGroupSearch } from './groups/useGroupSearch';
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { Tabs } from '../components/ui';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
all: { id: 'group.tabs.all', defaultMessage: 'All' },
|
all: { id: 'group.tabs.all', defaultMessage: 'All' },
|
||||||
members: { id: 'group.tabs.members', defaultMessage: 'Members' },
|
members: { id: 'group.tabs.members', defaultMessage: 'Members' },
|
||||||
|
media: { id: 'group.tabs.media', defaultMessage: 'Media' },
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IGroupPage {
|
interface IGroupPage {
|
||||||
|
@ -84,6 +85,11 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
|
||||||
name: '/groups/:id/members',
|
name: '/groups/:id/members',
|
||||||
count: pending.length,
|
count: pending.length,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.media),
|
||||||
|
to: `/groups/${group?.id}/media`,
|
||||||
|
name: '/groups/:id/media',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const renderChildren = () => {
|
const renderChildren = () => {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { normalizeStatus } from 'soapbox/normalizers';
|
||||||
|
import { toSchema } from 'soapbox/utils/normalizers';
|
||||||
|
|
||||||
|
const statusSchema = toSchema(normalizeStatus);
|
||||||
|
|
||||||
|
type Status = z.infer<typeof statusSchema>;
|
||||||
|
|
||||||
|
export { statusSchema, type Status };
|
Ładowanie…
Reference in New Issue