nicolium: remove admin reducer

Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
nicole mikołajczyk
2026-03-10 13:56:17 +01:00
parent d48df086d3
commit 53940f2c00
20 changed files with 179 additions and 233 deletions

View File

@ -10,52 +10,8 @@ import { filterBadges, getTagDiff } from '@/utils/badges';
import type { AppDispatch, RootState } from '@/store';
import type { PleromaConfig } from 'pl-api';
const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS' as const;
const ADMIN_CONFIG_UPDATE_REQUEST = 'ADMIN_CONFIG_UPDATE_REQUEST' as const;
const ADMIN_CONFIG_UPDATE_SUCCESS = 'ADMIN_CONFIG_UPDATE_SUCCESS' as const;
const fetchConfig = () => (dispatch: AppDispatch, getState: () => RootState) =>
getClient(getState)
.admin.config.getPleromaConfig()
.then((data) => {
dispatch<AdminActions>({
type: ADMIN_CONFIG_FETCH_SUCCESS,
configs: data.configs,
needsReboot: data.need_reboot,
});
});
const updateConfig =
(configs: PleromaConfig['configs']) => (dispatch: AppDispatch, getState: () => RootState) => {
dispatch<AdminActions>({ type: ADMIN_CONFIG_UPDATE_REQUEST, configs });
return getClient(getState)
.admin.config.updatePleromaConfig(configs)
.then((data) => {
dispatch<AdminActions>({
type: ADMIN_CONFIG_UPDATE_SUCCESS,
configs: data.configs,
needsReboot: data.need_reboot,
});
});
};
const updateFrontendConfig = (data: Record<string, any>) => (dispatch: AppDispatch) => {
const params = [
{
group: ':pleroma',
key: ':frontend_configurations',
value: [
{
tuple: [':nicolium', data],
},
],
},
];
return dispatch(updateConfig(params));
};
const deactivateUser =
(accountId: string, report_id?: string) => (dispatch: AppDispatch, getState: () => RootState) => {
const state = getState();
@ -150,26 +106,14 @@ const redactStatus = (statusId: string) => (dispatch: AppDispatch, getState: ()
});
};
type AdminActions =
| {
type: typeof ADMIN_CONFIG_FETCH_SUCCESS;
configs: PleromaConfig['configs'];
needsReboot: boolean;
}
| { type: typeof ADMIN_CONFIG_UPDATE_REQUEST; configs: PleromaConfig['configs'] }
| {
type: typeof ADMIN_CONFIG_UPDATE_SUCCESS;
configs: PleromaConfig['configs'];
needsReboot: boolean;
};
type AdminActions = {
type: typeof ADMIN_CONFIG_UPDATE_SUCCESS;
configs: PleromaConfig['configs'];
needsReboot: boolean;
};
export {
ADMIN_CONFIG_FETCH_SUCCESS,
ADMIN_CONFIG_UPDATE_REQUEST,
ADMIN_CONFIG_UPDATE_SUCCESS,
fetchConfig,
updateConfig,
updateFrontendConfig,
deactivateUser,
deleteUser,
deleteStatus,

View File

@ -1,9 +1,7 @@
import { mrfSimpleSchema, type MRFSimple } from '@/schemas/pleroma';
import ConfigDB from '@/utils/config-db';
import { fetchConfig, updateConfig } from './admin';
import type { AppDispatch, RootState } from '@/store';
import type { PleromaConfig } from 'pl-api';
const simplePolicyMerge = (
simplePolicy: Partial<MRFSimple>,
@ -32,15 +30,15 @@ const simplePolicyMerge = (
]);
};
const updateMrf =
(host: string, restrictions: Record<string, any>) =>
(dispatch: AppDispatch, getState: () => RootState) =>
dispatch(fetchConfig()).then(() => {
const configs = getState().admin.configs;
const simplePolicy = ConfigDB.toSimplePolicy(configs);
const merged = simplePolicyMerge(simplePolicy, host, restrictions);
const config = ConfigDB.fromSimplePolicy(merged);
return dispatch(updateConfig(config));
});
const getUpdatedMrf = (
configs: PleromaConfig['configs'],
host: string,
restrictions: Record<string, any>,
) => {
const simplePolicy = ConfigDB.toSimplePolicy(configs);
const merged = simplePolicyMerge(simplePolicy, host, restrictions);
const config = ConfigDB.fromSimplePolicy(merged);
return config;
};
export { updateMrf };
export { getUpdatedMrf };

