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
extendify / src / Agent / workflows / theme / components / SelectGeneratedPalette.jsx

SelectGeneratedPalette.jsx in Extendify 3.2.2, at src/Agent/workflows/theme/components/SelectGeneratedPalette.jsx

189 lines 5.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { usePalettes } from '@agent/hooks/usePalettes';
2 import { useVariationOverride } from '@agent/hooks/useVariationOverride';
3 import { useChatStore } from '@agent/state/chat';
4 import { remapPaletteDuotone } from '@shared/lib/palette-preview';
5 import { useEffect, useMemo, useRef, useState } from '@wordpress/element';
6 import { __ } from '@wordpress/i18n';
7
8 const colorsArrayToMap = (colors) => {
9 const map = {};
10 colors.forEach(({ slug, color }) => {
11 map[slug] = color;
12 });
13 return map;
14 };
15
16 const getBackgroundColor = (colors) => {
17 const bgSlugs = ['base', 'background', 'bg'];
18 for (const slug of bgSlugs) {
19 if (colors[slug]) return colors[slug];
20 }
21 return '#ffffff';
22 };
23
24 const buildPreviewCss = (colorsMap) => {
25 const styleEl = document.getElementById('global-styles-inline-css');
26 if (!styleEl) return '';
27 let css = styleEl.innerHTML;
28 Object.entries(colorsMap).forEach(([slug, hex]) => {
29 const regex = new RegExp(
30 `(--wp--preset--color--${slug}\\s*:\\s*)([^;]+)(;)`,
31 'g',
32 );
33 css = css.replace(regex, `$1${hex}$3`);
34 });
35 return css;
36 };
37
38 const themeReference = () => {
39 const presets = window.extAgentData?.context?.themePresets;
40 return {
41 colors: presets?.colors,
42 settings: { color: { duotone: presets?.duotone } },
43 };
44 };
45
46 export const SelectGeneratedPalette = ({
47 inputs,
48 onConfirm,
49 onCancel,
50 onRetry,
51 }) => {
52 const [selected, setSelected] = useState(null);
53 const [previewCss, setPreviewCss] = useState('');
54 const { addMessage, messages } = useChatStore();
55 const { applied } = usePalettes();
56 const aiPalettes = inputs?.palettes;
57
58 const palettes = useMemo(
59 () =>
60 aiPalettes?.map(({ name, colors }) => {
61 return {
62 name,
63 colors: colorsArrayToMap(colors),
64 colorsArray: colors,
65 };
66 }) || [],
67 [aiPalettes],
68 );
69 const noPalettes = !aiPalettes?.length || palettes.length === 0;
70 const selectedPalette = palettes.find(({ name }) => name === selected);
71
72 // foreground-and-background is served-only; the theme never registers it.
73 const duotoneTheme = useMemo(() => {
74 if (!selectedPalette) return null;
75 const reference = applied ?? themeReference();
76 return remapPaletteDuotone(reference, selectedPalette.colors) ?? null;
77 }, [applied, selectedPalette]);
78
79 const { undoChange } = useVariationOverride({
80 css: previewCss,
81 duotoneTheme,
82 });
83
84 const confirmed = useRef(false);
85 useEffect(() => {
86 return () => {
87 if (!confirmed.current) undoChange();
88 };
89 }, []);
90
91 useEffect(() => {
92 if (!noPalettes) return;
93 const timer = setTimeout(() => onCancel(), 100);
94 const content = __(
95 'We were unable to generate color palettes. Please try again.',
96 'extendify-local',
97 );
98 const last = messages.at(-1)?.details?.content;
99 if (content === last) return () => clearTimeout(timer);
100 addMessage('message', { role: 'assistant', content, error: true });
101 return () => clearTimeout(timer);
102 }, [addMessage, onCancel, noPalettes, messages]);
103
104 const handleRetry = () => {
105 undoChange();
106 onRetry();
107 };
108
109 const handleConfirm = () => {
110 if (!selectedPalette) return;
111 confirmed.current = true;
112 onConfirm({
113 data: {
114 palette: {
115 name: selectedPalette.name,
116 colors: selectedPalette.colorsArray.map(({ slug, color }) => ({
117 slug,
118 color,
119 name: slug,
120 })),
121 },
122 duotone: duotoneTheme,
123 },
124 shouldRefreshPage: true,
125 });
126 };
127
128 if (noPalettes) return null;
129
130 return (
131 <div className="mb-4 ms-2 me-2 flex flex-col rounded-lg border border-gray-300 bg-gray-50">
132 <div className="rounded-lg border-b border-gray-300 bg-white">
133 <div className="grid grid-cols-2 gap-2 p-3">
134 {palettes.map(({ name, colors, colorsArray }) => (
135 <button
136 key={name}
137 type="button"
138 style={{ backgroundColor: getBackgroundColor(colors) }}
139 className={`relative flex w-full items-center justify-center overflow-hidden rounded-lg border border-gray-300 p-2 text-center text-sm ${
140 selected === name ? 'ring ring-design-main ring-wp' : ''
141 }`}
142 onClick={() => {
143 setSelected(name);
144 setPreviewCss(buildPreviewCss(colors));
145 }}
146 >
147 <div className="flex max-w-fit items-center justify-center -space-x-4 rounded-lg rtl:space-x-reverse">
148 {colorsArray
149 .filter(({ slug }) => slug !== 'background')
150 .map(({ slug, color }) => (
151 <div
152 key={slug}
153 style={{ backgroundColor: color }}
154 className="size-6 shrink-0 overflow-visible rounded-full border border-white md:size-7"
155 />
156 ))}
157 </div>
158 </button>
159 ))}
160 </div>
161 </div>
162 <div className="flex justify-start gap-2 p-3">
163 <button
164 type="button"
165 className="w-full rounded-sm border border-gray-500 bg-white p-2 text-sm text-gray-900"
166 onClick={onCancel}
167 >
168 {__('Cancel', 'extendify-local')}
169 </button>
170 <button
171 type="button"
172 className="w-full rounded-sm border border-gray-500 bg-white p-2 text-sm text-gray-900"
173 onClick={handleRetry}
174 >
175 {__('Try Again', 'extendify-local')}
176 </button>
177 <button
178 type="button"
179 className="w-full rounded-sm border border-design-main bg-design-main p-2 text-sm text-white"
180 disabled={!selectedPalette}
181 onClick={handleConfirm}
182 >
183 {__('Save', 'extendify-local')}
184 </button>
185 </div>
186 </div>
187 );
188 };
189