PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / assets / js / features / provider.js

provider.js in ElasticPress 5.3.5, at assets/js/features/provider.js

223 lines 5.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * WordPress dependencies.
3 */
4 import apiFetch from '@wordpress/api-fetch';
5 import {
6 createContext,
7 useCallback,
8 useContext,
9 useMemo,
10 useState,
11 WPElement,
12 } from '@wordpress/element';
13 import { isEqual } from 'lodash'; // eslint-disable-line import/no-extraneous-dependencies
14
15 /**
16 * Sync context.
17 */
18 const Context = createContext();
19
20 /**
21 * Feature settings provider.
22 *
23 * Provides data and methods for interacting with ElasticPress feature
24 * settings.
25 *
26 * @param {object} props Component props.
27 * @param {string} props.apiUrl API URL.
28 * @param {Function} props.children Component children
29 * @param {object} props.defaultSettings Default settings values.
30 * @param {string} props.epioLogoUrl ElasticPress.io logo URL.
31 * @param {object} props.features Features data.
32 * @param {object} props.indexMeta Index meta.
33 * @param {object} props.syncedSettings Settings at last sync.
34 * @returns {WPElement}
35 */
36 export const FeatureSettingsProvider = ({
37 apiUrl,
38 children,
39 defaultSettings,
40 epioLogoUrl,
41 features: initialFeatures,
42 indexMeta,
43 syncedSettings,
44 }) => {
45 const [isBusy, setIsBusy] = useState(false);
46 const [isSyncing, setIsSyncing] = useState(!!indexMeta);
47 const [settings, setSettings] = useState({ ...defaultSettings });
48 const [savedSettings, setSavedSettings] = useState({ ...syncedSettings });
49 const [features, setFeatures] = useState(initialFeatures);
50
51 /**
52 * Refetch the authoritative state snapshot from the REST API.
53 */
54 const refresh = useCallback(async () => {
55 const response = await apiFetch({
56 method: 'GET',
57 url: apiUrl,
58 });
59
60 setFeatures(response.features);
61 setSavedSettings(response.settings);
62 setSettings(response.settingsDraft ?? response.settings);
63 }, [apiUrl]);
64
65 /**
66 * Get a feature's data by its slug.
67 *
68 * @param {string} slug Feature slug.
69 * @returns {object} Feature data.
70 */
71 const getFeature = useCallback(
72 (slug) => {
73 return features.find((f) => f.slug === slug);
74 },
75 [features],
76 );
77
78 /**
79 * Whether the settings have changes.
80 */
81 const isModified = useMemo(() => {
82 // The force_inactive attribute is something that is not controlled via UI, so we remove it before comparing things.
83 const deleteForceInactive = (settings) => {
84 Object.keys(settings).forEach((feature) => {
85 delete settings[feature].force_inactive;
86 });
87 };
88 deleteForceInactive(settings);
89 deleteForceInactive(savedSettings);
90
91 return !isEqual(settings, savedSettings);
92 }, [settings, savedSettings]);
93
94 /**
95 * Return whether a setting change will require a sync, based on the
96 * current value.
97 */
98 const willSettingRequireSync = useCallback((value, previousValue, requiresSync) => {
99 return requiresSync && value && value !== '0' && value !== previousValue;
100 }, []);
101
102 /**
103 * Return whether a feature requires a sync based on the current setting
104 * values.
105 *
106 * @returns {boolean}
107 */
108 const willFeatureRequireSync = useCallback(
109 (feature) => {
110 const { slug, settingsSchema } = feature;
111
112 return settingsSchema.some((s) => {
113 /**
114 * Settings that require a sync will only require a sync if the
115 * feature will be active.
116 */
117 if (settings?.[slug]?.active !== true) {
118 return false;
119 }
120
121 /**
122 * A setting requires a sync if it is flagged as requiring one,
123 * but only if it has a value. A sync is not required when a
124 * feature that requires a sync is disabled, for example.
125 */
126 const requiresSync = s.requires_sync;
127 const value = settings?.[slug]?.[s.key];
128 const previousValue = syncedSettings?.[slug]?.[s.key];
129
130 return willSettingRequireSync(value, previousValue, requiresSync);
131 });
132 },
133 [settings, syncedSettings, willSettingRequireSync],
134 );
135
136 /**
137 * The features that require a sync based on the current setting values.
138 */
139 const featuresRequiringSync = useMemo(() => {
140 return features.reduce((featuresRequiringSync, feature) => {
141 const settingsRequireSync = willFeatureRequireSync(feature);
142
143 if (settingsRequireSync) {
144 featuresRequiringSync.push(feature.slug);
145 }
146
147 return featuresRequiringSync;
148 }, []);
149 }, [features, willFeatureRequireSync]);
150
151 /**
152 * Whether a sync is required based on the current setting values.
153 */
154 const isSyncRequired = useMemo(() => {
155 return !!featuresRequiringSync.length;
156 }, [featuresRequiringSync]);
157
158 /**
159 * Handle resetting all settings.
160 *
161 * @returns {void}
162 */
163 const resetSettings = () => {
164 setSettings({ ...savedSettings });
165 };
166
167 /**
168 * Save settings.
169 *
170 * @returns {void}
171 */
172 const saveSettings = async () => {
173 try {
174 setIsBusy(true);
175
176 await apiFetch({
177 body: JSON.stringify(settings),
178 headers: {
179 'Content-Type': 'application/json',
180 },
181 method: 'PUT',
182 url: apiUrl,
183 });
184
185 await refresh();
186 } finally {
187 setIsBusy(false);
188 }
189 };
190
191 // eslint-disable-next-line react/jsx-no-constructed-context-values
192 const contextValue = {
193 epioLogoUrl,
194 features,
195 featuresRequiringSync,
196 getFeature,
197 isBusy,
198 isModified,
199 isSyncing,
200 setIsSyncing,
201 isSyncRequired,
202 refresh,
203 resetSettings,
204 saveSettings,
205 savedSettings,
206 syncedSettings,
207 settings,
208 setSettings,
209 willSettingRequireSync,
210 };
211
212 return <Context.Provider value={contextValue}>{children}</Context.Provider>;
213 };
214
215 /**
216 * Use the API Search context.
217 *
218 * @returns {object} API Search Context.
219 */
220 export const useFeatureSettings = () => {
221 return useContext(Context);
222 };
223