PluginProbe
Extendify / trunk
Extendify vtrunk
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 0.6.0 All 127 releases
← All changes | src/Agent/workflows/theme/components/change-site-design/SelectSiteDesign.jsx +161 -56 3.0.5 → trunk View file →
@@ -1,9 +1,19 @@
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 19 import { Spinner } from '@wordpress/components';
@@ -11,37 +21,43 @@
11 21 import { useEffect, useMemo, useRef, useState } from '@wordpress/element';
12 22 import { __ } from '@wordpress/i18n';
13 23 import classnames from 'classnames';
14 24
25 +let originalHeroElement = null;
26 +
15 27 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';
28 + const preview = document.querySelector('.ext-hero-section-preview');
29 + if (preview && originalHeroElement) {
30 + preview.replaceWith(originalHeroElement);
31 + } else {
32 + preview?.remove();
33 + }
34 + originalHeroElement = null;
21 35 };
22 36
23 37 const updateHeroSection = (content) => {
24 - document.querySelector('.ext-hero-section-preview')?.remove();
38 + const tempDiv = document.createElement('div');
39 + tempDiv.innerHTML = content;
40 + const newNode = tempDiv.firstElementChild;
41 + newNode.classList.add('ext-hero-section-preview');
25 42
43 + const existingPreview = document.querySelector('.ext-hero-section-preview');
44 + if (existingPreview) {
45 + existingPreview.replaceWith(newNode);
46 + return;
47 + }
48 +
26 49 const heroSectionElement = document.querySelector('.ext-hero-section');
27 -
28 50 if (heroSectionElement) {
29 - heroSectionElement.style.display = 'none';
51 + originalHeroElement = heroSectionElement;
52 + heroSectionElement.replaceWith(newNode);
53 + return;
30 54 }
31 55
32 56 const contentArea =
33 57 document.querySelector('.entry-content') ?? document.querySelector('main');
34 -
35 58 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');
59 + contentArea.insertAdjacentElement('afterbegin', newNode);
44 60 };
45 61
46 62 const { context } = window.extAgentData;
47 63 const isAdmin = context?.adminPage;
@@ -47,8 +63,31 @@
47 63 const isAdmin = context?.adminPage;
48 64
49 65 const PAGE_SIZE = 5;
50 66
67 +const hasImages = ({ hero, general }) => hero.length > 0 || general.length > 0;
68 +
69 +const resolveSiteImages = async ({ storedSiteImages, siteProfile }) => {
70 + const stored = normalizeSiteImages(storedSiteImages);
71 + if (hasImages(stored)) return stored;
72 +
73 + // A browser that didn't run Launch otherwise reuses one image on every option.
74 + const localized = normalizeSiteImages(window.extSharedData.siteImages);
75 + if (hasImages(localized)) return localized;
76 +
77 + if (siteProfile) {
78 + const { siteImages } = await handleSiteImages({ siteProfile });
79 + const fetched = normalizeSiteImages(siteImages);
80 + if (hasImages(fetched)) return fetched;
81 + }
82 +
83 + const saved = await apiFetch({
84 + path: '/extendify/v1/shared/site-images',
85 + }).catch(() => null);
86 +
87 + return normalizeSiteImages(saved?.siteImages);
88 +};
89 +
51 90 export const SelectSiteDesign = ({ onConfirm, onCancel }) => {
52 91 const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
53 92 const [isLoading, setIsLoading] = useState(false);
54 93 const [isSaving, setIsSaving] = useState(false);
@@ -61,8 +100,9 @@
61 100
62 101 const [selectedColorAndFonts, setSelectedColorAndFonts] = useState();
63 102 const [selectedHeroPattern, setSelectedHeroPattern] = useState();
64 103 const [selectedVibe, setSelectedVibe] = useState();
104 + const [selectedPalette, setSelectedPalette] = useState();
65 105
66 106 const { updateSettings } = useDispatch('core/block-editor');
67 107
68 108 const injectedLinksRef = useRef([]);
@@ -89,14 +129,35 @@
89 129 });
90 130 injectedLinksRef.current = [];
91 131 };
92 132
133 + const {
134 + palettes,
135 + preferred,
136 + css: paletteCss,
137 + isLoading: isLoadingPalettes,
138 + } = usePalettes();
139 + const onPalettes = Boolean(palettes?.length);
140 + const shownPalettes = useMemo(
141 + () => samplePalettes(palettes, preferred),
142 + [palettes, preferred],
143 + );
144 + const paletteFor = (index) =>
145 + onPalettes ? shownPalettes[index % shownPalettes.length] : undefined;
146 +
93 147 const { undoChange: undoColorAndFontsChange } = useVariationOverride({
94 148 css: !isAdmin && selectedColorAndFonts?.css,
95 149 duotoneTheme:
96 - !isAdmin && selectedColorAndFonts?.settings?.color?.duotone?.theme,
150 + !isAdmin &&
151 + !onPalettes &&
152 + selectedColorAndFonts?.settings?.color?.duotone?.theme,
97 153 });
98 154
155 + const { undoChange: undoPaletteChange } = usePaletteOverride({
156 + css: (!isAdmin && paletteCss?.[selectedPalette?.slug]) || '',
157 + duotoneTheme: (!isAdmin && paletteDuotone(selectedPalette)) || null,
158 + });
159 +
99 160 const { undoChange: undoVibesChange } = useSiteVibesOverride({
100 161 css: !isAdmin && selectedVibe?.css,
101 162 slug: !isAdmin && selectedVibe?.slug,
102 163 });
@@ -104,9 +165,10 @@
104 165 const { data: vibesData, isLoading: isLoadingVibes } =
105 166 useSiteVibesVariations();
106 167
107 168 const vibes = useMemo(() => {
108 - if (isLoadingVibes) return null;
169 + // Null for a theme with no block variations, which .css would throw on.
170 + if (isLoadingVibes || !vibesData?.css) return [];
109 171
110 172 const vibes = Object.entries(vibesData.css)
111 173 .filter(([slug]) => slug !== vibesData.currentVibe)
112 174 .map(([slug, css]) => ({
@@ -111,8 +173,13 @@
111 173 .filter(([slug]) => slug !== vibesData.currentVibe)
112 174 .map(([slug, css]) => ({
113 175 slug,
114 176 css: css?.replaceAll(slug, 'natural-1'),
177 + // Css only adds, so a cloned page keeps the applied vibe's slots.
178 + previewCss: `${vibesData.resets?.[slug] ?? ''}${css ?? ''}`.replaceAll(
179 + slug,
180 + 'natural-1',
181 + ),
115 182 }))
116 183 .sort(() => Math.random() - 0.5);
117 184
118 185 return [...vibes, ...vibes.slice(0, 3)];
@@ -139,12 +206,12 @@
139 206 heroSectionElement?.querySelector('p')?.textContent ?? null;
140 207 const cta =
141 208 heroSectionElement?.querySelector('.wp-block-button__link') ?? null;
142 209 const heroPatternName =
143 - [...(heroSectionElement?.classList ?? [])]
144 - .find((className) => className.startsWith('ext-hero-section--'))
145 - ?.replace('ext-hero-section--', '') ?? null;
146 - const images = [...(heroSectionElement?.querySelectorAll('img') ?? [])]
210 + heroSectionElement?.className?.match(
211 + /ext-hero-section ext-hero-section--(\S+)/,
212 + )?.[1] ?? null;
213 + const domImages = [...(heroSectionElement?.querySelectorAll('img') ?? [])]
147 214 .filter(
148 215 (img) =>
149 216 !img.src.includes('.svg') && !img.src.includes('data:image/svg+xml'),
150 217 )
@@ -156,35 +223,54 @@
156 223 return img.src;
157 224 }
158 225 });
159 226
160 - apiFetch({
161 - path: '/extendify/v1/agent/site-design-variations',
162 - method: 'POST',
163 - data: {
164 - title,
165 - images,
166 - description,
167 - currentHeroPattern: heroPatternName,
168 - cta: {
169 - label: cta?.textContent,
170 - link: cta?.href,
227 + const { siteId } = window.extSharedData;
228 + const stored = safeParseJson(
229 + localStorage.getItem(`extendify-launch-data-${siteId}`),
230 + );
231 + const launchState = useUserSelectionStore.getState();
232 + const storedSiteImages =
233 + stored?.state?.siteImages ?? launchState?.siteImages?.siteImages ?? [];
234 + const siteProfile = stored?.state?.siteProfile ?? launchState?.siteProfile;
235 +
236 + (async () => {
237 + const siteImages = await resolveSiteImages({
238 + storedSiteImages,
239 + siteProfile,
240 + });
241 +
242 + apiFetch({
243 + path: '/extendify/v1/agent/site-design-variations',
244 + method: 'POST',
245 + data: {
246 + title,
247 + images: domImages,
248 + siteImages,
249 + postId: context?.postId,
250 + description,
251 + currentHeroPattern: heroPatternName,
252 + source: 'change-site-design-workflow',
253 + cta: {
254 + label: cta?.textContent,
255 + link: cta?.href,
256 + },
171 257 },
172 - },
173 - })
174 - .then((data) => {
175 - setHeroPatterns(data?.patterns?.flat() ?? []);
176 - setColorAndFontsVariations(
177 - [...(data?.colorAndFontsVariations ?? [])].sort(
178 - () => Math.random() - 0.5,
179 - ),
180 - );
258 + })
259 + .then((data) => {
260 + setHeroPatterns(data?.patterns?.flat() ?? []);
261 + setColorAndFontsVariations(
262 + [...(data?.colorAndFontsVariations ?? [])].sort(
263 + () => Math.random() - 0.5,
264 + ),
265 + );
181 266
182 - setBlockEditorStyles(data?.blockEditorSettings);
183 - })
184 - .finally(() => {
185 - setIsLoading(false);
186 - });
267 + setBlockEditorStyles(data?.blockEditorSettings);
268 + })
269 + .finally(() => {
270 + setIsLoading(false);
271 + });
272 + })();
187 273 }, []);
188 274
189 275 useEffect(() => {
190 276 if (blockEditorStyles) {
@@ -206,12 +292,21 @@
206 292
207 293 const undoChanges = () => {
208 294 undoHeroSectionChange();
209 295 undoColorAndFontsChange();
296 + undoPaletteChange();
210 297 undoVibesChange();
211 298 removeInjectedLinks();
299 + refreshBlockHighlight();
212 300 };
213 301
302 + const confirmed = useRef(false);
303 + useEffect(() => {
304 + return () => {
305 + if (!confirmed.current) undoChanges();
306 + };
307 + }, []);
308 +
214 309 const handleCancel = () => {
215 310 undoChanges();
216 311 onCancel();
217 312 };
@@ -219,8 +314,9 @@
219 314 const handleConfirm = async () => {
220 315 if (!selectedHeroPattern) return;
221 316
222 317 if (selectedHeroPattern.isCurrent) {
318 + confirmed.current = true;
223 319 onConfirm({
224 320 data: { postId: context?.postId },
225 321 shouldRefreshPage: false,
226 322 });
@@ -255,14 +351,18 @@
255 351 return parse(selectedHeroPattern.code)?.[0] || block;
256 352 }),
257 353 );
258 354
355 + confirmed.current = true;
259 356 onConfirm({
260 357 data: {
261 358 updatedPageBlocks,
262 359 postId,
263 360 vibeSlug: selectedVibe.slug,
264 - colorAndFontsVariation: selectedColorAndFonts,
361 + colorAndFontsVariation: onPalettes
362 + ? fontsOnlyVariation(selectedColorAndFonts)
363 + : selectedColorAndFonts,
364 + colorPalette: selectedPalette?.slug,
265 365 },
266 366 shouldRefreshPage: true,
267 367 });
268 368 } catch (error) {
@@ -271,18 +371,18 @@
271 371 setIsSaving(false);
272 372 }
273 373 };
274 374
275 - if (isLoading || isLoadingVibes) {
375 + if (isLoading || isLoadingVibes || isLoadingPalettes) {
276 376 return (
277 - <div className="flex justify-center flex-col gap-1">
278 - <Spinner />
377 + <div className="min-h-24 p-2 text-center text-sm">
378 + {__('Loading design options...', 'extendify-local')}
279 379 </div>
280 380 );
281 381 }
282 382
283 383 return (
284 - <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">
384 + <div className="mb-4 ms-2 me-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50">
285 385 <div className="rounded-lg border-b border-gray-300 bg-white">
286 386 <div className="flex flex-col gap-4 p-3">
287 387 {currentHeroHtml && (
288 388 <DesignOption
@@ -292,8 +392,9 @@
292 392 onClick={() => {
293 393 setSelectedHeroPattern(currentDesignOption);
294 394 setSelectedColorAndFonts(null);
295 395 setSelectedVibe(null);
396 + setSelectedPalette(null);
296 397
297 398 undoChanges();
298 399 }}
299 400 />
@@ -305,11 +406,13 @@
305 406 isSelected={selectedHeroPattern?.id === heroPattern.id}
306 407 styles={{
307 408 linkStyles: heroPattern.linkStyles,
308 409 colorAndFontsVariations: colorAndFontsVariations[i].css,
309 - duotoneTheme:
310 - colorAndFontsVariations[i]?.settings?.color?.duotone?.theme,
311 - vibes: vibes[i]?.css,
410 + paletteCss: paletteCss?.[paletteFor(i)?.slug],
411 + duotoneTheme: onPalettes
412 + ? paletteDuotone(paletteFor(i))
413 + : colorAndFontsVariations[i]?.settings?.color?.duotone?.theme,
414 + vibes: vibes[i]?.previewCss,
312 415 blockSupportsCss: heroPattern.blockSupportsCss,
313 416 }}
314 417 onClick={() => {
315 418 setSelectedHeroPattern(heroPattern);
@@ -314,8 +417,9 @@
314 417 onClick={() => {
315 418 setSelectedHeroPattern(heroPattern);
316 419 setSelectedColorAndFonts(colorAndFontsVariations[i]);
317 420 setSelectedVibe(vibes[i]);
421 + setSelectedPalette(paletteFor(i));
318 422
319 423 if (!isAdmin) {
320 424 const blockSupportsCss = heroPattern.blockSupportsCss;
321 425 if (blockSupportsCss) {
@@ -330,8 +434,9 @@
330 434
331 435 injectLinkStyles(heroPattern.linkStyles);
332 436
333 437 updateHeroSection(heroPattern.renderedHtml);
438 + refreshBlockHighlight();
334 439 }
335 440 }}
336 441 />
337 442 ))}