Placeholder group cards
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
c1663d743a
commit
7d48c40b89
|
@ -310,7 +310,7 @@ const fetchGroupRelationshipsFail = (error: AxiosError) => ({
|
||||||
|
|
||||||
const joinGroup = (id: string) =>
|
const joinGroup = (id: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const locked = (getState().groups.get(id) as any).locked || false;
|
const locked = (getState().groups.items.get(id) as any).locked || false;
|
||||||
|
|
||||||
dispatch(joinGroupRequest(id, locked));
|
dispatch(joinGroupRequest(id, locked));
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ const GroupCard: React.FC<IGroupCard> = ({ group }) => {
|
||||||
<Stack className='bg-white dark:bg-primary-900 border border-solid border-gray-300 dark:border-primary-800 rounded-lg sm:rounded-xl'>
|
<Stack className='bg-white dark:bg-primary-900 border border-solid border-gray-300 dark:border-primary-800 rounded-lg sm:rounded-xl'>
|
||||||
<div className='bg-primary-100 dark:bg-gray-800 h-[120px] relative -m-[1px] mb-0 rounded-t-lg sm:rounded-t-xl'>
|
<div className='bg-primary-100 dark:bg-gray-800 h-[120px] relative -m-[1px] mb-0 rounded-t-lg sm:rounded-t-xl'>
|
||||||
{group.header && <img className='h-full w-full object-cover rounded-t-lg sm:rounded-t-xl' src={group.header} alt={intl.formatMessage(messages.groupHeader)} />}
|
{group.header && <img className='h-full w-full object-cover rounded-t-lg sm:rounded-t-xl' src={group.header} alt={intl.formatMessage(messages.groupHeader)} />}
|
||||||
<div className='absolute left-1/2 -translate-x-1/2 -translate-y-1/2'>
|
<div className='absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2'>
|
||||||
<Avatar className='ring-2 ring-white dark:ring-primary-900' src={group.avatar} size={64} />
|
<Avatar className='ring-2 ring-white dark:ring-primary-900' src={group.avatar} size={64} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,30 +10,28 @@ import ScrollableList from 'soapbox/components/scrollable-list';
|
||||||
import { Column, Spinner, Stack, Text } from 'soapbox/components/ui';
|
import { Column, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card';
|
||||||
|
|
||||||
import type { List as ImmutableList } from 'immutable';
|
import type { List as ImmutableList } from 'immutable';
|
||||||
import type { RootState } from 'soapbox/store';
|
import type { RootState } from 'soapbox/store';
|
||||||
import type { Group as GroupEntity } from 'soapbox/types/entities';
|
import type { Group as GroupEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const getOrderedGroups = createSelector([
|
const getOrderedGroups = createSelector([
|
||||||
(state: RootState) => state.groups,
|
(state: RootState) => state.groups.items,
|
||||||
|
(state: RootState) => state.groups.isLoading,
|
||||||
(state: RootState) => state.group_relationships,
|
(state: RootState) => state.group_relationships,
|
||||||
], (groups, group_relationships) => {
|
], (groups, isLoading, group_relationships) => ({
|
||||||
if (!groups) {
|
groups: (groups.toList().filter((item: GroupEntity | false) => !!item) as ImmutableList<GroupEntity>)
|
||||||
return groups;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (groups
|
|
||||||
.toList()
|
|
||||||
.filter((item: GroupEntity | false) => !!item) as ImmutableList<GroupEntity>)
|
|
||||||
.map((item) => item.set('relationship', group_relationships.get(item.id) || null))
|
.map((item) => item.set('relationship', group_relationships.get(item.id) || null))
|
||||||
.filter((item) => item.relationship?.member)
|
.filter((item) => item.relationship?.member)
|
||||||
.sort((a, b) => a.display_name.localeCompare(b.display_name));
|
.sort((a, b) => a.display_name.localeCompare(b.display_name)),
|
||||||
});
|
isLoading,
|
||||||
|
}));
|
||||||
|
|
||||||
const Groups: React.FC = () => {
|
const Groups: React.FC = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const groups = useAppSelector((state) => getOrderedGroups(state));
|
const { groups, isLoading } = useAppSelector((state) => getOrderedGroups(state));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchGroups());
|
dispatch(fetchGroups());
|
||||||
|
@ -72,6 +70,10 @@ const Groups: React.FC = () => {
|
||||||
scrollKey='groups'
|
scrollKey='groups'
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
itemClassName='py-3 last:pb-0'
|
itemClassName='py-3 last:pb-0'
|
||||||
|
isLoading
|
||||||
|
showLoading
|
||||||
|
placeholderComponent={PlaceholderGroupCard}
|
||||||
|
placeholderCount={3}
|
||||||
>
|
>
|
||||||
{groups.map((group) => (
|
{groups.map((group) => (
|
||||||
<Link to={`/groups/${group.id}`}>
|
<Link to={`/groups/${group.id}`}>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { HStack, Stack, Text } from 'soapbox/components/ui';
|
||||||
|
|
||||||
|
import { generateText, randomIntFromInterval } from '../utils';
|
||||||
|
|
||||||
|
const PlaceholderGroupCard = () => {
|
||||||
|
const groupNameLength = randomIntFromInterval(5, 25);
|
||||||
|
const roleLength = randomIntFromInterval(5, 15);
|
||||||
|
const privacyLength = randomIntFromInterval(5, 15);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='overflow-hidden animate-pulse'>
|
||||||
|
<Stack className='bg-white dark:bg-primary-900 border border-solid border-gray-300 dark:border-primary-800 rounded-lg sm:rounded-xl'>
|
||||||
|
<div className='bg-primary-100 dark:bg-gray-800 h-[120px] relative -m-[1px] mb-0 rounded-t-lg sm:rounded-t-xl'>
|
||||||
|
<div className='absolute left-1/2 bottom-0 -translate-x-1/2 translate-y-1/2'>
|
||||||
|
<div className='h-16 w-16 rounded-full bg-primary-500 ring-2 ring-white dark:ring-primary-900' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Stack className='p-3 pt-9' alignItems='center' space={3}>
|
||||||
|
<Text size='lg' weight='bold'>{generateText(groupNameLength)}</Text>
|
||||||
|
<HStack className='text-gray-700 dark:text-gray-600' space={3} wrap>
|
||||||
|
<span>{generateText(roleLength)}</span>
|
||||||
|
<span>{generateText(privacyLength)}</span>
|
||||||
|
</HStack>
|
||||||
|
</Stack>
|
||||||
|
</Stack>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaceholderGroupCard;
|
|
@ -1,6 +1,6 @@
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
import { GROUP_FETCH_FAIL, GROUP_DELETE_SUCCESS } from 'soapbox/actions/groups';
|
import { GROUP_FETCH_FAIL, GROUP_DELETE_SUCCESS, GROUP_FETCH_REQUEST } from 'soapbox/actions/groups';
|
||||||
import { GROUPS_IMPORT } from 'soapbox/actions/importer';
|
import { GROUPS_IMPORT } from 'soapbox/actions/importer';
|
||||||
import { normalizeGroup } from 'soapbox/normalizers';
|
import { normalizeGroup } from 'soapbox/normalizers';
|
||||||
|
|
||||||
|
@ -10,23 +10,30 @@ import type { APIEntity } from 'soapbox/types/entities';
|
||||||
type GroupRecord = ReturnType<typeof normalizeGroup>;
|
type GroupRecord = ReturnType<typeof normalizeGroup>;
|
||||||
type APIEntities = Array<APIEntity>;
|
type APIEntities = Array<APIEntity>;
|
||||||
|
|
||||||
type State = ImmutableMap<string, GroupRecord | false>;
|
const ReducerRecord = ImmutableRecord({
|
||||||
|
isLoading: true,
|
||||||
const normalizeGroups = (state: State, relationships: APIEntities) => {
|
items: ImmutableMap<string, GroupRecord | false>({}),
|
||||||
relationships.forEach(relationship => {
|
|
||||||
state = state.set(relationship.id, normalizeGroup(relationship));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return state;
|
type State = ReturnType<typeof ReducerRecord>;
|
||||||
};
|
|
||||||
|
|
||||||
export default function groups(state: State = ImmutableMap(), action: AnyAction) {
|
const normalizeGroups = (state: State, groups: APIEntities) =>
|
||||||
|
state.update('items', items =>
|
||||||
|
groups.reduce((items: ImmutableMap<string, GroupRecord | false>, group) =>
|
||||||
|
items.set(group.id, normalizeGroup(group)), items),
|
||||||
|
).set('isLoading', false);
|
||||||
|
|
||||||
|
export default function groups(state: State = ReducerRecord(), action: AnyAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case GROUPS_IMPORT:
|
case GROUPS_IMPORT:
|
||||||
return normalizeGroups(state, action.groups);
|
return normalizeGroups(state, action.groups);
|
||||||
|
case GROUP_FETCH_REQUEST:
|
||||||
|
return state.set('isLoading', true);
|
||||||
case GROUP_DELETE_SUCCESS:
|
case GROUP_DELETE_SUCCESS:
|
||||||
case GROUP_FETCH_FAIL:
|
case GROUP_FETCH_FAIL:
|
||||||
return state.set(action.id, false);
|
return state
|
||||||
|
.setIn(['items', action.id], false)
|
||||||
|
.set('isLoading', false);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -144,7 +144,7 @@ export const makeGetStatus = () => {
|
||||||
(state: RootState, { id }: APIStatus) => state.statuses.get(state.statuses.get(id)?.reblog || ''),
|
(state: RootState, { id }: APIStatus) => state.statuses.get(state.statuses.get(id)?.reblog || ''),
|
||||||
(state: RootState, { id }: APIStatus) => state.accounts.get(state.statuses.get(id)?.account || ''),
|
(state: RootState, { id }: APIStatus) => state.accounts.get(state.statuses.get(id)?.account || ''),
|
||||||
(state: RootState, { id }: APIStatus) => state.accounts.get(state.statuses.get(state.statuses.get(id)?.reblog || '')?.account || ''),
|
(state: RootState, { id }: APIStatus) => state.accounts.get(state.statuses.get(state.statuses.get(id)?.reblog || '')?.account || ''),
|
||||||
(state: RootState, { id }: APIStatus) => state.groups.get(state.statuses.get(id)?.group || ''),
|
(state: RootState, { id }: APIStatus) => state.groups.items.get(state.statuses.get(id)?.group || ''),
|
||||||
(_state: RootState, { username }: APIStatus) => username,
|
(_state: RootState, { username }: APIStatus) => username,
|
||||||
getFilters,
|
getFilters,
|
||||||
(state: RootState) => state.me,
|
(state: RootState) => state.me,
|
||||||
|
@ -356,7 +356,7 @@ export const makeGetStatusIds = () => createSelector([
|
||||||
|
|
||||||
export const makeGetGroup = () => {
|
export const makeGetGroup = () => {
|
||||||
return createSelector([
|
return createSelector([
|
||||||
(state: RootState, id: string) => state.groups.get(id),
|
(state: RootState, id: string) => state.groups.items.get(id),
|
||||||
(state: RootState, id: string) => state.group_relationships.get(id),
|
(state: RootState, id: string) => state.group_relationships.get(id),
|
||||||
], (base, relationship) => {
|
], (base, relationship) => {
|
||||||
if (!base) return null;
|
if (!base) return null;
|
||||||
|
|
Loading…
Reference in New Issue