View File

@ -149,7 +149,9 @@ const DropdownNavigation: React.FC = React.memo((): React.JSX.Element | null =>
const scheduledStatusCount =
useInfiniteQuery(authenticatedScheduledStatusesCountQueryOptions).data ?? 0;
const { data: draftCount = 0 } = useDraftStatusesCountQuery();
// const dashboardCount = useAppSelector((state) => state.admin.openReports.count() + state.admin.awaitingApproval.count());
// const { data: awaitingApprovalCount = 0 } = usePendingUsersCount();
// const { data: pendingReportsCount = 0 } = usePendingReportsCount();
// const dashboardCount = pendingReportsCount + awaitingApprovalCount;
const [sidebarVisible, setSidebarVisible] = useState(isSidebarOpen);
const touchStart = useRef(0);
const touchEnd = useRef<number | null>(null);

View File

@ -1,10 +1,9 @@
import React from 'react';
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { updateConfig } from '@/actions/admin';
import { RadioGroup, RadioItem } from '@/components/ui/radio';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useInstance } from '@/hooks/use-instance';
import { useUpdateAdminConfig } from '@/queries/admin/use-config';
import toast from '@/toast';
import type { Instance } from 'pl-api';
@ -45,18 +44,18 @@ const modeFromInstance = ({ registrations }: Instance): RegistrationMode => {
/** Allows changing the registration mode of the instance, eg "open", "closed", "approval" */
const RegistrationModePicker: React.FC = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const instance = useInstance();
const { mutate: updateConfig } = useUpdateAdminConfig();
const mode = modeFromInstance(instance);
const onChange: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const config = generateConfig(e.target.value as RegistrationMode);
dispatch(updateConfig(config))
.then(() => {
updateConfig(config, {
onSuccess: () => {
toast.success(intl.formatMessage(messages.saved));
})
.catch(() => {});
},
});
};
return (

View File

@ -2,19 +2,16 @@ import clsx from 'clsx';
import React, { useState } from 'react';
import Icon from '@/components/icon';
import { useAppSelector } from '@/hooks/use-app-selector';
import { makeGetRemoteInstance } from '@/selectors';
import { useRemoteInstance } from '@/selectors';
import InstanceRestrictions from './instance-restrictions';
const getRemoteInstance = makeGetRemoteInstance();
interface IRestrictedInstance {
host: string;
}
const RestrictedInstance: React.FC<IRestrictedInstance> = ({ host }) => {
const remoteInstance = useAppSelector((state) => getRemoteInstance(state, host));
const remoteInstance = useRemoteInstance(host);
const [expanded, setExpanded] = useState(false);

View File

@ -4,12 +4,9 @@ import { useIntl, defineMessages } from 'react-intl';
import { pinHost, unpinHost } from '@/actions/remote-timeline';
import Widget from '@/components/ui/widget';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useAppSelector } from '@/hooks/use-app-selector';
import { makeGetRemoteInstance } from '@/selectors';
import { useRemoteInstance } from '@/selectors';
import { useSettings } from '@/stores/settings';
const getRemoteInstance = makeGetRemoteInstance();
const messages = defineMessages({
pinHost: { id: 'remote_instance.pin_host', defaultMessage: 'Pin {host}' },
unpinHost: { id: 'remote_instance.unpin_host', defaultMessage: 'Unpin {host}' },
@ -26,7 +23,7 @@ const InstanceInfoPanel: React.FC<IInstanceInfoPanel> = ({ host }) => {
const dispatch = useAppDispatch();
const settings = useSettings();
const remoteInstance = useAppSelector((state) => getRemoteInstance(state, host));
const remoteInstance = useRemoteInstance(host);
const pinned = settings.remote_timeline.pinnedHosts.includes(host);
const handlePinHost = () => {

View File

@ -4,13 +4,10 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import DropdownMenu from '@/components/dropdown-menu';
import Widget from '@/components/ui/widget';
import InstanceRestrictions from '@/features/federation-restrictions/components/instance-restrictions';
import { useAppSelector } from '@/hooks/use-app-selector';
import { useOwnAccount } from '@/hooks/use-own-account';
import { makeGetRemoteInstance } from '@/selectors';
import { useRemoteInstance } from '@/selectors';
import { useModalsActions } from '@/stores/modals';
const getRemoteInstance = makeGetRemoteInstance();
const messages = defineMessages({
editFederation: { id: 'remote_instance.edit_federation', defaultMessage: 'Edit federation' },
});
@ -26,7 +23,7 @@ const InstanceModerationPanel: React.FC<IInstanceModerationPanel> = ({ host }) =
const { openModal } = useModalsActions();
const { data: account } = useOwnAccount();
const remoteInstance = useAppSelector((state) => getRemoteInstance(state, host));
const remoteInstance = useRemoteInstance(host);
const handleEditFederation = () => {
openModal('EDIT_FEDERATION', { host });

View File

@ -3,7 +3,6 @@ import clsx from 'clsx';
import React, { Suspense, useEffect, useRef } from 'react';
import { Toaster } from 'react-hot-toast';
import { fetchConfig } from '@/actions/admin';
import { register as registerPushNotifications } from '@/actions/push-notifications/registerer';
import { useUserStream } from '@/api/hooks/streaming/use-user-stream';
import SidebarNavigation from '@/components/navigation/sidebar-navigation';
@ -17,6 +16,7 @@ import { useFeatures } from '@/hooks/use-features';
import { useInstance } from '@/hooks/use-instance';
import { useOwnAccount } from '@/hooks/use-own-account';
import { prefetchFollowRequests } from '@/queries/accounts/use-follow-requests';
import { useAdminConfig } from '@/queries/admin/use-config';
import { queryClient } from '@/queries/client';
import { prefetchCustomEmojis } from '@/queries/instance/use-custom-emojis';
import { usePrefetchNotificationsMarker } from '@/queries/markers/use-markers';
@ -26,10 +26,11 @@ import { scheduledStatusesQueryOptions } from '@/queries/statuses/scheduled-stat
import { useShoutboxSubscription } from '@/stores/shoutbox';
import { useIsDropdownMenuOpen } from '@/stores/ui';
import { getVapidKey } from '@/utils/auth';
import { isStandalone } from '@/utils/state';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
// Without this it ends up in ~8 very commonly used bundles.
import '@/components/statuses/status';
import { isStandalone } from '@/utils/state';
import {
ModalRoot,
AccountHoverCard,
@ -53,6 +54,7 @@ const UI: React.FC = React.memo(() => {
const isDropdownMenuOpen = useIsDropdownMenuOpen();
const standalone = useAppSelector(isStandalone);
useAdminConfig();
useShoutboxSubscription();
useFilters();
usePrefetchNotifications();
@ -87,10 +89,6 @@ const UI: React.FC = React.memo(() => {
prefetchCustomEmojis(client);
if (account.is_admin && features.pleromaAdminAccounts) {
dispatch(fetchConfig());
}
if (account.locked) {
requestIdleCallback(() => prefetchFollowRequests(client), { timeout: 2000 });
}

View File

@ -1,13 +1,12 @@
import React, { useState, useEffect, useCallback } from 'react';
import React, { useState, useEffect } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { updateMrf } from '@/actions/mrf';
import { getUpdatedMrf } from '@/actions/mrf';
import List, { ListItem } from '@/components/list';
import Modal from '@/components/ui/modal';
import Toggle from '@/components/ui/toggle';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useAppSelector } from '@/hooks/use-app-selector';
import { makeGetRemoteInstance } from '@/selectors';
import { useAdminConfig, useUpdateAdminConfig } from '@/queries/admin/use-config';
import { useRemoteInstance } from '@/selectors';
import toast from '@/toast';
import type { BaseModalProps } from '@/features/ui/components/modal-root';
@ -37,10 +36,10 @@ const EditFederationModal: React.FC<BaseModalProps & EditFederationModalProps> =
onClose,
}) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const getRemoteInstance = useCallback(makeGetRemoteInstance(), []);
const remoteInstance = useAppSelector((state) => getRemoteInstance(state, host));
const { data: config } = useAdminConfig();
const remoteInstance = useRemoteInstance(host);
const { mutate: updateConfig } = useUpdateAdminConfig();
const [data, setData] = useState<Record<string, any>>({});
@ -68,11 +67,11 @@ const EditFederationModal: React.FC<BaseModalProps & EditFederationModalProps> =
};
const handleSubmit = () => {
dispatch(updateMrf(host, data))
.then(() => {
updateConfig(getUpdatedMrf(config!.configs, host, data), {
onSuccess: () => {
toast.success(intl.formatMessage(messages.success, { host }));
})
.catch(() => {});
},
});
onClose('EDIT_FEDERATION');
};

View File

@ -3,7 +3,6 @@ import React, { useState, useEffect, useMemo } from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import * as v from 'valibot';
import { updateFrontendConfig } from '@/actions/admin';
import { uploadMedia } from '@/actions/media';
import List, { ListItem } from '@/components/list';
import Accordion from '@/components/ui/accordion';
@ -27,6 +26,7 @@ import ThemeSelector from '@/features/ui/components/theme-selector';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useAppSelector } from '@/hooks/use-app-selector';
import { useFeatures } from '@/hooks/use-features';
import { getUpdateFrontendConfigParams, useUpdateAdminConfig } from '@/queries/admin/use-config';
import {
cryptoAddressSchema,
footerItemSchema,
@ -69,8 +69,9 @@ const FrontendConfigEditor: React.FC = () => {
const features = useFeatures();
const initialData = useAppSelector((state) => state.frontendConfig);
console.log(initialData);
const { mutate: updateConfig, isPending } = useUpdateAdminConfig();
const [isLoading, setLoading] = useState(false);
const [data, setData] = useState(v.parse(frontendConfigSchema, initialData));
const [jsonEditorExpanded, setJsonEditorExpanded] = useState(false);
const [rawJSON, setRawJSON] = useState<string>(JSON.stringify(initialData, null, 2));
@ -89,15 +90,11 @@ const FrontendConfigEditor: React.FC = () => {
};
const handleSubmit: React.SubmitEventHandler<HTMLFormElement> = (e) => {
dispatch(updateFrontendConfig(data))
.then(() => {
setLoading(false);
updateConfig(getUpdateFrontendConfigParams(data), {
onSuccess: () => {
toast.success(intl.formatMessage(messages.saved));
})
.catch(() => {
setLoading(false);
});
setLoading(true);
},
});
e.preventDefault();
};
@ -199,7 +196,7 @@ const FrontendConfigEditor: React.FC = () => {
return (
<Column label={intl.formatMessage(messages.heading)}>
<Form onSubmit={handleSubmit}>
<fieldset className='space-y-6' disabled={isLoading}>
<fieldset className='space-y-6' disabled={isPending}>
<SitePreview frontendConfig={frontendConfig} />
<CardHeader>

View File

@ -2,7 +2,6 @@ import React, { useRef, useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import * as v from 'valibot';
import { updateFrontendConfig } from '@/actions/admin';
import { fetchFrontendConfig } from '@/actions/frontend-config';
import { getHost } from '@/actions/instance';
import DropdownMenu from '@/components/dropdown-menu';
@ -17,6 +16,7 @@ import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useAppSelector } from '@/hooks/use-app-selector';
import { useFrontendConfig } from '@/hooks/use-frontend-config';
import { normalizeColors } from '@/hooks/use-theme-css';
import { getUpdateFrontendConfigParams, useUpdateAdminConfig } from '@/queries/admin/use-config';
import { frontendConfigSchema } from '@/schemas/frontend-config';
import toast from '@/toast';
import { download } from '@/utils/download';
@ -56,6 +56,7 @@ const ThemeEditorPage: React.FC = () => {
const frontendConfig = useFrontendConfig();
const host = useAppSelector((state) => getHost(state));
const rawConfig = useAppSelector((state) => state.frontendConfig);
const { mutate: updateConfig } = useUpdateAdminConfig();
const [colors, setColors] = useState(normalizeColors(frontendConfig));
const [isDefault, setIsDefault] = useState(false);
@ -97,14 +98,14 @@ const ThemeEditorPage: React.FC = () => {
setTheme(normalizeColors(frontendConfig));
};
const updateTheme = async () => {
const updateTheme = () => {
let params;
if (isDefault) {
params = { ...rawConfig, colors: undefined, brandColor: undefined, accentColor: undefined };
} else {
params = { ...rawConfig, colors };
}
await dispatch(updateFrontendConfig(params));
return updateConfig(getUpdateFrontendConfigParams(params));
};
const restoreDefaultTheme = () => {

View File

@ -1,4 +1,4 @@
import React, { useState, useCallback } from 'react';
import React, { useState } from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import ScrollableList from '@/components/scrollable-list';
@ -7,7 +7,7 @@ import Column from '@/components/ui/column';
import RestrictedInstance from '@/features/federation-restrictions/components/restricted-instance';
import { useAppSelector } from '@/hooks/use-app-selector';
import { useInstance } from '@/hooks/use-instance';
import { makeGetHosts } from '@/selectors';
import { useHosts } from '@/selectors';
import { federationRestrictionsDisclosed } from '@/utils/state';
const messages = defineMessages({
@ -27,9 +27,7 @@ const FederationRestrictionsPage = () => {
const intl = useIntl();
const instance = useInstance();
const getHosts = useCallback(makeGetHosts(), []);
const hosts = useAppSelector((state) => getHosts(state));
const hosts = useHosts();
const disclosed = useAppSelector((state) => federationRestrictionsDisclosed(state));
const [explanationBoxExpanded, setExplanationBoxExpanded] = useState(true);

View File

@ -0,0 +1,57 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { ADMIN_CONFIG_UPDATE_SUCCESS, type AdminActions } from '@/actions/admin';
import { useAppDispatch } from '@/hooks/use-app-dispatch';
import { useClient } from '@/hooks/use-client';
import { useFeatures } from '@/hooks/use-features';
import { useOwnAccount } from '@/hooks/use-own-account';
import { queryKeys } from '../keys';
const useAdminConfig = () => {
const client = useClient();
const features = useFeatures();
const { data: ownAccount } = useOwnAccount();
return useQuery({
queryKey: queryKeys.admin.config,
queryFn: () => client.admin.config.getPleromaConfig(),
enabled: ownAccount?.is_admin && features.pleromaAdminAccounts,
});
};
const useUpdateAdminConfig = () => {
const client = useClient();
const dispatch = useAppDispatch();
const queryClient = useQueryClient();
return useMutation({
mutationFn: (params: Parameters<typeof client.admin.config.updatePleromaConfig>[0]) =>
client.admin.config.updatePleromaConfig(params),
retry: false,
onSuccess: (data) => {
dispatch<AdminActions>({
type: ADMIN_CONFIG_UPDATE_SUCCESS,
configs: data.configs,
needsReboot: data.need_reboot,
});
queryClient.setQueryData(queryKeys.admin.config, data);
},
});
};
const getUpdateFrontendConfigParams = (data: any) => {
return [
{
group: ':pleroma',
key: ':frontend_configurations',
value: [
{
tuple: [':nicolium', data],
},
],
},
];
};
export { useAdminConfig, useUpdateAdminConfig, getUpdateFrontendConfigParams };

View File

@ -53,6 +53,7 @@ import type {
OauthToken,
PaginatedResponse,
PlApiClient,
PleromaConfig,
Poll,
Relationship,
RssFeed,
@ -304,6 +305,7 @@ const groupRelationships = {
const admin = {
root: ['admin'] as const,
config: ['admin', 'config'] as TaggedKey<['admin', 'config'], PleromaConfig>,
accounts: {
root: ['admin', 'accounts'] as const,
show: (accountId: string) => {

View File

@ -20,9 +20,9 @@ import { useLoggedIn } from '@/hooks/use-logged-in';
import { appendFollowRequest } from '@/queries/accounts/use-follow-requests';
import { queryClient } from '@/queries/client';
import { makePaginatedResponseQueryOptions } from '@/queries/utils/make-paginated-response-query-options';
import { regexFromFilters } from '@/selectors';
import { useSettingsStore } from '@/stores/settings';
import { compareId } from '@/utils/comparators';
import { regexFromFilters } from '@/utils/filters';
import { unescapeHTML } from '@/utils/html';
import { EXCLUDE_TYPES, NOTIFICATION_TYPES } from '@/utils/notification';
import { play, soundCache } from '@/utils/sounds';

View File

@ -1,38 +0,0 @@
import { create } from 'mutative';
import {
ADMIN_CONFIG_FETCH_SUCCESS,
ADMIN_CONFIG_UPDATE_SUCCESS,
// ADMIN_USER_DELETE_SUCCESS,
type AdminActions,
} from '@/actions/admin';
import type { Config } from '@/utils/config-db';
interface State {
configs: Array<Config>;
needsReboot: boolean;
}
const initialState: State = {
configs: [],
needsReboot: false,
};
const importConfigs = (state: State, configs: Array<Config>) => {
state.configs = configs;
};
const admin = (state = initialState, action: AdminActions): State => {
switch (action.type) {
case ADMIN_CONFIG_FETCH_SUCCESS:
case ADMIN_CONFIG_UPDATE_SUCCESS:
return create(state, (draft) => {
importConfigs(draft, action.configs);
});
default:
return state;
}
};
export { admin as default };

View File

@ -23,7 +23,7 @@ const fallbackState: PartialFrontendConfig = {
const updateFromAdmin = (state: Record<string, any>, configs: PleromaConfig['configs']) => {
try {
return ConfigDB.find(configs, ':pleroma', ':frontend_configurations')!.value.find(
(value: Record<string, any>) => value.tuple?.[0] === ':pl_fe',
(value: Record<string, any>) => value.tuple?.[0] === ':nicolium',
).tuple?.[1];
} catch {
return state;
@ -77,7 +77,7 @@ const frontendConfig = (
case FRONTEND_CONFIG_REQUEST_FAIL:
return { ...fallbackState, ...state };
case ADMIN_CONFIG_UPDATE_SUCCESS:
return parseFrontendConfig(updateFromAdmin(state, action.configs ?? [])) || state;
return parseFrontendConfig(updateFromAdmin(state, action.configs)) || state;
default:
return state;
}

View File

@ -3,7 +3,6 @@ import { combineReducers } from '@reduxjs/toolkit';
import { AUTH_LOGGED_OUT } from '@/actions/auth';
import * as BuildConfig from '@/build-config';
import admin from './admin';
import auth from './auth';
import frontendConfig from './frontend-config';
import instance from './instance';
@ -12,7 +11,6 @@ import meta from './meta';
import pushNotifications from './push-notifications';
const reducers = {
admin,
auth,
frontendConfig,
instance,

View File

@ -2,11 +2,7 @@ import { create } from 'mutative';
import { type Instance, instanceSchema, type PleromaConfig } from 'pl-api';
import * as v from 'valibot';
import {
ADMIN_CONFIG_UPDATE_REQUEST,
ADMIN_CONFIG_UPDATE_SUCCESS,
type AdminActions,
} from '@/actions/admin';
import { ADMIN_CONFIG_UPDATE_SUCCESS, type AdminActions } from '@/actions/admin';
import {
INSTANCE_FETCH_FAIL,
INSTANCE_FETCH_SUCCESS,
@ -105,7 +101,6 @@ const instance = (
return { fetched: true, ...action.instance };
case INSTANCE_FETCH_FAIL:
return handleInstanceFetchFail(state, action.error);
case ADMIN_CONFIG_UPDATE_REQUEST:
case ADMIN_CONFIG_UPDATE_SUCCESS:
return create(state, (draft) => importConfigs(draft, action.configs));
default:

View File

@ -1,35 +1,36 @@
import { createSelector } from 'reselect';
import { useQueryClient } from '@tanstack/react-query';
import { getAccounts } from '@/queries/accounts/selectors';
import { useInstance } from '@/hooks/use-instance';
import { useAdminConfig } from '@/queries/admin/use-config';
import { queryKeys } from '@/queries/keys';
import { getDomain } from '@/utils/accounts';
import ConfigDB from '@/utils/config-db';
import { regexFromFilters } from '@/utils/filters';
import type { MRFSimple } from '@/schemas/pleroma';
import type { RootState } from '@/store';
const getSimplePolicy = createSelector(
[
(state: RootState) => state.admin.configs,
(state: RootState) => state.instance.pleroma.metadata.federation.mrf_simple_info,
],
(configs, instancePolicy): MRFSimple => ({
...instancePolicy,
...ConfigDB.toSimplePolicy(configs),
}),
);
const getRemoteInstanceFavicon = (_state: RootState, host: string) => {
const account = getAccounts().find((item) => item && getDomain(item) === host);
return account?.favicon ?? null;
};
import type { Account } from 'pl-api';
type HostFederation = {
[key in keyof MRFSimple]: boolean;
};
const getRemoteInstanceFederation = (state: RootState, host: string): HostFederation => {
const simplePolicy = getSimplePolicy(state);
interface RemoteInstance {
host: string;
favicon: string | null;
federation: HostFederation;
}
const useSimplePolicy = () => {
const { data: config } = useAdminConfig();
const simplePolicy = useInstance().pleroma.metadata.federation.mrf_simple_info;
return {
...simplePolicy,
...ConfigDB.toSimplePolicy(config?.configs || []),
};
};
const useRemoteInstanceFederation = (host: string) => {
const simplePolicy = useSimplePolicy();
return Object.fromEntries(
Object.entries(simplePolicy).map(([key, hosts]) => [
@ -39,33 +40,37 @@ const getRemoteInstanceFederation = (state: RootState, host: string): HostFedera
) as HostFederation;
};
const makeGetHosts = () =>
createSelector([getSimplePolicy], (simplePolicy) => {
const { accept, reject_deletes, report_removal, ...rest } = simplePolicy;
const useRemoteInstanceFavicon = (host: string) => {
const queryClient = useQueryClient();
return [
...new Set(Object.values(rest).reduce((acc, hosts) => (acc.push(...hosts), acc), [])),
].toSorted();
});
interface RemoteInstance {
host: string;
favicon: string | null;
federation: HostFederation;
}
const makeGetRemoteInstance = () =>
createSelector(
[
(_state: RootState, host: string) => host,
getRemoteInstanceFavicon,
getRemoteInstanceFederation,
],
(host, favicon, federation): RemoteInstance => ({
host,
favicon,
federation,
}),
return (
queryClient
.getQueriesData<Account>({ queryKey: queryKeys.accounts.root })
.map(([, account]) => account)
.find(
(account): account is Account =>
typeof account?.id === 'string' && getDomain(account) === host,
)?.favicon ?? null
);
};
export { type RemoteInstance, regexFromFilters, makeGetHosts, makeGetRemoteInstance };
const useRemoteInstance = (host: string) => {
const federation = useRemoteInstanceFederation(host);
const favicon = useRemoteInstanceFavicon(host);
return {
host,
favicon,
federation,
};
};
const useHosts = () => {
const simplePolicy = useSimplePolicy();
return [
...new Set(Object.values(simplePolicy).reduce((acc, hosts) => (acc.push(...hosts), acc), [])),
].toSorted();
};
export { type RemoteInstance, useHosts, useRemoteInstance };