mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-13 22:41:32 -05:00
Updated AdminX to sync data changes to Ember (#18327)
refs https://github.com/TryGhost/Product/issues/3832 --- ### <samp>🤖 Generated by Copilot at 7a91ba3</samp> This pull request enables data synchronization between the Ember app and the React app for the settings module. It passes `onUpdate` and `onInvalidate` functions as props from the Ember app to the React app through the `ReactApp` component and the `ServicesContext`. It also removes unused code and adds some debugging logs in the `setting` serializer and the `settings` service.
This commit is contained in:
parent
3f04c93e21
commit
328a785065
18 changed files with 137 additions and 34 deletions
|
@ -17,10 +17,12 @@ interface AppProps {
|
|||
officialThemes: OfficialTheme[];
|
||||
zapierTemplates: ZapierTemplate[];
|
||||
externalNavigate: (link: ExternalLink) => void;
|
||||
toggleFeatureFlag: (flag: string, enabled: boolean) => void;
|
||||
darkMode?: boolean;
|
||||
unsplashConfig: DefaultHeaderTypes
|
||||
sentryDSN: string | null;
|
||||
onUpdate: (dataType: string, response: unknown) => void;
|
||||
onInvalidate: (dataType: string) => void;
|
||||
onDelete: (dataType: string, id: string) => void;
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
|
@ -41,7 +43,7 @@ function SentryErrorBoundary({children}: {children: React.ReactNode}) {
|
|||
);
|
||||
}
|
||||
|
||||
function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, toggleFeatureFlag, darkMode = false, unsplashConfig, sentryDSN}: AppProps) {
|
||||
function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, darkMode = false, unsplashConfig, sentryDSN, onUpdate, onInvalidate, onDelete}: AppProps) {
|
||||
const appClassName = clsx(
|
||||
'admin-x-settings h-[100vh] w-full overflow-y-auto overflow-x-hidden',
|
||||
darkMode && 'dark'
|
||||
|
@ -50,7 +52,7 @@ function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate, t
|
|||
return (
|
||||
<SentryErrorBoundary>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ServicesProvider ghostVersion={ghostVersion} officialThemes={officialThemes} sentryDSN={sentryDSN} toggleFeatureFlag={toggleFeatureFlag} unsplashConfig={unsplashConfig} zapierTemplates={zapierTemplates}>
|
||||
<ServicesProvider ghostVersion={ghostVersion} officialThemes={officialThemes} sentryDSN={sentryDSN} unsplashConfig={unsplashConfig} zapierTemplates={zapierTemplates} onDelete={onDelete} onInvalidate={onInvalidate} onUpdate={onUpdate}>
|
||||
<GlobalDataProvider>
|
||||
<RoutingProvider externalNavigate={externalNavigate}>
|
||||
<GlobalDirtyStateProvider>
|
||||
|
|
|
@ -23,6 +23,7 @@ export const useRefreshAPIKey = createMutation<IntegrationsResponseType, {integr
|
|||
path: ({integrationId, apiKeyId}) => `/integrations/${integrationId}/api_key/${apiKeyId}/refresh/`,
|
||||
body: ({integrationId}) => ({integrations: [{id: integrationId}]}),
|
||||
updateQueries: {
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
dataType: integrationsDataType,
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as IntegrationsResponseType),
|
||||
|
|
|
@ -40,6 +40,7 @@ export const useEditCustomThemeSettings = createMutation<CustomThemeSettingsResp
|
|||
body: settings => ({custom_theme_settings: settings}),
|
||||
|
||||
updateQueries: {
|
||||
emberUpdateType: 'skip',
|
||||
dataType,
|
||||
update: newData => newData
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export const verifyEmailToken = createMutation<EmailVerificationResponseType, em
|
|||
body: ({token}) => ({token}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: newData => ({
|
||||
...newData,
|
||||
settings: newData.settings
|
||||
|
|
|
@ -41,6 +41,7 @@ export const useCreateIntegration = createMutation<IntegrationsResponseType, Par
|
|||
searchParams: () => ({include: 'api_keys,webhooks'}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as IntegrationsResponseType),
|
||||
integrations: (currentData as IntegrationsResponseType).integrations.concat(newData.integrations)
|
||||
|
@ -55,6 +56,7 @@ export const useEditIntegration = createMutation<IntegrationsResponseType, Integ
|
|||
searchParams: () => ({include: 'api_keys,webhooks'}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as IntegrationsResponseType),
|
||||
integrations: (currentData as IntegrationsResponseType).integrations.map((integration) => {
|
||||
|
@ -70,6 +72,7 @@ export const useDeleteIntegration = createMutation<unknown, string>({
|
|||
path: id => `/integrations/${id}/`,
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'delete',
|
||||
update: (_, currentData, id) => ({
|
||||
...(currentData as IntegrationsResponseType),
|
||||
integrations: (currentData as IntegrationsResponseType).integrations.filter(user => user.id !== id)
|
||||
|
|
|
@ -37,6 +37,7 @@ export const useAddInvite = createMutation<InvitesResponseType, {email: string,
|
|||
}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
// Assume that all invite queries should include this new one
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as InvitesResponseType),
|
||||
|
@ -53,6 +54,7 @@ export const useDeleteInvite = createMutation<unknown, string>({
|
|||
method: 'DELETE',
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'delete',
|
||||
update: (_, currentData, id) => ({
|
||||
...(currentData as InvitesResponseType),
|
||||
invites: (currentData as InvitesResponseType).invites.filter(invite => invite.id !== id)
|
||||
|
|
|
@ -77,6 +77,7 @@ export const useAddNewsletter = createMutation<NewslettersResponseType, Partial<
|
|||
searchParams: payload => ({opt_in_existing: payload.opt_in_existing.toString(), include: 'count.active_members,count.posts'}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: insertToQueryCache('newsletters')
|
||||
}
|
||||
});
|
||||
|
@ -92,6 +93,7 @@ export const useEditNewsletter = createMutation<NewslettersEditResponseType, New
|
|||
defaultSearchParams: {include: 'count.active_members,count.posts'},
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: updateQueryCache('newsletters')
|
||||
}
|
||||
});
|
||||
|
|
|
@ -35,6 +35,7 @@ export const useEditSettings = createMutation<SettingsResponseType, Setting[]>({
|
|||
body: settings => ({settings: settings.map(({key, value}) => ({key, value}))}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: newData => ({
|
||||
...newData,
|
||||
settings: newData.settings
|
||||
|
|
|
@ -54,6 +54,7 @@ export const useActivateTheme = createMutation<ThemesResponseType, string>({
|
|||
path: name => `/themes/${name}/activate/`,
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: (newData: ThemesResponseType, currentData: unknown) => ({
|
||||
...(currentData as ThemesResponseType),
|
||||
themes: (currentData as ThemesResponseType).themes.map((theme) => {
|
||||
|
@ -77,6 +78,7 @@ export const useDeleteTheme = createMutation<unknown, string>({
|
|||
path: name => `/themes/${name}/`,
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'delete',
|
||||
update: (_, currentData, name) => ({
|
||||
...(currentData as ThemesResponseType),
|
||||
themes: (currentData as ThemesResponseType).themes.filter(theme => theme.name !== name)
|
||||
|
@ -90,6 +92,7 @@ export const useInstallTheme = createMutation<ThemesInstallResponseType, string>
|
|||
searchParams: repo => ({source: 'github', ref: repo}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
// Assume that all invite queries should include this new one
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as ThemesResponseType),
|
||||
|
@ -111,6 +114,7 @@ export const useUploadTheme = createMutation<ThemesInstallResponseType, {file: F
|
|||
},
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
// Assume that all invite queries should include this new one
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as ThemesResponseType),
|
||||
|
|
|
@ -65,6 +65,7 @@ export const useEditTier = createMutation<TiersResponseType, Tier>({
|
|||
body: tier => ({tiers: [tier]}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: updateQueryCache('tiers')
|
||||
}
|
||||
});
|
||||
|
|
|
@ -99,6 +99,7 @@ export const useEditUser = createMutation<UsersResponseType, User>({
|
|||
searchParams: () => ({include: 'roles'}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: updateQueryCache('users')
|
||||
}
|
||||
});
|
||||
|
@ -108,6 +109,7 @@ export const useDeleteUser = createMutation<DeleteUserResponse, string>({
|
|||
path: id => `/users/${id}/`,
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'delete',
|
||||
update: deleteFromQueryCache('users')
|
||||
}
|
||||
});
|
||||
|
@ -135,6 +137,7 @@ export const useMakeOwner = createMutation<UsersResponseType, string>({
|
|||
}),
|
||||
updateQueries: {
|
||||
dataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: updateQueryCache('users')
|
||||
}
|
||||
});
|
||||
|
|
|
@ -31,6 +31,7 @@ export const useCreateWebhook = createMutation<WebhooksResponseType, Partial<Web
|
|||
body: webhook => ({webhooks: [webhook]}),
|
||||
updateQueries: {
|
||||
dataType: integrationsDataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as IntegrationsResponseType),
|
||||
integrations: (currentData as IntegrationsResponseType).integrations.map((integration) => {
|
||||
|
@ -52,6 +53,7 @@ export const useEditWebhook = createMutation<WebhooksResponseType, Webhook>({
|
|||
body: webhook => ({webhooks: [webhook]}),
|
||||
updateQueries: {
|
||||
dataType: integrationsDataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: (newData, currentData) => (currentData && {
|
||||
...(currentData as IntegrationsResponseType),
|
||||
integrations: (currentData as IntegrationsResponseType).integrations.map(integration => ({
|
||||
|
@ -69,6 +71,7 @@ export const useDeleteWebhook = createMutation<unknown, string>({
|
|||
path: id => `/webhooks/${id}/`,
|
||||
updateQueries: {
|
||||
dataType: integrationsDataType,
|
||||
emberUpdateType: 'createOrUpdate',
|
||||
update: (_, currentData, id) => ({
|
||||
...(currentData as IntegrationsResponseType),
|
||||
integrations: (currentData as IntegrationsResponseType).integrations.map(integration => ({
|
||||
|
|
|
@ -18,8 +18,10 @@ interface ServicesContextProps {
|
|||
zapierTemplates: ZapierTemplate[];
|
||||
search: SearchService;
|
||||
unsplashConfig: DefaultHeaderTypes;
|
||||
toggleFeatureFlag: (flag: string, enabled: boolean) => void;
|
||||
sentryDSN: string | null;
|
||||
onUpdate: (dataType: string, response: unknown) => void;
|
||||
onInvalidate: (dataType: string) => void;
|
||||
onDelete: (dataType: string, id: string) => void;
|
||||
}
|
||||
|
||||
interface ServicesProviderProps {
|
||||
|
@ -27,9 +29,11 @@ interface ServicesProviderProps {
|
|||
ghostVersion: string;
|
||||
zapierTemplates: ZapierTemplate[];
|
||||
officialThemes: OfficialTheme[];
|
||||
toggleFeatureFlag: (flag: string, enabled: boolean) => void;
|
||||
unsplashConfig: DefaultHeaderTypes;
|
||||
sentryDSN: string | null;
|
||||
onUpdate: (dataType: string, response: unknown) => void;
|
||||
onInvalidate: (dataType: string) => void;
|
||||
onDelete: (dataType: string, id: string) => void;
|
||||
}
|
||||
|
||||
const ServicesContext = createContext<ServicesContextProps>({
|
||||
|
@ -37,7 +41,6 @@ const ServicesContext = createContext<ServicesContextProps>({
|
|||
officialThemes: [],
|
||||
zapierTemplates: [],
|
||||
search: {filter: '', setFilter: () => {}, checkVisible: () => true},
|
||||
toggleFeatureFlag: () => {},
|
||||
unsplashConfig: {
|
||||
Authorization: '',
|
||||
'Accept-Version': '',
|
||||
|
@ -45,10 +48,13 @@ const ServicesContext = createContext<ServicesContextProps>({
|
|||
'App-Pragma': '',
|
||||
'X-Unsplash-Cache': true
|
||||
},
|
||||
sentryDSN: null
|
||||
sentryDSN: null,
|
||||
onUpdate: () => {},
|
||||
onInvalidate: () => {},
|
||||
onDelete: () => {}
|
||||
});
|
||||
|
||||
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, zapierTemplates, officialThemes, toggleFeatureFlag, unsplashConfig, sentryDSN}) => {
|
||||
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, zapierTemplates, officialThemes, unsplashConfig, sentryDSN, onUpdate, onInvalidate, onDelete}) => {
|
||||
const search = useSearchService();
|
||||
|
||||
return (
|
||||
|
@ -58,8 +64,10 @@ const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersi
|
|||
zapierTemplates,
|
||||
search,
|
||||
unsplashConfig,
|
||||
toggleFeatureFlag,
|
||||
sentryDSN
|
||||
sentryDSN,
|
||||
onUpdate,
|
||||
onInvalidate,
|
||||
onDelete
|
||||
}}>
|
||||
{children}
|
||||
</ServicesContext.Provider>
|
||||
|
|
|
@ -5,14 +5,12 @@ import {ConfigResponseType, configDataType} from '../../../../api/config';
|
|||
import {getSettingValue, useEditSettings} from '../../../../api/settings';
|
||||
import {useGlobalData} from '../../../providers/GlobalDataProvider';
|
||||
import {useQueryClient} from '@tanstack/react-query';
|
||||
import {useServices} from '../../../providers/ServiceProvider';
|
||||
|
||||
const FeatureToggle: React.FC<{ flag: string; }> = ({flag}) => {
|
||||
const {settings} = useGlobalData();
|
||||
const labs = JSON.parse(getSettingValue<string>(settings, 'labs') || '{}');
|
||||
const {mutateAsync: editSettings} = useEditSettings();
|
||||
const client = useQueryClient();
|
||||
const {toggleFeatureFlag} = useServices();
|
||||
|
||||
return <Toggle checked={labs[flag]} onChange={async () => {
|
||||
const newValue = !labs[flag];
|
||||
|
@ -21,7 +19,6 @@ const FeatureToggle: React.FC<{ flag: string; }> = ({flag}) => {
|
|||
key: 'labs',
|
||||
value: JSON.stringify({...labs, [flag]: newValue})
|
||||
}]);
|
||||
toggleFeatureFlag(flag, newValue);
|
||||
client.setQueriesData([configDataType], current => ({
|
||||
config: {
|
||||
...(current as ConfigResponseType).config,
|
||||
|
|
|
@ -31,9 +31,11 @@ ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
|||
image: 'assets/img/themes/Edition.png'
|
||||
}]}
|
||||
sentryDSN={'' as string | null}
|
||||
toggleFeatureFlag={() => {}}
|
||||
unsplashConfig={{} as DefaultHeaderTypes}
|
||||
zapierTemplates={[]}
|
||||
onDelete={() => {}}
|
||||
onInvalidate={() => {}}
|
||||
onUpdate={() => {}}
|
||||
/>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
|
|
@ -2,9 +2,9 @@ import * as Sentry from '@sentry/react';
|
|||
import handleError from './handleError';
|
||||
import handleResponse from './handleResponse';
|
||||
import {APIError, MaintenanceError, ServerUnreachableError, TimeoutError} from '../errors';
|
||||
import {QueryClient, UseInfiniteQueryOptions, UseQueryOptions, useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
|
||||
import {UseInfiniteQueryOptions, UseQueryOptions, useInfiniteQuery, useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
|
||||
import {getGhostPaths} from '../helpers';
|
||||
import {useEffect, useMemo} from 'react';
|
||||
import {useCallback, useEffect, useMemo} from 'react';
|
||||
import {usePage, usePagination} from '../../hooks/usePagination';
|
||||
import {useSentryDSN, useServices} from '../../components/providers/ServiceProvider';
|
||||
|
||||
|
@ -263,7 +263,7 @@ interface MutationOptions<ResponseData, Payload> extends Omit<QueryOptions<Respo
|
|||
body?: (payload: Payload) => FormData | object;
|
||||
searchParams?: (payload: Payload) => { [key: string]: string; };
|
||||
invalidateQueries?: { dataType: string; };
|
||||
updateQueries?: { dataType: string; update: (newData: ResponseData, currentData: unknown, payload: Payload) => unknown };
|
||||
updateQueries?: { dataType: string; emberUpdateType: 'createOrUpdate' | 'delete' | 'skip'; update: (newData: ResponseData, currentData: unknown, payload: Payload) => unknown };
|
||||
}
|
||||
|
||||
const mutate = <ResponseData, Payload>({fetchApi, path, payload, searchParams, options}: {
|
||||
|
@ -290,22 +290,33 @@ const mutate = <ResponseData, Payload>({fetchApi, path, payload, searchParams, o
|
|||
});
|
||||
};
|
||||
|
||||
const afterMutate = <ResponseData, Payload>(newData: ResponseData, payload: Payload, queryClient: QueryClient, options: MutationOptions<ResponseData, Payload>) => {
|
||||
if (options.invalidateQueries) {
|
||||
queryClient.invalidateQueries([options.invalidateQueries.dataType]);
|
||||
}
|
||||
|
||||
if (options.updateQueries) {
|
||||
queryClient.setQueriesData([options.updateQueries.dataType], (data: unknown) => options.updateQueries!.update(newData, data, payload));
|
||||
}
|
||||
};
|
||||
|
||||
export const createMutation = <ResponseData, Payload>(options: MutationOptions<ResponseData, Payload>) => () => {
|
||||
const fetchApi = useFetchApi();
|
||||
const queryClient = useQueryClient();
|
||||
const {onUpdate, onInvalidate, onDelete} = useServices();
|
||||
|
||||
const afterMutate = useCallback((newData: ResponseData, payload: Payload) => {
|
||||
if (options.invalidateQueries) {
|
||||
queryClient.invalidateQueries([options.invalidateQueries.dataType]);
|
||||
onInvalidate(options.invalidateQueries.dataType);
|
||||
}
|
||||
|
||||
if (options.updateQueries) {
|
||||
queryClient.setQueriesData([options.updateQueries.dataType], (data: unknown) => options.updateQueries!.update(newData, data, payload));
|
||||
if (options.updateQueries.emberUpdateType === 'createOrUpdate') {
|
||||
onUpdate(options.updateQueries.dataType, newData);
|
||||
} else if (options.updateQueries.emberUpdateType === 'delete') {
|
||||
if (typeof payload !== 'string') {
|
||||
throw new Error('Expected delete mutation to have a string (ID) payload. Either change the payload or update the createMutation hook');
|
||||
}
|
||||
|
||||
onDelete(options.updateQueries.dataType, payload);
|
||||
}
|
||||
}
|
||||
}, [onInvalidate, onUpdate, onDelete, queryClient]);
|
||||
|
||||
return useMutation<ResponseData, unknown, Payload>({
|
||||
mutationFn: payload => mutate({fetchApi, path: options.path(payload), payload, searchParams: options.searchParams?.(payload) || options.defaultSearchParams, options}),
|
||||
onSuccess: (newData, payload) => afterMutate(newData, payload, queryClient, options)
|
||||
onSuccess: afterMutate
|
||||
});
|
||||
};
|
||||
|
|
|
@ -5,6 +5,7 @@ import config from 'ghost-admin/config/environment';
|
|||
import ghostPaths from 'ghost-admin/utils/ghost-paths';
|
||||
import {action} from '@ember/object';
|
||||
import {inject} from 'ghost-admin/decorators/inject';
|
||||
import {run} from '@ember/runloop';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {tracked} from '@glimmer/tracking';
|
||||
|
||||
|
@ -256,6 +257,17 @@ const fetchSettings = function () {
|
|||
return {read};
|
||||
};
|
||||
|
||||
const emberDataTypeMapping = {
|
||||
IntegrationsResponseType: {type: 'integration'},
|
||||
InvitesResponseType: {type: 'invite'},
|
||||
NewslettersResponseType: {type: 'newsletter'},
|
||||
RecommendationsResponseType: {type: 'recommendation'},
|
||||
SettingsResponseType: {type: 'setting', singleton: true},
|
||||
ThemesResponseType: {type: 'theme'},
|
||||
TiersResponseType: {type: 'tier'},
|
||||
UsersResponseType: {type: 'user'}
|
||||
};
|
||||
|
||||
export default class AdminXSettings extends Component {
|
||||
@service ajax;
|
||||
@service feature;
|
||||
|
@ -285,12 +297,59 @@ export default class AdminXSettings extends Component {
|
|||
// don't rethrow, app should attempt to gracefully recover
|
||||
}
|
||||
|
||||
externalNavigate = ({route, models = []}) => {
|
||||
this.router.transitionTo(route, ...models);
|
||||
onUpdate = (dataType, response) => {
|
||||
if (!emberDataTypeMapping[dataType]) {
|
||||
throw new Error(`A mutation updating ${dataType} succeeded in AdminX but there is no mapping to an Ember type. Add one to emberDataTypeMapping`);
|
||||
}
|
||||
|
||||
const {type, singleton} = emberDataTypeMapping[dataType];
|
||||
|
||||
if (singleton) {
|
||||
// Special singleton objects like settings don't work with pushPayload, we need to add the ID explicitly
|
||||
this.store.push(this.store.serializerFor(type).normalizeSingleResponse(
|
||||
this.store,
|
||||
this.store.modelFor(type),
|
||||
response,
|
||||
null,
|
||||
'queryRecord'
|
||||
));
|
||||
} else {
|
||||
this.store.pushPayload(type, response);
|
||||
}
|
||||
};
|
||||
|
||||
toggleFeatureFlag = (flag, value) => {
|
||||
this.feature.set(flag, value);
|
||||
onInvalidate = (dataType) => {
|
||||
if (!emberDataTypeMapping[dataType]) {
|
||||
throw new Error(`A mutation invalidating ${dataType} succeeded in AdminX but there is no mapping to an Ember type. Add one to emberDataTypeMapping`);
|
||||
}
|
||||
|
||||
const {type, singleton} = emberDataTypeMapping[dataType];
|
||||
|
||||
if (singleton) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`An AdminX mutation invalidated ${dataType}, but this is is marked as a singleton and cannot be reloaded in Ember. You probably wanted to use updateQueries instead of invalidateQueries`);
|
||||
return;
|
||||
}
|
||||
|
||||
run(() => this.store.unloadAll(type));
|
||||
};
|
||||
|
||||
onDelete = (dataType, id) => {
|
||||
if (!emberDataTypeMapping[dataType]) {
|
||||
throw new Error(`A mutation deleting ${dataType} succeeded in AdminX but there is no mapping to an Ember type. Add one to emberDataTypeMapping`);
|
||||
}
|
||||
|
||||
const {type} = emberDataTypeMapping[dataType];
|
||||
|
||||
const record = this.store.peekRecord(type, id);
|
||||
|
||||
if (record) {
|
||||
record.unloadRecord();
|
||||
}
|
||||
};
|
||||
|
||||
externalNavigate = ({route, models = []}) => {
|
||||
this.router.transitionTo(route, ...models);
|
||||
};
|
||||
|
||||
editorResource = fetchSettings();
|
||||
|
@ -328,10 +387,12 @@ export default class AdminXSettings extends Component {
|
|||
officialThemes={officialThemes}
|
||||
zapierTemplates={zapierTemplates}
|
||||
externalNavigate={this.externalNavigate}
|
||||
toggleFeatureFlag={this.toggleFeatureFlag}
|
||||
darkMode={this.feature.nightShift}
|
||||
unsplashConfig={defaultUnsplashHeaders}
|
||||
sentryDSN={this.config.sentry_dsn}
|
||||
onUpdate={this.onUpdate}
|
||||
onInvalidate={this.onInvalidate}
|
||||
onDelete={this.onDelete}
|
||||
/>
|
||||
</Suspense>
|
||||
</ErrorHandler>
|
||||
|
|
|
@ -23,7 +23,7 @@ export default class Setting extends ApplicationSerializer {
|
|||
}
|
||||
|
||||
serializeAttribute(snapshot, json, key, attributes) {
|
||||
// Only serialize attributes that have changed and
|
||||
// Only serialize attributes that have changed and
|
||||
// send a partial update to the API to avoid conflicts
|
||||
// with different screens using the same model
|
||||
// See https://github.com/TryGhost/Ghost/issues/15470
|
||||
|
|
Loading…
Add table
Reference in a new issue