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 +138 -39 3.0.6 → 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';
@@ -53,8 +63,31 @@
53 63 const isAdmin = context?.adminPage;
54 64
55 65 const PAGE_SIZE = 5;
56 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 +
57 90 export const SelectSiteDesign = ({ onConfirm, onCancel }) => {
58 91 const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
59 92 const [isLoading, setIsLoading] = useState(false);
60 93 const [isSaving, setIsSaving] = useState(false);
@@ -67,8 +100,9 @@
67 100
68 101 const [selectedColorAndFonts, setSelectedColorAndFonts] = useState();
69 102 const [selectedHeroPattern, setSelectedHeroPattern] = useState();
70 103 const [selectedVibe, setSelectedVibe] = useState();
104 + const [selectedPalette, setSelectedPalette] = useState();
71 105
72 106 const { updateSettings } = useDispatch('core/block-editor');
73 107
74 108 const injectedLinksRef = useRef([]);
@@ -95,14 +129,35 @@
95 129 });
96 130 injectedLinksRef.current = [];
97 131 };
98 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 +
99 147 const { undoChange: undoColorAndFontsChange } = useVariationOverride({
100 148 css: !isAdmin && selectedColorAndFonts?.css,
101 149 duotoneTheme:
102 - !isAdmin && selectedColorAndFonts?.settings?.color?.duotone?.theme,
150 + !isAdmin &&
151 + !onPalettes &&
152 + selectedColorAndFonts?.settings?.color?.duotone?.theme,
103 153 });
104 154
155 + const { undoChange: undoPaletteChange } = usePaletteOverride({
156 + css: (!isAdmin && paletteCss?.[selectedPalette?.slug]) || '',
157 + duotoneTheme: (!isAdmin && paletteDuotone(selectedPalette)) || null,
158 + });
159 +
105 160 const { undoChange: undoVibesChange } = useSiteVibesOverride({
106 161 css: !isAdmin && selectedVibe?.css,
107 162 slug: !isAdmin && selectedVibe?.slug,
108 163 });
@@ -110,9 +165,10 @@
110 165 const { data: vibesData, isLoading: isLoadingVibes } =
111 166 useSiteVibesVariations();
112 167
113 168 const vibes = useMemo(() => {
114 - if (isLoadingVibes) return null;
169 + // Null for a theme with no block variations, which .css would throw on.
170 + if (isLoadingVibes || !vibesData?.css) return [];
115 171
116 172 const vibes = Object.entries(vibesData.css)
117 173 .filter(([slug]) => slug !== vibesData.currentVibe)
118 174 .map(([slug, css]) => ({
@@ -117,8 +173,13 @@
117 173 .filter(([slug]) => slug !== vibesData.currentVibe)
118 174 .map(([slug, css]) => ({
119 175 slug,
120 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 + ),
121 182 }))
122 183 .sort(() => Math.random() - 0.5);
123 184
124 185 return [...vibes, ...vibes.slice(0, 3)];
@@ -145,12 +206,12 @@
145 206 heroSectionElement?.querySelector('p')?.textContent ?? null;
146 207 const cta =
147 208 heroSectionElement?.querySelector('.wp-block-button__link') ?? null;
148 209 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') ?? [])]
210 + heroSectionElement?.className?.match(
211 + /ext-hero-section ext-hero-section--(\S+)/,
212 + )?.[1] ?? null;
213 + const domImages = [...(heroSectionElement?.querySelectorAll('img') ?? [])]
153 214 .filter(
154 215 (img) =>
155 216 !img.src.includes('.svg') && !img.src.includes('data:image/svg+xml'),
156 217 )
@@ -162,35 +223,54 @@
162 223 return img.src;
163 224 }
164 225 });
165 226
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,
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 + },
177 257 },
178 - },
179 - })
180 - .then((data) => {
181 - setHeroPatterns(data?.patterns?.flat() ?? []);
182 - setColorAndFontsVariations(
183 - [...(data?.colorAndFontsVariations ?? [])].sort(
184 - () => Math.random() - 0.5,
185 - ),
186 - );
258 + })
259 + .then((data) => {
260 + setHeroPatterns(data?.patterns?.flat() ?? []);
261 + setColorAndFontsVariations(
262 + [...(data?.colorAndFontsVariations ?? [])].sort(
263 + () => Math.random() - 0.5,
264 + ),
265 + );
187 266
188 - setBlockEditorStyles(data?.blockEditorSettings);
189 - })
190 - .finally(() => {
191 - setIsLoading(false);
192 - });
267 + setBlockEditorStyles(data?.blockEditorSettings);
268 + })
269 + .finally(() => {
270 + setIsLoading(false);
271 + });
272 + })();
193 273 }, []);
194 274
195 275 useEffect(() => {
196 276 if (blockEditorStyles) {
@@ -212,12 +292,21 @@
212 292
213 293 const undoChanges = () => {
214 294 undoHeroSectionChange();
215 295 undoColorAndFontsChange();
296 + undoPaletteChange();
216 297 undoVibesChange();
217 298 removeInjectedLinks();
299 + refreshBlockHighlight();
218 300 };
219 301
302 + const confirmed = useRef(false);
303 + useEffect(() => {
304 + return () => {
305 + if (!confirmed.current) undoChanges();
306 + };
307 + }, []);
308 +
220 309 const handleCancel = () => {
221 310 undoChanges();
222 311 onCancel();
223 312 };
@@ -225,8 +314,9 @@
225 314 const handleConfirm = async () => {
226 315 if (!selectedHeroPattern) return;
227 316
228 317 if (selectedHeroPattern.isCurrent) {
318 + confirmed.current = true;
229 319 onConfirm({
230 320 data: { postId: context?.postId },
231 321 shouldRefreshPage: false,
232 322 });
@@ -261,14 +351,18 @@
261 351 return parse(selectedHeroPattern.code)?.[0] || block;
262 352 }),
263 353 );
264 354
355 + confirmed.current = true;
265 356 onConfirm({
266 357 data: {
267 358 updatedPageBlocks,
268 359 postId,
269 360 vibeSlug: selectedVibe.slug,
270 - colorAndFontsVariation: selectedColorAndFonts,
361 + colorAndFontsVariation: onPalettes
362 + ? fontsOnlyVariation(selectedColorAndFonts)
363 + : selectedColorAndFonts,
364 + colorPalette: selectedPalette?.slug,
271 365 },
272 366 shouldRefreshPage: true,
273 367 });
274 368 } catch (error) {
@@ -277,18 +371,18 @@
277 371 setIsSaving(false);
278 372 }
279 373 };
280 374
281 - if (isLoading || isLoadingVibes) {
375 + if (isLoading || isLoadingVibes || isLoadingPalettes) {
282 376 return (
283 - <div className="flex justify-center flex-col gap-1">
284 - <Spinner />
377 + <div className="min-h-24 p-2 text-center text-sm">
378 + {__('Loading design options...', 'extendify-local')}
285 379 </div>
286 380 );
287 381 }
288 382
289 383 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">
384 + <div className="mb-4 ms-2 me-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50">
291 385 <div className="rounded-lg border-b border-gray-300 bg-white">
292 386 <div className="flex flex-col gap-4 p-3">
293 387 {currentHeroHtml && (
294 388 <DesignOption
@@ -298,8 +392,9 @@
298 392 onClick={() => {
299 393 setSelectedHeroPattern(currentDesignOption);
300 394 setSelectedColorAndFonts(null);
301 395 setSelectedVibe(null);
396 + setSelectedPalette(null);
302 397
303 398 undoChanges();
304 399 }}
305 400 />
@@ -311,11 +406,13 @@
311 406 isSelected={selectedHeroPattern?.id === heroPattern.id}
312 407 styles={{
313 408 linkStyles: heroPattern.linkStyles,
314 409 colorAndFontsVariations: colorAndFontsVariations[i].css,
315 - duotoneTheme:
316 - colorAndFontsVariations[i]?.settings?.color?.duotone?.theme,
317 - 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,
318 415 blockSupportsCss: heroPattern.blockSupportsCss,
319 416 }}
320 417 onClick={() => {
321 418 setSelectedHeroPattern(heroPattern);
@@ -320,8 +417,9 @@
320 417 onClick={() => {
321 418 setSelectedHeroPattern(heroPattern);
322 419 setSelectedColorAndFonts(colorAndFontsVariations[i]);
323 420 setSelectedVibe(vibes[i]);
421 + setSelectedPalette(paletteFor(i));
324 422
325 423 if (!isAdmin) {
326 424 const blockSupportsCss = heroPattern.blockSupportsCss;
327 425 if (blockSupportsCss) {
@@ -336,8 +434,9 @@
336 434
337 435 injectLinkStyles(heroPattern.linkStyles);
338 436
339 437 updateHeroSection(heroPattern.renderedHtml);
438 + refreshBlockHighlight();
340 439 }
341 440 }}
342 441 />
343 442 ))}