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
extendify / src / AutoLaunch / fetchers / get-design-build.js

get-design-build.js in Extendify trunk, at src/AutoLaunch/fetchers/get-design-build.js

155 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { isExternalLogo, uploadLogo } from '@auto-launch/fetchers/get-logo';
2 import { getStyleDocument } from '@auto-launch/fetchers/get-style-document';
3 import {
4 getDesignBuildShape,
5 getLogoShape,
6 getPluginsShape,
7 getStyleShape,
8 } from '@auto-launch/fetchers/shape';
9 import { importBuiltPagesImages } from '@auto-launch/functions/get-imported-images';
10 import {
11 fetchWithTimeout,
12 retryTwice,
13 setStatus,
14 } from '@auto-launch/functions/helpers';
15 import { updateOption } from '@auto-launch/functions/wp';
16 import { useLaunchDataStore } from '@auto-launch/state/launch-data';
17 import { launchStrings } from '@auto-launch/strings';
18 import { AI_HOST } from '@constants';
19 import { digest } from '@shared/api/digest';
20 import { mutate } from 'swr';
21
22 const fallback = null;
23 const headers = { 'Content-Type': 'application/json' };
24
25 // Left set, the launch screen waits forever on a design that never arrives.
26 const dropBuildId = () =>
27 useLaunchDataStore.setState((s) => ({
28 urlParams: { ...s.urlParams, 'build-id': '' },
29 }));
30
31 export const handleDesignBuild = async ({ urlParams }) => {
32 const buildId = urlParams?.['build-id'];
33 if (!buildId) return fallback;
34
35 // translators: this is for a action log UI. Keep it short
36 setStatus(launchStrings().statusDesign);
37
38 const url = `${AI_HOST}/api/design/${encodeURIComponent(buildId)}`;
39 const response = await retryTwice(() =>
40 fetchWithTimeout(url, { headers }),
41 ).catch((error) => {
42 return { ok: false, statusText: error.message, status: 0 };
43 });
44
45 if (!response?.ok) {
46 digest({
47 error: {
48 message: response.statusText,
49 name: 'FetchError',
50 status: response.status,
51 },
52 details: { source: 'auto-launch', caller: 'handleDesignBuild' },
53 });
54 dropBuildId();
55 return fallback;
56 }
57
58 try {
59 const parsed = getDesignBuildShape.parse(await response.json());
60
61 // Stash the site profile
62 const profile = parsed.siteProfile;
63 await updateOption('extendify_site_profile', JSON.stringify(profile));
64 mutate('siteProfile', { siteProfile: profile }, false);
65
66 const style = parsed.siteStyle;
67 const variation = await getStyleDocument({
68 colorPalette: style.colorPalette,
69 fonts: style.fonts,
70 });
71 const siteStyle = { ...style, variation };
72 // The Agent reads extendify_siteStyle; the legacy row keeps old readers.
73 await updateOption('extendify_siteStyle', siteStyle);
74 await updateOption('extendify_site_style', siteStyle);
75 await updateOption('extendify_animation_settings', {
76 type: style.animation ?? 'fade',
77 speed: 'medium',
78 });
79 mutate('siteStyle', getStyleShape.parse({ siteStyle }), false);
80
81 // Stash the plugins
82 const sitePlugins = parsed.selectedPlugins;
83 mutate('sitePlugins', getPluginsShape.parse({ sitePlugins }), false);
84
85 // The logo upload swaps logoUrl for a Media Library URL without the
86 // logos-custom/ marker, so capture "is the brand's own logo" from the
87 // source URL now.
88 const hasExternalLogo = isExternalLogo(parsed.logoUrl);
89
90 // Sideload the built-page images, and upload the logo alongside when the
91 // build provided one. logoUrl is nullable; without it we skip the upload
92 // so the siteLogo step falls through to AI logo generation.
93 const uploads = [importBuiltPagesImages(parsed.builtPages)];
94 if (parsed.logoUrl) {
95 mutate(
96 'siteLogo',
97 getLogoShape.parse({ logoUrl: parsed.logoUrl }),
98 false,
99 );
100 uploads.push(uploadLogo(parsed.logoUrl, { external: hasExternalLogo }));
101 }
102 const [builtPages] = await Promise.all(uploads);
103
104 const designBuild = {
105 buildId,
106 ...parsed,
107 builtPages,
108 hasExternalLogo,
109 siteProfile: profile,
110 siteStyle,
111 };
112 // Exclude `pages` from the spread: the pages step owns it (templates with
113 // patterns); designBuild.pages (slug/name only) would clobber it.
114 const { pages, ...topLevel } = designBuild;
115 return { designBuild, ...topLevel };
116 } catch (e) {
117 digest({
118 error: e,
119 details: { source: 'auto-launch', caller: 'handleDesignBuild::parsing' },
120 });
121 console.error('handleDesignBuild:', e);
122 dropBuildId();
123 return fallback;
124 }
125 };
126
127 // Prepend the design build's hero to the fetched home template. Full-page
128 // builds skip /api/home entirely and are assembled in handleHome.
129 export const applyDesignBuildHero = (patterns, designBuild) => {
130 const builtHome = designBuild?.builtPages?.find((p) => p.slug === 'home');
131 if (!builtHome?.patterns?.length) return patterns;
132 return [...builtHome.patterns, ...patterns];
133 };
134
135 // /api/home answers a locked nav with one section per entry, in order, and holds back
136 // the hero, so a count mismatch means that broke: leave the names to the pattern types.
137 export const applyDesignBuildNav = (patterns, designBuild, structure) => {
138 const pages = designBuild?.pages ?? [];
139 if (structure !== 'single-page' || patterns.length !== pages.length) {
140 return patterns;
141 }
142
143 return patterns.map((pattern, i) => ({ ...pattern, navSlug: pages[i].slug }));
144 };
145
146 // Reorder pages to match the design build's page order; extras fall to the end.
147 export const applyDesignBuildOrder = (pages, designBuild) => {
148 const order = designBuild?.pages?.map((p) => p.slug) ?? [];
149 if (!order.length) return pages;
150 return [
151 ...order.map((slug) => pages.find((t) => t.slug === slug)).filter(Boolean),
152 ...pages.filter((t) => !order.includes(t.slug)),
153 ];
154 };
155