PluginProbe
Extendify / 3.2.2
Extendify v3.2.2
3.2.2 3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 All 128 releases
← All changes | src/Agent/workflows/theme/components/change-site-design/SelectSiteDesign.jsx +141 -45 3.0.6 → 3.2.2 View file →
@@ -1,13 +1,22 @@
1 +import { usePaletteOverride } from '@agent/hooks/usePaletteOverride';
2 +import { usePalettes } from '@agent/hooks/usePalettes';
1 3 import { useSiteVibesOverride } from '@agent/hooks/useSiteVibesOverride';
2 4 import { useSiteVibesVariations } from '@agent/hooks/useSiteVibesVariations';
3 5 import { useVariationOverride } from '@agent/hooks/useVariationOverride';
6 +import { refreshBlockHighlight } from '@agent/lib/block-highlight';
4 7 import { DesignOption } from '@agent/workflows/theme/components/change-site-design/DesignOption';
8 +import { fontsOnlyVariation } from '@agent/workflows/theme/components/change-site-design/utils/fontsOnlyVariation';
5 9 import { removeAnimationClasses } from '@agent/workflows/theme/components/change-site-design/utils/removeAnimationClasses';
10 +import { handleSiteImages } from '@auto-launch/fetchers/get-images';
11 +import { useUserSelectionStore } from '@launch/state/user-selections';
12 +import { paletteDuotone } from '@shared/lib/palette-preview';
13 +import { samplePalettes } from '@shared/lib/palettes';
14 +import { safeParseJson } from '@shared/lib/parsing';
15 +import { normalizeSiteImages } from '@shared/lib/site-images';
6 16 import apiFetch from '@wordpress/api-fetch';
7 17 import { registerCoreBlocks } from '@wordpress/block-library';
8 18 import { getBlockTypes, parse, serialize } from '@wordpress/blocks';
9 -import { Spinner } from '@wordpress/components';
10 19 import { useDispatch } from '@wordpress/data';
11 20 import { useEffect, useMemo, useRef, useState } from '@wordpress/element';
12 21 import { __ } from '@wordpress/i18n';
13 22 import classnames from 'classnames';
@@ -53,8 +62,31 @@
53 62 const isAdmin = context?.adminPage;
54 63
55 64 const PAGE_SIZE = 5;
56 65
66 +const hasImages = ({ hero, general }) => hero.length > 0 || general.length > 0;
67 +
68 +const resolveSiteImages = async ({ storedSiteImages, siteProfile }) => {
69 + const stored = normalizeSiteImages(storedSiteImages);
70 + if (hasImages(stored)) return stored;
71 +
72 + // A browser that didn't run Launch otherwise reuses one image on every option.
73 + const localized = normalizeSiteImages(window.extSharedData.siteImages);
74 + if (hasImages(localized)) return localized;
75 +
76 + if (siteProfile) {
77 + const { siteImages } = await handleSiteImages({ siteProfile });
78 + const fetched = normalizeSiteImages(siteImages);
79 + if (hasImages(fetched)) return fetched;
80 + }
81 +
82 + const saved = await apiFetch({
83 + path: '/extendify/v1/shared/site-images',
84 + }).catch(() => null);
85 +
86 + return normalizeSiteImages(saved?.siteImages);
87 +};
88 +
57 89 export const SelectSiteDesign = ({ onConfirm, onCancel }) => {
58 90 const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
59 91 const [isLoading, setIsLoading] = useState(false);
60 92 const [isSaving, setIsSaving] = useState(false);
@@ -67,8 +99,9 @@
67 99
68 100 const [selectedColorAndFonts, setSelectedColorAndFonts] = useState();
69 101 const [selectedHeroPattern, setSelectedHeroPattern] = useState();
70 102 const [selectedVibe, setSelectedVibe] = useState();
103 + const [selectedPalette, setSelectedPalette] = useState();
71 104
72 105 const { updateSettings } = useDispatch('core/block-editor');
73 106
74 107 const injectedLinksRef = useRef([]);
@@ -95,14 +128,35 @@
95 128 });
96 129 injectedLinksRef.current = [];
97 130 };
98 131
132 + const {
133 + palettes,
134 + preferred,
135 + css: paletteCss,
136 + isLoading: isLoadingPalettes,
137 + } = usePalettes();
138 + const onPalettes = Boolean(palettes?.length);
139 + const shownPalettes = useMemo(
140 + () => samplePalettes(palettes, preferred),
141 + [palettes, preferred],
142 + );
143 + const paletteFor = (index) =>
144 + onPalettes ? shownPalettes[index % shownPalettes.length] : undefined;
145 +
99 146 const { undoChange: undoColorAndFontsChange } = useVariationOverride({
100 147 css: !isAdmin && selectedColorAndFonts?.css,
101 148 duotoneTheme:
102 - !isAdmin && selectedColorAndFonts?.settings?.color?.duotone?.theme,
149 + !isAdmin &&
150 + !onPalettes &&
151 + selectedColorAndFonts?.settings?.color?.duotone?.theme,
103 152 });
104 153
154 + const { undoChange: undoPaletteChange } = usePaletteOverride({
155 + css: (!isAdmin && paletteCss?.[selectedPalette?.slug]) || '',
156 + duotoneTheme: (!isAdmin && paletteDuotone(selectedPalette)) || null,
157 + });
158 +
105 159 const { undoChange: undoVibesChange } = useSiteVibesOverride({
106 160 css: !isAdmin && selectedVibe?.css,
107 161 slug: !isAdmin && selectedVibe?.slug,
108 162 });
@@ -110,9 +164,10 @@
110 164 const { data: vibesData, isLoading: isLoadingVibes } =
111 165 useSiteVibesVariations();
112 166
113 167 const vibes = useMemo(() => {
114 - if (isLoadingVibes) return null;
168 + // Null for a theme with no block variations, which .css would throw on.
169 + if (isLoadingVibes || !vibesData?.css) return [];
115 170
116 171 const vibes = Object.entries(vibesData.css)
117 172 .filter(([slug]) => slug !== vibesData.currentVibe)
118 173 .map(([slug, css]) => ({
@@ -117,8 +172,13 @@
117 172 .filter(([slug]) => slug !== vibesData.currentVibe)
118 173 .map(([slug, css]) => ({
119 174 slug,
120 175 css: css?.replaceAll(slug, 'natural-1'),
176 + // Css only adds, so a cloned page keeps the applied vibe's slots.
177 + previewCss: `${vibesData.resets?.[slug] ?? ''}${css ?? ''}`.replaceAll(
178 + slug,
179 + 'natural-1',
180 + ),
121 181 }))
122 182 .sort(() => Math.random() - 0.5);
123 183
124 184 return [...vibes, ...vibes.slice(0, 3)];
@@ -145,12 +205,12 @@
145 205 heroSectionElement?.querySelector('p')?.textContent ?? null;
146 206 const cta =
147 207 heroSectionElement?.querySelector('.wp-block-button__link') ?? null;
148 208 const heroPatternName =
149 - [...(heroSectionElement?.classList ?? [])]
150 - .find((className) => className.startsWith('ext-hero-section--'))
151 - ?.replace('ext-hero-section--', '') ?? null;
152 - const images = [...(heroSectionElement?.querySelectorAll('img') ?? [])]
209 + heroSectionElement?.className?.match(
210 + /ext-hero-section ext-hero-section--(\S+)/,
211 + )?.[1] ?? null;
212 + const domImages = [...(heroSectionElement?.querySelectorAll('img') ?? [])]
153 213 .filter(
154 214 (img) =>
155 215 !img.src.includes('.svg') && !img.src.includes('data:image/svg+xml'),
156 216 )
@@ -162,35 +222,54 @@
162 222 return img.src;
163 223 }
164 224 });
165 225
166 - apiFetch({
167 - path: '/extendify/v1/agent/site-design-variations',
168 - method: 'POST',
169 - data: {
170 - title,
171 - images,
172 - description,
173 - currentHeroPattern: heroPatternName,
174 - cta: {
175 - label: cta?.textContent,
176 - link: cta?.href,
226 + const { siteId } = window.extSharedData;
227 + const stored = safeParseJson(
228 + localStorage.getItem(`extendify-launch-data-${siteId}`),
229 + );
230 + const launchState = useUserSelectionStore.getState();
231 + const storedSiteImages =
232 + stored?.state?.siteImages ?? launchState?.siteImages?.siteImages ?? [];
233 + const siteProfile = stored?.state?.siteProfile ?? launchState?.siteProfile;
234 +
235 + (async () => {
236 + const siteImages = await resolveSiteImages({
237 + storedSiteImages,
238 + siteProfile,
239 + });
240 +
241 + apiFetch({
242 + path: '/extendify/v1/agent/site-design-variations',
243 + method: 'POST',
244 + data: {
245 + title,
246 + images: domImages,
247 + siteImages,
248 + postId: context?.postId,
249 + description,
250 + currentHeroPattern: heroPatternName,
251 + source: 'change-site-design-workflow',
252 + cta: {
253 + label: cta?.textContent,
254 + link: cta?.href,
255 + },
177 256 },
178 - },
179 - })
180 - .then((data) => {
181 - setHeroPatterns(data?.patterns?.flat() ?? []);
182 - setColorAndFontsVariations(
183 - [...(data?.colorAndFontsVariations ?? [])].sort(
184 - () => Math.random() - 0.5,
185 - ),
186 - );
257 + })
258 + .then((data) => {
259 + setHeroPatterns(data?.patterns?.flat() ?? []);
260 + setColorAndFontsVariations(
261 + [...(data?.colorAndFontsVariations ?? [])].sort(
262 + () => Math.random() - 0.5,
263 + ),
264 + );
187 265
188 - setBlockEditorStyles(data?.blockEditorSettings);
189 - })
190 - .finally(() => {
191 - setIsLoading(false);
192 - });
266 + setBlockEditorStyles(data?.blockEditorSettings);
267 + })
268 + .finally(() => {
269 + setIsLoading(false);
270 + });
271 + })();
193 272 }, []);
194 273
195 274 useEffect(() => {
196 275 if (blockEditorStyles) {
@@ -212,12 +291,21 @@
212 291
213 292 const undoChanges = () => {
214 293 undoHeroSectionChange();
215 294 undoColorAndFontsChange();
295 + undoPaletteChange();
216 296 undoVibesChange();
217 297 removeInjectedLinks();
298 + refreshBlockHighlight();
218 299 };
219 300
301 + const confirmed = useRef(false);
302 + useEffect(() => {
303 + return () => {
304 + if (!confirmed.current) undoChanges();
305 + };
306 + }, []);
307 +
220 308 const handleCancel = () => {
221 309 undoChanges();
222 310 onCancel();
223 311 };
@@ -225,8 +313,9 @@
225 313 const handleConfirm = async () => {
226 314 if (!selectedHeroPattern) return;
227 315
228 316 if (selectedHeroPattern.isCurrent) {
317 + confirmed.current = true;
229 318 onConfirm({
230 319 data: { postId: context?.postId },
231 320 shouldRefreshPage: false,
232 321 });
@@ -261,14 +350,18 @@
261 350 return parse(selectedHeroPattern.code)?.[0] || block;
262 351 }),
263 352 );
264 353
354 + confirmed.current = true;
265 355 onConfirm({
266 356 data: {
267 357 updatedPageBlocks,
268 358 postId,
269 359 vibeSlug: selectedVibe.slug,
270 - colorAndFontsVariation: selectedColorAndFonts,
360 + colorAndFontsVariation: onPalettes
361 + ? fontsOnlyVariation(selectedColorAndFonts)
362 + : selectedColorAndFonts,
363 + colorPalette: selectedPalette?.slug,
271 364 },
272 365 shouldRefreshPage: true,
273 366 });
274 367 } catch (error) {
@@ -277,18 +370,18 @@
277 370 setIsSaving(false);
278 371 }
279 372 };
280 373
281 - if (isLoading || isLoadingVibes) {
374 + if (isLoading || isLoadingVibes || isLoadingPalettes) {
282 375 return (
283 - <div className="flex justify-center flex-col gap-1">
284 - <Spinner />
376 + <div className="min-h-24 p-2 text-center text-sm">
377 + {__('Loading design options...', 'extendify-local')}
285 378 </div>
286 379 );
287 380 }
288 381
289 382 return (
290 - <div className="mb-4 ml-10 mr-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50 rtl:ml-2 rtl:mr-10">
383 + <div className="mb-4 ms-2 me-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50">
291 384 <div className="rounded-lg border-b border-gray-300 bg-white">
292 385 <div className="flex flex-col gap-4 p-3">
293 386 {currentHeroHtml && (
294 387 <DesignOption
@@ -298,8 +391,9 @@
298 391 onClick={() => {
299 392 setSelectedHeroPattern(currentDesignOption);
300 393 setSelectedColorAndFonts(null);
301 394 setSelectedVibe(null);
395 + setSelectedPalette(null);
302 396
303 397 undoChanges();
304 398 }}
305 399 />
@@ -311,11 +405,13 @@
311 405 isSelected={selectedHeroPattern?.id === heroPattern.id}
312 406 styles={{
313 407 linkStyles: heroPattern.linkStyles,
314 408 colorAndFontsVariations: colorAndFontsVariations[i].css,
315 - duotoneTheme:
316 - colorAndFontsVariations[i]?.settings?.color?.duotone?.theme,
317 - vibes: vibes[i]?.css,
409 + paletteCss: paletteCss?.[paletteFor(i)?.slug],
410 + duotoneTheme: onPalettes
411 + ? paletteDuotone(paletteFor(i))
412 + : colorAndFontsVariations[i]?.settings?.color?.duotone?.theme,
413 + vibes: vibes[i]?.previewCss,
318 414 blockSupportsCss: heroPattern.blockSupportsCss,
319 415 }}
320 416 onClick={() => {
321 417 setSelectedHeroPattern(heroPattern);
@@ -320,8 +416,9 @@
320 416 onClick={() => {
321 417 setSelectedHeroPattern(heroPattern);
322 418 setSelectedColorAndFonts(colorAndFontsVariations[i]);
323 419 setSelectedVibe(vibes[i]);
420 + setSelectedPalette(paletteFor(i));
324 421
325 422 if (!isAdmin) {
326 423 const blockSupportsCss = heroPattern.blockSupportsCss;
327 424 if (blockSupportsCss) {
@@ -336,8 +433,9 @@
336 433
337 434 injectLinkStyles(heroPattern.linkStyles);
338 435
339 436 updateHeroSection(heroPattern.renderedHtml);
437 + refreshBlockHighlight();
340 438 }
341 439 }}
342 440 />
343 441 ))}
@@ -384,13 +482,11 @@
384 482 )}
385 483 disabled={!selectedHeroPattern || isSaving}
386 484 onClick={handleConfirm}
387 485 >
388 - {isSaving ? (
389 - <Spinner className="m-0" />
390 - ) : (
391 - __('Save', 'extendify-local')
392 - )}
486 + {isSaving
487 + ? __('Saving...', 'extendify-local')
488 + : __('Save', 'extendify-local')}
393 489 </button>
394 490 </div>
395 491 </div>
396 492 );