nicolium: group migrations

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-02-22 20:47:14 +01:00
parent f1aea5f17e
commit 636e488219
10 changed files with 23 additions and 112 deletions

View File

@ -10,6 +10,7 @@ import type {
Poll as BasePoll,
Relationship as BaseRelationship,
Status as BaseStatus,
GroupRelationship,
} from 'pl-api';
const STATUS_IMPORT = 'STATUS_IMPORT' as const;
@ -112,7 +113,10 @@ const importEntities =
for (const group of Object.values(groups)) {
queryClient.setQueryData<BaseGroup>(['groups', group.id], group);
if (group.relationship) {
queryClient.setQueryData<BaseGroup>(['groupRelationships', group.id], group.relationship);
queryClient.setQueryData<GroupRelationship>(
['groupRelationships', group.id],
group.relationship,
);
}
}
if (!isEmpty(polls)) {

View File

@ -14,6 +14,7 @@ import StatusTypeIcon from '@/features/status/components/status-type-icon';
import { Hotkeys } from '@/features/ui/components/hotkeys';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useAppSelector } from '@/hooks/use-app-selector';
import { useGroupQuery } from '@/queries/groups/use-group';
import { useFollowedTags } from '@/queries/hashtags/use-followed-tags';
import {
useFavouriteStatus,

View File

@ -1,3 +1,4 @@
import { useQueryClient } from '@tanstack/react-query';
import { useMatch } from '@tanstack/react-router';
import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
@ -6,7 +7,6 @@ import { groupComposeModal } from '@/actions/compose';
import ThumbNavigationLink from '@/components/thumb-navigation-link';
import Icon from '@/components/ui/icon';
import { useStatContext } from '@/contexts/stat-context';
import { Entities } from '@/entity-store/entities';
import { layouts } from '@/features/ui/router';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useAppSelector } from '@/hooks/use-app-selector';
@ -16,6 +16,8 @@ import { useModalsActions } from '@/stores/modals';
import { useIsSidebarOpen, useUiStoreActions } from '@/stores/ui';
import { isStandalone } from '@/utils/state';
import type { Group } from 'pl-api';
const messages = defineMessages({
home: { id: 'column.home', defaultMessage: 'Home' },
search: { id: 'column.search', defaultMessage: 'Search' },
@ -31,6 +33,7 @@ const ThumbNavigation: React.FC = React.memo((): JSX.Element => {
const dispatch = useAppDispatch();
const { account } = useOwnAccount();
const features = useFeatures();
const queryClient = useQueryClient();
const match = useMatch({ from: layouts.group.id, shouldThrow: false });
@ -45,7 +48,7 @@ const ThumbNavigation: React.FC = React.memo((): JSX.Element => {
const handleOpenComposeModal = () => {
if (match?.params.groupId) {
dispatch((_, getState) => {
const group = getState().entities[Entities.GROUPS]?.store[match.params.groupId];
const group = queryClient.getQueryData<Group>(['groups', match.params.groupId]);
if (group) dispatch(groupComposeModal(group));
});
} else {

View File

@ -1,37 +1,14 @@
import type { Entities } from './entities';
import type { EntitiesTransaction, Entity, ImportPosition } from './types';
import type { EntitiesTransaction, Entity } from './types';
const ENTITIES_IMPORT = 'ENTITIES_IMPORT' as const;
const ENTITIES_DELETE = 'ENTITIES_DELETE' as const;
const ENTITIES_TRANSACTION = 'ENTITIES_TRANSACTION' as const;
/** Action to import entities into the cache. */
const importEntities = (
entities: Entity[],
entityType: Entities,
listKey?: string,
pos?: ImportPosition,
) => ({
const importEntities = (entities: Entity[], entityType: Entities) => ({
type: ENTITIES_IMPORT,
entityType,
entities,
listKey,
pos,
});
interface DeleteEntitiesOpts {
preserveLists?: boolean;
}
const deleteEntities = (
ids: Iterable<string>,
entityType: string,
opts: DeleteEntitiesOpts = {},
) => ({
type: ENTITIES_DELETE,
ids,
entityType,
opts,
});
const entitiesTransaction = (transaction: EntitiesTransaction) => ({
@ -40,18 +17,12 @@ const entitiesTransaction = (transaction: EntitiesTransaction) => ({
});
/** Any action pertaining to entities. */
type EntityAction =
| ReturnType<typeof importEntities>
| ReturnType<typeof deleteEntities>
| ReturnType<typeof entitiesTransaction>;
type EntityAction = ReturnType<typeof importEntities> | ReturnType<typeof entitiesTransaction>;
export {
type DeleteEntitiesOpts,
type EntityAction,
ENTITIES_IMPORT,
ENTITIES_DELETE,
ENTITIES_TRANSACTION,
importEntities,
deleteEntities,
entitiesTransaction,
};

View File

@ -1,12 +1,6 @@
import { create, type Immutable, type Draft } from 'mutative';
import {
ENTITIES_IMPORT,
ENTITIES_DELETE,
ENTITIES_TRANSACTION,
type EntityAction,
type DeleteEntitiesOpts,
} from './actions';
import { ENTITIES_IMPORT, ENTITIES_TRANSACTION, type EntityAction } from './actions';
import { Entities } from './entities';
import { createCache, createList, updateStore, updateList } from './utils';
@ -55,33 +49,6 @@ const importEntities = (
draft[entityType] = cache;
};
const deleteEntities = (
draft: Draft<State>,
entityType: string,
ids: Iterable<string>,
opts: DeleteEntitiesOpts,
) => {
const cache = draft[entityType] ?? createCache();
for (const id of ids) {
delete cache.store[id];
if (!opts?.preserveLists) {
for (const list of Object.values(cache.lists)) {
if (list) {
list.ids.delete(id);
if (typeof list.state.totalCount === 'number') {
list.state.totalCount--;
}
}
}
}
}
draft[entityType] = cache;
};
const doTransaction = (draft: Draft<State>, transaction: EntitiesTransaction) => {
for (const [entityType, changes] of Object.entries(transaction)) {
const cache = draft[entityType] ?? createCache();
@ -101,14 +68,10 @@ const reducer = (state: Readonly<State> = {}, action: EntityAction): State => {
return create(
state,
(draft) => {
importEntities(draft, action.entityType, action.entities, action.listKey, action.pos);
importEntities(draft, action.entityType, action.entities);
},
{ enableAutoFreeze: true },
);
case ENTITIES_DELETE:
return create(state, (draft) => {
deleteEntities(draft, action.entityType, action.ids, action.opts);
});
case ENTITIES_TRANSACTION:
return create(state, (draft) => {
doTransaction(draft, action.transaction);

View File

@ -25,18 +25,12 @@ interface EntityListState {
next: (() => Promise<PaginatedResponse<any>>) | null;
/** Previous URL for pagination, if any. */
prev: (() => Promise<PaginatedResponse<any>>) | null;
/** Total number of items according to the API. */
totalCount: number | undefined;
/** Error returned from the API, if any. */
error: unknown;
/** Whether data has already been fetched */
fetched: boolean;
/** Whether data for this list is currently being fetched. */
fetching: boolean;
/** Date of the last API fetch for this list. */
lastFetchedAt: Date | undefined;
/** Whether the entities should be refetched on the next component mount. */
invalid: boolean;
}
/** Cache data pertaining to a paritcular entity type.. */

View File

@ -27,11 +27,6 @@ const updateList = (
const oldIds = Array.from(list.ids);
const ids = new Set(pos === 'start' ? [...newIds, ...oldIds] : [...oldIds, ...newIds]);
if (typeof list.state.totalCount === 'number') {
const sizeDiff = ids.size - list.ids.size;
list.state.totalCount += sizeDiff;
}
return {
...list,
ids,
@ -54,12 +49,9 @@ const createList = (): EntityList => ({
const createListState = (): EntityListState => ({
next: null,
prev: null,
totalCount: 0,
error: null,
fetched: false,
fetching: false,
lastFetchedAt: undefined,
invalid: false,
});
export { updateStore, updateList, createCache, createList };

View File

@ -5,6 +5,7 @@ import Link from '@/components/link';
import Text from '@/components/ui/text';
import Emojify from '@/features/emoji/emojify';
import { useAppSelector } from '@/hooks/use-app-selector';
import { useGroupQuery } from '@/queries/groups/use-group';
import { makeGetStatus } from '@/selectors';
interface IReplyGroupIndicator {
@ -19,7 +20,8 @@ const ReplyGroupIndicator = (props: IReplyGroupIndicator) => {
const status = useAppSelector((state) =>
getStatus(state, { id: state.compose[composeId]?.inReplyToId! }),
);
const group = status?.group;
const { data: group } = useGroupQuery(status?.group_id ?? undefined);
if (!group) {
return null;

View File

@ -69,15 +69,12 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
group: (
<Link
to='/groups/$groupId'
params={{ groupId: status.group.id }}
params={{ groupId: status.group_id }}
className='hover:underline'
>
<bdi className='truncate'>
<strong className='text-gray-800 dark:text-gray-200'>
<Emojify
text={status.account.display_name}
emojis={status.account.emojis}
/>
<Emojify text={group.display_name} emojis={group.emojis} />
</strong>
</bdi>
</Link>

View File

@ -12,7 +12,7 @@ import type { minifyAdminReport } from '@/queries/utils/minify-list';
import type { MinifiedStatus } from '@/reducers/statuses';
import type { MRFSimple } from '@/schemas/pleroma';
import type { RootState } from '@/store';
import type { Account, Filter, FilterResult, Group, NotificationGroup } from 'pl-api';
import type { Account, Filter, FilterResult, NotificationGroup } from 'pl-api';
const selectAccount = (state: RootState, accountId: string) =>
state.entities[Entities.ACCOUNTS]?.store[accountId] as Account | undefined;
@ -120,11 +120,6 @@ const makeGetStatus = () =>
state.statuses[state.statuses[id]?.reblog_id ?? ''] || null,
(state: RootState, { id }: APIStatus) =>
state.statuses[state.statuses[id]?.quote_id ?? ''] || null,
(state: RootState, { id }: APIStatus) => {
const group = state.statuses[id]?.group_id;
if (group) return state.entities[Entities.GROUPS]?.store[group] as Group;
return undefined;
},
(_state: RootState, { username }: APIStatus) => username,
(state: RootState) => state.filters,
(_state: RootState, { contextType }: FilterContext) => contextType,
@ -132,17 +127,7 @@ const makeGetStatus = () =>
(state: RootState) => state.auth.client.features,
],
(
statusBase,
statusReblog,
statusQuote,
statusGroup,
username,
filters,
contextType,
me,
features,
) => {
(statusBase, statusReblog, statusQuote, username, filters, contextType, me, features) => {
if (!statusBase) return null;
const { account } = statusBase;
const accountUsername = account.acct;
@ -165,7 +150,6 @@ const makeGetStatus = () =>
...statusBase,
reblog: statusReblog || null,
quote: statusQuote || null,
group: statusGroup ?? null,
filtered,
};
},