Merge branch 'main' into remove-immutable-compose

remove-immutable-compose
danidfra 2025-01-03 16:45:36 -03:00
commit 1e65434824
11 zmienionych plików z 52 dodań i 68 usunięć

Wyświetl plik

@ -1,6 +1,7 @@
import { HTTPError } from 'soapbox/api/HTTPError.ts'; import { HTTPError } from 'soapbox/api/HTTPError.ts';
import { importEntities } from 'soapbox/entity-store/actions.ts'; import { importEntities } from 'soapbox/entity-store/actions.ts';
import { Entities } from 'soapbox/entity-store/entities.ts'; import { Entities } from 'soapbox/entity-store/entities.ts';
import { relationshipSchema } from 'soapbox/schemas/relationship.ts';
import { selectAccount } from 'soapbox/selectors/index.ts'; import { selectAccount } from 'soapbox/selectors/index.ts';
import { isLoggedIn } from 'soapbox/utils/auth.ts'; import { isLoggedIn } from 'soapbox/utils/auth.ts';
import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features.ts'; import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features.ts';
@ -15,7 +16,7 @@ import {
import type { Map as ImmutableMap } from 'immutable'; import type { Map as ImmutableMap } from 'immutable';
import type { AppDispatch, RootState } from 'soapbox/store.ts'; import type { AppDispatch, RootState } from 'soapbox/store.ts';
import type { APIEntity, Status } from 'soapbox/types/entities.ts'; import type { APIEntity, Relationship, Status } from 'soapbox/types/entities.ts';
import type { History } from 'soapbox/types/history.ts'; import type { History } from 'soapbox/types/history.ts';
const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST'; const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST';
@ -609,7 +610,7 @@ const expandFollowingFail = (id: string, error: unknown) => ({
}); });
const fetchRelationships = (accountIds: string[]) => const fetchRelationships = (accountIds: string[]) =>
(dispatch: AppDispatch, getState: () => RootState) => { async (dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null; if (!isLoggedIn(getState)) return null;
const loadedRelationships = getState().relationships; const loadedRelationships = getState().relationships;
@ -621,15 +622,32 @@ const fetchRelationships = (accountIds: string[]) =>
dispatch(fetchRelationshipsRequest(newAccountIds)); dispatch(fetchRelationshipsRequest(newAccountIds));
return api(getState) const results: Relationship[] = [];
.get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`)
.then((response) => response.json()).then((data) => { try {
dispatch(importEntities(data, Entities.RELATIONSHIPS)); for (const ids of chunkArray(newAccountIds, 20)) {
dispatch(fetchRelationshipsSuccess(data)); const response = await api(getState).get('/api/v1/accounts/relationships', { searchParams: { id: ids } });
}) const json = await response.json();
.catch(error => dispatch(fetchRelationshipsFail(error))); const data = relationshipSchema.array().parse(json);
results.push(...data);
}
dispatch(importEntities(results, Entities.RELATIONSHIPS));
dispatch(fetchRelationshipsSuccess(results));
} catch (error) {
dispatch(fetchRelationshipsFail(error));
}
}; };
function* chunkArray<T>(array: T[], chunkSize: number): Iterable<T[]> {
if (chunkSize <= 0) throw new Error('Chunk size must be greater than zero.');
for (let i = 0; i < array.length; i += chunkSize) {
yield array.slice(i, i + chunkSize);
}
}
const fetchRelationshipsRequest = (ids: string[]) => ({ const fetchRelationshipsRequest = (ids: string[]) => ({
type: RELATIONSHIPS_FETCH_REQUEST, type: RELATIONSHIPS_FETCH_REQUEST,
ids, ids,

Wyświetl plik

@ -99,10 +99,9 @@ interface TimelineStreamOpts {
const connectTimelineStream = ( const connectTimelineStream = (
timelineId: string, timelineId: string,
path: string, path: string,
pollingRefresh: ((dispatch: AppDispatch, done?: () => void) => void) | null = null,
accept: ((status: APIEntity) => boolean) | null = null, accept: ((status: APIEntity) => boolean) | null = null,
opts?: TimelineStreamOpts, opts?: TimelineStreamOpts,
) => connectStream(path, pollingRefresh, (dispatch: AppDispatch, getState: () => RootState) => { ) => connectStream(path, (dispatch: AppDispatch, getState: () => RootState) => {
const locale = getLocale(getState()); const locale = getLocale(getState());
return { return {

Wyświetl plik

@ -1,3 +1,4 @@
import { MastodonResponse } from 'soapbox/api/MastodonResponse.ts';
import { Entities } from 'soapbox/entity-store/entities.ts'; import { Entities } from 'soapbox/entity-store/entities.ts';
import { useBatchedEntities } from 'soapbox/entity-store/hooks/useBatchedEntities.ts'; import { useBatchedEntities } from 'soapbox/entity-store/hooks/useBatchedEntities.ts';
import { useApi } from 'soapbox/hooks/useApi.ts'; import { useApi } from 'soapbox/hooks/useApi.ts';
@ -8,9 +9,19 @@ function useRelationships(listKey: string[], ids: string[]) {
const api = useApi(); const api = useApi();
const { isLoggedIn } = useLoggedIn(); const { isLoggedIn } = useLoggedIn();
function fetchRelationships(ids: string[]) { async function fetchRelationships(ids: string[]) {
const q = ids.map((id) => `id[]=${id}`).join('&'); const results: Relationship[] = [];
return api.get(`/api/v1/accounts/relationships?${q}`);
for (const id of chunkArray(ids, 20)) {
const response = await api.get('/api/v1/accounts/relationships', { searchParams: { id } });
const json = await response.json();
results.push(...json);
}
return new MastodonResponse(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' },
});
} }
const { entityMap: relationships, ...result } = useBatchedEntities<Relationship>( const { entityMap: relationships, ...result } = useBatchedEntities<Relationship>(
@ -23,4 +34,12 @@ function useRelationships(listKey: string[], ids: string[]) {
return { relationships, ...result }; return { relationships, ...result };
} }
function* chunkArray<T>(array: T[], chunkSize: number): Iterable<T[]> {
if (chunkSize <= 0) throw new Error('Chunk size must be greater than zero.');
for (let i = 0; i < array.length; i += chunkSize) {
yield array.slice(i, i + chunkSize);
}
}
export { useRelationships }; export { useRelationships };

Wyświetl plik

@ -10,7 +10,6 @@ function useCommunityStream({ onlyMedia, enabled }: UseCommunityStreamOpts = {})
`community${onlyMedia ? ':media' : ''}`, `community${onlyMedia ? ':media' : ''}`,
`public:local${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`,
undefined, undefined,
undefined,
{ enabled }, { enabled },
); );
} }

Wyświetl plik

@ -9,7 +9,6 @@ function useDirectStream() {
'direct', 'direct',
'direct', 'direct',
null, null,
null,
{ enabled: isLoggedIn }, { enabled: isLoggedIn },
); );
} }

Wyświetl plik

@ -9,7 +9,6 @@ function useListStream(listId: string) {
`list:${listId}`, `list:${listId}`,
`list&list=${listId}`, `list&list=${listId}`,
null, null,
null,
{ enabled: isLoggedIn }, { enabled: isLoggedIn },
); );
} }

Wyświetl plik

@ -10,7 +10,6 @@ function usePublicStream({ onlyMedia, language }: UsePublicStreamOpts = {}) {
`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`,
`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`,
null, null,
null,
{ enabled: !language }, // TODO: support language streaming { enabled: !language }, // TODO: support language streaming
); );
} }

Wyświetl plik

@ -9,7 +9,7 @@ import { getAccessToken } from 'soapbox/utils/auth.ts';
function useTimelineStream(...args: Parameters<typeof connectTimelineStream>) { function useTimelineStream(...args: Parameters<typeof connectTimelineStream>) {
// TODO: get rid of streaming.ts and move the actual opts here. // TODO: get rid of streaming.ts and move the actual opts here.
const [timelineId, path] = args; const [timelineId, path] = args;
const { enabled = true } = args[4] ?? {}; const { enabled = true } = args[3] ?? {};
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { instance } = useInstance(); const { instance } = useInstance();

Wyświetl plik

@ -1,12 +1,8 @@
import { expandNotifications } from 'soapbox/actions/notifications.ts';
import { expandHomeTimeline } from 'soapbox/actions/timelines.ts';
import { useStatContext } from 'soapbox/contexts/stat-context.tsx'; import { useStatContext } from 'soapbox/contexts/stat-context.tsx';
import { useLoggedIn } from 'soapbox/hooks/useLoggedIn.ts'; import { useLoggedIn } from 'soapbox/hooks/useLoggedIn.ts';
import { useTimelineStream } from './useTimelineStream.ts'; import { useTimelineStream } from './useTimelineStream.ts';
import type { AppDispatch } from 'soapbox/store.ts';
function useUserStream() { function useUserStream() {
const { isLoggedIn } = useLoggedIn(); const { isLoggedIn } = useLoggedIn();
const statContext = useStatContext(); const statContext = useStatContext();
@ -14,16 +10,9 @@ function useUserStream() {
return useTimelineStream( return useTimelineStream(
'home', 'home',
'user', 'user',
refresh,
null, null,
{ statContext, enabled: isLoggedIn }, { statContext, enabled: isLoggedIn },
); );
} }
/** Refresh home timeline and notifications. */
function refresh(dispatch: AppDispatch, done?: () => void) {
return dispatch(expandHomeTimeline({}, () =>
dispatch(expandNotifications({}, done))));
}
export { useUserStream }; export { useUserStream };

Wyświetl plik

@ -293,7 +293,7 @@ const getSimplePolicy = createSelector([
}); });
const getRemoteInstanceFavicon = (state: RootState, host: string) => { const getRemoteInstanceFavicon = (state: RootState, host: string) => {
const accounts = state.entities[Entities.ACCOUNTS]?.store as EntityStore<AccountSchema>; const accounts = (state.entities[Entities.ACCOUNTS]?.store ?? {}) as EntityStore<AccountSchema>;
const account = Object.entries(accounts).find(([_, account]) => account && getDomain(account) === host)?.[1]; const account = Object.entries(accounts).find(([_, account]) => account && getDomain(account) === host)?.[1];
return account?.pleroma?.favicon; return account?.pleroma?.favicon;
}; };

Wyświetl plik

@ -4,19 +4,14 @@ import { getAccessToken } from 'soapbox/utils/auth.ts';
import type { AppDispatch, RootState } from 'soapbox/store.ts'; import type { AppDispatch, RootState } from 'soapbox/store.ts';
const randomIntUpTo = (max: number) => Math.floor(Math.random() * Math.floor(max));
interface ConnectStreamCallbacks { interface ConnectStreamCallbacks {
onConnect(): void; onConnect(): void;
onDisconnect(): void; onDisconnect(): void;
onReceive(websocket: Websocket, data: unknown): void; onReceive(websocket: Websocket, data: unknown): void;
} }
type PollingRefreshFn = (dispatch: AppDispatch, done?: () => void) => void
export function connectStream( export function connectStream(
path: string, path: string,
pollingRefresh: PollingRefreshFn | null = null,
callbacks: (dispatch: AppDispatch, getState: () => RootState) => ConnectStreamCallbacks, callbacks: (dispatch: AppDispatch, getState: () => RootState) => ConnectStreamCallbacks,
) { ) {
return (dispatch: AppDispatch, getState: () => RootState) => { return (dispatch: AppDispatch, getState: () => RootState) => {
@ -24,23 +19,6 @@ export function connectStream(
const accessToken = getAccessToken(getState()); const accessToken = getAccessToken(getState());
const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState); const { onConnect, onDisconnect, onReceive } = callbacks(dispatch, getState);
let polling: NodeJS.Timeout | null = null;
const setupPolling = () => {
if (pollingRefresh) {
pollingRefresh(dispatch, () => {
polling = setTimeout(() => setupPolling(), 20000 + randomIntUpTo(20000));
});
}
};
const clearPolling = () => {
if (polling) {
clearTimeout(polling);
polling = null;
}
};
let subscription: Websocket; let subscription: Websocket;
// If the WebSocket fails to be created, don't crash the whole page, // If the WebSocket fails to be created, don't crash the whole page,
@ -48,18 +26,10 @@ export function connectStream(
try { try {
subscription = getStream(streamingAPIBaseURL!, accessToken!, path, { subscription = getStream(streamingAPIBaseURL!, accessToken!, path, {
connected() { connected() {
if (pollingRefresh) {
clearPolling();
}
onConnect(); onConnect();
}, },
disconnected() { disconnected() {
if (pollingRefresh) {
polling = setTimeout(() => setupPolling(), randomIntUpTo(40000));
}
onDisconnect(); onDisconnect();
}, },
@ -68,11 +38,6 @@ export function connectStream(
}, },
reconnected() { reconnected() {
if (pollingRefresh) {
clearPolling();
pollingRefresh(dispatch);
}
onConnect(); onConnect();
}, },
@ -85,8 +50,6 @@ export function connectStream(
if (subscription) { if (subscription) {
subscription.close(); subscription.close();
} }
clearPolling();
}; };
return disconnect; return disconnect;