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 +165 -58 3.0.4 → 3.2.2 View file →
@@ -1,47 +1,62 @@
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';
14 23
24 +let originalHeroElement = null;
25 +
15 26 const undoHeroSectionChange = () => {
16 - document.querySelector('.ext-hero-section-preview')?.remove();
17 -
18 - const heroSectionElement = document.querySelector('.ext-hero-section');
19 -
20 - if (heroSectionElement) heroSectionElement.style.display = 'block';
27 + const preview = document.querySelector('.ext-hero-section-preview');
28 + if (preview && originalHeroElement) {
29 + preview.replaceWith(originalHeroElement);
30 + } else {
31 + preview?.remove();
32 + }
33 + originalHeroElement = null;
21 34 };
22 35
23 36 const updateHeroSection = (content) => {
24 - document.querySelector('.ext-hero-section-preview')?.remove();
37 + const tempDiv = document.createElement('div');
38 + tempDiv.innerHTML = content;
39 + const newNode = tempDiv.firstElementChild;
40 + newNode.classList.add('ext-hero-section-preview');
25 41
42 + const existingPreview = document.querySelector('.ext-hero-section-preview');
43 + if (existingPreview) {
44 + existingPreview.replaceWith(newNode);
45 + return;
46 + }
47 +
26 48 const heroSectionElement = document.querySelector('.ext-hero-section');
27 -
28 49 if (heroSectionElement) {
29 - heroSectionElement.style.display = 'none';
50 + originalHeroElement = heroSectionElement;
51 + heroSectionElement.replaceWith(newNode);
52 + return;
30 53 }
31 54
32 55 const contentArea =
33 56 document.querySelector('.entry-content') ?? document.querySelector('main');
34 -
35 57 if (!contentArea) return;
36 -
37 - contentArea.insertAdjacentHTML('afterbegin', content);
38 -
39 - const visibleHeroSection = [
40 - ...document.querySelectorAll('.ext-hero-section'),
41 - ].find((el) => el.style.display !== 'none');
42 -
43 - visibleHeroSection?.classList.add('ext-hero-section-preview');
58 + contentArea.insertAdjacentElement('afterbegin', newNode);
44 59 };
45 60
46 61 const { context } = window.extAgentData;
47 62 const isAdmin = context?.adminPage;
@@ -47,8 +62,31 @@
47 62 const isAdmin = context?.adminPage;
48 63
49 64 const PAGE_SIZE = 5;
50 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 +
51 89 export const SelectSiteDesign = ({ onConfirm, onCancel }) => {
52 90 const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
53 91 const [isLoading, setIsLoading] = useState(false);
54 92 const [isSaving, setIsSaving] = useState(false);
@@ -61,8 +99,9 @@
61 99
62 100 const [selectedColorAndFonts, setSelectedColorAndFonts] = useState();
63 101 const [selectedHeroPattern, setSelectedHeroPattern] = useState();
64 102 const [selectedVibe, setSelectedVibe] = useState();
103 + const [selectedPalette, setSelectedPalette] = useState();
65 104
66 105 const { updateSettings } = useDispatch('core/block-editor');
67 106
68 107 const injectedLinksRef = useRef([]);
@@ -89,14 +128,35 @@
89 128 });
90 129 injectedLinksRef.current = [];
91 130 };
92 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 +
93 146 const { undoChange: undoColorAndFontsChange } = useVariationOverride({
94 147 css: !isAdmin && selectedColorAndFonts?.css,
95 148 duotoneTheme:
96 - !isAdmin && selectedColorAndFonts?.settings?.color?.duotone?.theme,
149 + !isAdmin &&
150 + !onPalettes &&
151 + selectedColorAndFonts?.settings?.color?.duotone?.theme,
97 152 });
98 153
154 + const { undoChange: undoPaletteChange } = usePaletteOverride({
155 + css: (!isAdmin && paletteCss?.[selectedPalette?.slug]) || '',
156 + duotoneTheme: (!isAdmin && paletteDuotone(selectedPalette)) || null,
157 + });
158 +
99 159 const { undoChange: undoVibesChange } = useSiteVibesOverride({
100 160 css: !isAdmin && selectedVibe?.css,
101 161 slug: !isAdmin && selectedVibe?.slug,
102 162 });
@@ -104,9 +164,10 @@
104 164 const { data: vibesData, isLoading: isLoadingVibes } =
105 165 useSiteVibesVariations();
106 166
107 167 const vibes = useMemo(() => {
108 - if (isLoadingVibes) return null;
168 + // Null for a theme with no block variations, which .css would throw on.
169 + if (isLoadingVibes || !vibesData?.css) return [];
109 170
110 171 const vibes = Object.entries(vibesData.css)
111 172 .filter(([slug]) => slug !== vibesData.currentVibe)
112 173 .map(([slug, css]) => ({
@@ -111,8 +172,13 @@
111 172 .filter(([slug]) => slug !== vibesData.currentVibe)
112 173 .map(([slug, css]) => ({
113 174 slug,
114 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 + ),
115 181 }))
116 182 .sort(() => Math.random() - 0.5);
117 183
118 184 return [...vibes, ...vibes.slice(0, 3)];
@@ -138,9 +204,13 @@
138 204 const description =
139 205 heroSectionElement?.querySelector('p')?.textContent ?? null;
140 206 const cta =
141 207 heroSectionElement?.querySelector('.wp-block-button__link') ?? null;
142 - const images = [...(heroSectionElement?.querySelectorAll('img') ?? [])]
208 + const heroPatternName =
209 + heroSectionElement?.className?.match(
210 + /ext-hero-section ext-hero-section--(\S+)/,
211 + )?.[1] ?? null;
212 + const domImages = [...(heroSectionElement?.querySelectorAll('img') ?? [])]
143 213 .filter(
144 214 (img) =>
145 215 !img.src.includes('.svg') && !img.src.includes('data:image/svg+xml'),
146 216 )
@@ -152,34 +222,54 @@
152 222 return img.src;
153 223 }
154 224 });
155 225
156 - apiFetch({
157 - path: '/extendify/v1/agent/site-design-variations',
158 - method: 'POST',
159 - data: {
160 - title,
161 - images,
162 - description,
163 - cta: {
164 - label: cta?.textContent,
165 - 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 + },
166 256 },
167 - },
168 - })
169 - .then((data) => {
170 - setHeroPatterns(data?.patterns?.flat() ?? []);
171 - setColorAndFontsVariations(
172 - [...(data?.colorAndFontsVariations ?? [])].sort(
173 - () => Math.random() - 0.5,
174 - ),
175 - );
257 + })
258 + .then((data) => {
259 + setHeroPatterns(data?.patterns?.flat() ?? []);
260 + setColorAndFontsVariations(
261 + [...(data?.colorAndFontsVariations ?? [])].sort(
262 + () => Math.random() - 0.5,
263 + ),
264 + );
176 265
177 - setBlockEditorStyles(data?.blockEditorSettings);
178 - })
179 - .finally(() => {
180 - setIsLoading(false);
181 - });
266 + setBlockEditorStyles(data?.blockEditorSettings);
267 + })
268 + .finally(() => {
269 + setIsLoading(false);
270 + });
271 + })();
182 272 }, []);
183 273
184 274 useEffect(() => {
185 275 if (blockEditorStyles) {
@@ -201,12 +291,21 @@
201 291
202 292 const undoChanges = () => {
203 293 undoHeroSectionChange();
204 294 undoColorAndFontsChange();
295 + undoPaletteChange();
205 296 undoVibesChange();
206 297 removeInjectedLinks();
298 + refreshBlockHighlight();
207 299 };
208 300
301 + const confirmed = useRef(false);
302 + useEffect(() => {
303 + return () => {
304 + if (!confirmed.current) undoChanges();
305 + };
306 + }, []);
307 +
209 308 const handleCancel = () => {
210 309 undoChanges();
211 310 onCancel();
212 311 };
@@ -214,8 +313,9 @@
214 313 const handleConfirm = async () => {
215 314 if (!selectedHeroPattern) return;
216 315
217 316 if (selectedHeroPattern.isCurrent) {
317 + confirmed.current = true;
218 318 onConfirm({
219 319 data: { postId: context?.postId },
220 320 shouldRefreshPage: false,
221 321 });
@@ -250,14 +350,18 @@
250 350 return parse(selectedHeroPattern.code)?.[0] || block;
251 351 }),
252 352 );
253 353
354 + confirmed.current = true;
254 355 onConfirm({
255 356 data: {
256 357 updatedPageBlocks,
257 358 postId,
258 359 vibeSlug: selectedVibe.slug,
259 - colorAndFontsVariation: selectedColorAndFonts,
360 + colorAndFontsVariation: onPalettes
361 + ? fontsOnlyVariation(selectedColorAndFonts)
362 + : selectedColorAndFonts,
363 + colorPalette: selectedPalette?.slug,
260 364 },
261 365 shouldRefreshPage: true,
262 366 });
263 367 } catch (error) {
@@ -266,18 +370,18 @@
266 370 setIsSaving(false);
267 371 }
268 372 };
269 373
270 - if (isLoading || isLoadingVibes) {
374 + if (isLoading || isLoadingVibes || isLoadingPalettes) {
271 375 return (
272 - <div className="flex justify-center flex-col gap-1">
273 - <Spinner />
376 + <div className="min-h-24 p-2 text-center text-sm">
377 + {__('Loading design options...', 'extendify-local')}
274 378 </div>
275 379 );
276 380 }
277 381
278 382 return (
279 - <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">
280 384 <div className="rounded-lg border-b border-gray-300 bg-white">
281 385 <div className="flex flex-col gap-4 p-3">
282 386 {currentHeroHtml && (
283 387 <DesignOption
@@ -287,8 +391,9 @@
287 391 onClick={() => {
288 392 setSelectedHeroPattern(currentDesignOption);
289 393 setSelectedColorAndFonts(null);
290 394 setSelectedVibe(null);
395 + setSelectedPalette(null);
291 396
292 397 undoChanges();
293 398 }}
294 399 />
@@ -300,11 +405,13 @@
300 405 isSelected={selectedHeroPattern?.id === heroPattern.id}
301 406 styles={{
302 407 linkStyles: heroPattern.linkStyles,
303 408 colorAndFontsVariations: colorAndFontsVariations[i].css,
304 - duotoneTheme:
305 - colorAndFontsVariations[i]?.settings?.color?.duotone?.theme,
306 - 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,
307 414 blockSupportsCss: heroPattern.blockSupportsCss,
308 415 }}
309 416 onClick={() => {
310 417 setSelectedHeroPattern(heroPattern);
@@ -309,8 +416,9 @@
309 416 onClick={() => {
310 417 setSelectedHeroPattern(heroPattern);
311 418 setSelectedColorAndFonts(colorAndFontsVariations[i]);
312 419 setSelectedVibe(vibes[i]);
420 + setSelectedPalette(paletteFor(i));
313 421
314 422 if (!isAdmin) {
315 423 const blockSupportsCss = heroPattern.blockSupportsCss;
316 424 if (blockSupportsCss) {
@@ -325,8 +433,9 @@
325 433
326 434 injectLinkStyles(heroPattern.linkStyles);
327 435
328 436 updateHeroSection(heroPattern.renderedHtml);
437 + refreshBlockHighlight();
329 438 }
330 439 }}
331 440 />
332 441 ))}
@@ -373,13 +482,11 @@
373 482 )}
374 483 disabled={!selectedHeroPattern || isSaving}
375 484 onClick={handleConfirm}
376 485 >
377 - {isSaving ? (
378 - <Spinner className="m-0" />
379 - ) : (
380 - __('Save', 'extendify-local')
381 - )}
486 + {isSaving
487 + ? __('Saving...', 'extendify-local')
488 + : __('Save', 'extendify-local')}
382 489 </button>
383 490 </div>
384 491 </div>
385 492 );