@lightspeed/crane 1.4.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/UPGRADE.md +19 -0
- package/dist/app.d.mts +1 -1028
- package/dist/app.d.ts +1 -1028
- package/dist/app.mjs +1 -1
- package/dist/cli.mjs +20 -7
- package/package.json +3 -2
- package/template/footers/example-footer/ExampleFooter.vue +1 -1
- package/template/footers/example-footer/client.ts +1 -1
- package/template/footers/example-footer/component/LegalLinks.vue +1 -1
- package/template/footers/example-footer/component/MadeWith.vue +1 -1
- package/template/footers/example-footer/component/ReportAbuse.vue +1 -1
- package/template/footers/example-footer/entity/color.ts +2 -2
- package/template/footers/example-footer/server.ts +1 -1
- package/template/headers/example-header/client.ts +1 -1
- package/template/headers/example-header/component/Account.vue +1 -1
- package/template/headers/example-header/component/Cart.vue +1 -1
- package/template/headers/example-header/component/CategoriesDropdown.vue +1 -1
- package/template/headers/example-header/component/Logo.vue +1 -1
- package/template/headers/example-header/component/NavigationMenu.vue +1 -1
- package/template/headers/example-header/component/SearchForm.vue +1 -1
- package/template/headers/example-header/server.ts +1 -1
- package/template/index.d.ts +1 -1
- package/template/layouts/catalog/example-catalog/Main.vue +1 -1
- package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/client.ts +1 -1
- package/template/layouts/catalog/example-catalog/slots/custom-bottom-bar/server.ts +1 -1
- package/template/layouts/category/example-category/Main.vue +1 -1
- package/template/layouts/category/example-category/settings/content.ts +1 -1
- package/template/layouts/category/example-category/settings/design.ts +1 -1
- package/template/layouts/product/example-product/Main.vue +1 -1
- package/template/layouts/product/example-product/settings/content.ts +1 -1
- package/template/layouts/product/example-product/settings/design.ts +1 -1
- package/template/package.json +6 -3
- package/template/page-templates/example-template/pages/catalog.ts +1 -1
- package/template/page-templates/example-template/pages/category.ts +1 -1
- package/template/page-templates/example-template/pages/product.ts +1 -1
- package/template/preview/sections/preview.html +1 -1
- package/template/preview/shared/api-routes.ts +347 -39
- package/template/preview/shared/mock.ts +43 -41
- package/template/preview/shared/preview.ts +205 -126
- package/template/preview/shared/utils.ts +208 -62
- package/template/preview/ssr-server.ts +429 -0
- package/template/preview/vite.config.js +64 -65
- package/template/reference/sections/about-us/AboutUs.vue +1 -1
- package/template/reference/sections/about-us/client.ts +1 -1
- package/template/reference/sections/about-us/component/Image.vue +1 -1
- package/template/reference/sections/about-us/component/Stats.vue +2 -2
- package/template/reference/sections/about-us/component/Title.vue +1 -1
- package/template/reference/sections/about-us/server.ts +1 -1
- package/template/reference/sections/about-us/util/visibility-provider.ts +1 -1
- package/template/reference/sections/featured-products/FeaturedProducts.vue +65 -0
- package/template/reference/sections/featured-products/assets/arrow.svg +3 -0
- package/template/reference/sections/featured-products/assets/custom_section_showcase_1_preview.png +0 -0
- package/template/reference/sections/featured-products/client.ts +5 -0
- package/template/reference/sections/featured-products/component/ProductItem.vue +71 -0
- package/template/reference/sections/featured-products/component/Title.vue +31 -0
- package/template/reference/sections/featured-products/entity/color.ts +4 -0
- package/template/reference/sections/featured-products/server.ts +5 -0
- package/template/reference/sections/featured-products/settings/content.ts +14 -0
- package/template/reference/sections/featured-products/settings/design.ts +33 -0
- package/template/reference/sections/featured-products/settings/translations.ts +24 -0
- package/template/reference/sections/featured-products/showcases/1.ts +28 -0
- package/template/reference/sections/featured-products/showcases/translations.ts +16 -0
- package/template/reference/sections/featured-products/type.ts +5 -0
- package/template/reference/sections/intro-slider/IntroSlider.vue +1 -1
- package/template/reference/sections/intro-slider/client.ts +1 -1
- package/template/reference/sections/intro-slider/component/Slider.vue +8 -2
- package/template/reference/sections/intro-slider/component/Title.vue +1 -1
- package/template/reference/sections/intro-slider/entity/color.ts +2 -2
- package/template/reference/sections/intro-slider/server.ts +1 -1
- package/template/reference/sections/tag-lines/TagLines.vue +1 -1
- package/template/reference/sections/tag-lines/client.ts +1 -1
- package/template/reference/sections/tag-lines/component/SectionImage.vue +1 -1
- package/template/reference/sections/tag-lines/component/Title.vue +1 -1
- package/template/reference/sections/tag-lines/composables/highlighted-text-image-list.ts +2 -2
- package/template/reference/sections/tag-lines/server.ts +1 -1
- package/template/reference/sections/trending-categories/TrendingCategories.vue +70 -0
- package/template/reference/sections/trending-categories/assets/arrow.svg +3 -0
- package/template/reference/sections/trending-categories/assets/custom_section_showcase_1_preview.png +0 -0
- package/template/reference/sections/trending-categories/client.ts +5 -0
- package/template/reference/sections/trending-categories/component/CategoryItem.vue +62 -0
- package/template/reference/sections/trending-categories/component/Title.vue +32 -0
- package/template/reference/sections/trending-categories/entity/color.ts +4 -0
- package/template/reference/sections/trending-categories/server.ts +5 -0
- package/template/reference/sections/trending-categories/settings/content.ts +14 -0
- package/template/reference/sections/trending-categories/settings/design.ts +33 -0
- package/template/reference/sections/trending-categories/settings/translations.ts +24 -0
- package/template/reference/sections/trending-categories/showcases/1.ts +36 -0
- package/template/reference/sections/trending-categories/showcases/translations.ts +22 -0
- package/template/reference/sections/trending-categories/type.ts +5 -0
- package/template/reference/shared/components/Button.vue +1 -1
- package/template/reference/templates/reference-template-apparel/pages/catalog.ts +1 -1
- package/template/reference/templates/reference-template-apparel/pages/category.ts +1 -1
- package/template/reference/templates/reference-template-apparel/pages/home.ts +10 -0
- package/template/reference/templates/reference-template-apparel/pages/product.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/catalog.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/category.ts +1 -1
- package/template/reference/templates/reference-template-bike/pages/home.ts +10 -0
- package/template/reference/templates/reference-template-bike/pages/product.ts +1 -1
- package/template/sections/example-section/ExampleSection.vue +8 -1
- package/template/sections/example-section/client.ts +1 -1
- package/template/sections/example-section/component/button/Button.vue +1 -1
- package/template/sections/example-section/component/image/Image.vue +1 -1
- package/template/sections/example-section/component/image/ImagesGrid.vue +1 -1
- package/template/sections/example-section/component/selectbox/Selectbox.vue +1 -1
- package/template/sections/example-section/component/title/Title.vue +1 -1
- package/template/sections/example-section/component/toggle/Toggle.vue +1 -1
- package/template/sections/example-section/entity/color.ts +2 -2
- package/template/sections/example-section/server.ts +1 -1
- package/template/sections/example-section/settings/translations.ts +1 -1
- package/template/sections/example-section/showcases/translations.ts +13 -13
- package/template/shared/components/LanguageSelector.vue +1 -1
- package/template/shared/translation.ts +16 -0
- package/template/shared/utils.ts +3 -1
- package/template/tsconfig.json +1 -0
- package/types.d.ts +6 -457
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
/* eslint-disable no-console */
|
|
2
1
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
3
|
-
import { loadModule} from
|
|
4
|
-
import { getExternalContentMock } from
|
|
2
|
+
import { loadModule, renderServerModule } from './utils';
|
|
3
|
+
import { getExternalContentMock } from './mock';
|
|
5
4
|
|
|
6
5
|
let distFolderPath: string | null = null;
|
|
7
6
|
let currentApp: any = null;
|
|
@@ -10,10 +9,29 @@ export function setDistFolderPath(path: string): void {
|
|
|
10
9
|
distFolderPath = path;
|
|
11
10
|
}
|
|
12
11
|
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Module Loading Helpers
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Loads all required modules for a showcase.
|
|
18
|
+
*/
|
|
19
|
+
async function loadShowcaseModules(sectionName: string, showcaseId: string, distFolder: string): Promise<[any, any, any, any, any]> {
|
|
20
|
+
const basePath = `${distFolder}/sections/${sectionName}`;
|
|
21
|
+
return Promise.all([
|
|
22
|
+
loadModule(`${basePath}/js/settings/content.mjs`),
|
|
23
|
+
loadModule(`${basePath}/js/settings/translations.mjs`),
|
|
24
|
+
loadModule(`${basePath}/js/showcases/translations.mjs`),
|
|
25
|
+
loadModule(`${basePath}/js/showcases/${showcaseId}.mjs`),
|
|
26
|
+
loadModule(`${basePath}/js/settings/design.mjs`),
|
|
27
|
+
]) as Promise<[any, any, any, any, any]>;
|
|
28
|
+
}
|
|
29
|
+
|
|
13
30
|
interface HSLColor {
|
|
14
31
|
h: number;
|
|
15
32
|
s: number;
|
|
16
33
|
l: number;
|
|
34
|
+
a: number;
|
|
17
35
|
}
|
|
18
36
|
|
|
19
37
|
interface RGBAColor {
|
|
@@ -27,12 +45,14 @@ interface ColorObject {
|
|
|
27
45
|
hex: string;
|
|
28
46
|
hsl: HSLColor;
|
|
29
47
|
rgba: RGBAColor;
|
|
48
|
+
auto: boolean;
|
|
49
|
+
raw: string;
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
function hexToColorObject(hex: string): ColorObject {
|
|
33
53
|
// Support both 6-digit (#RRGGBB) and 8-digit (#RRGGBBAA) hex colors
|
|
34
54
|
const match = /^#?([0-9a-fA-F]{6}([0-9a-fA-F]{2})?)$/.exec(hex);
|
|
35
|
-
if (!match) throw new Error(
|
|
55
|
+
if (!match) throw new Error('Invalid hex color format');
|
|
36
56
|
const cleanHex = match[1].toLowerCase();
|
|
37
57
|
const r = parseInt(cleanHex.substring(0, 2), 16);
|
|
38
58
|
const g = parseInt(cleanHex.substring(2, 4), 16);
|
|
@@ -42,21 +62,38 @@ function hexToColorObject(hex: string): ColorObject {
|
|
|
42
62
|
const rNorm = r / 255, gNorm = g / 255, bNorm = b / 255;
|
|
43
63
|
const max = Math.max(rNorm, gNorm, bNorm), min = Math.min(rNorm, gNorm, bNorm);
|
|
44
64
|
const delta = max - min;
|
|
45
|
-
let h = 0, s = 0
|
|
65
|
+
let h = 0, s = 0;
|
|
66
|
+
const l = (max + min) / 2;
|
|
46
67
|
if (delta !== 0) {
|
|
47
68
|
s = delta / (1 - Math.abs(2 * l - 1));
|
|
48
69
|
switch (max) {
|
|
49
|
-
case rNorm:
|
|
50
|
-
|
|
51
|
-
|
|
70
|
+
case rNorm: {
|
|
71
|
+
h = ((gNorm - bNorm) / delta) % 6;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case gNorm: {
|
|
75
|
+
h = (bNorm - rNorm) / delta + 2;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
case bNorm: {
|
|
79
|
+
h = (rNorm - gNorm) / delta + 4;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
52
82
|
}
|
|
53
83
|
h *= 60;
|
|
54
84
|
if (h < 0) h += 360;
|
|
55
85
|
}
|
|
56
86
|
return {
|
|
57
87
|
hex: `#${cleanHex.substring(0, 6)}${a.toString(16).padStart(2, '0')}`,
|
|
58
|
-
hsl: {
|
|
88
|
+
hsl: {
|
|
89
|
+
h: +(h / 360).toFixed(4),
|
|
90
|
+
s: +s.toFixed(4),
|
|
91
|
+
l: +l.toFixed(4),
|
|
92
|
+
a: +(a / 255).toFixed(2),
|
|
93
|
+
},
|
|
59
94
|
rgba: { r, g, b, a: +(a / 255).toFixed(2) },
|
|
95
|
+
auto: false,
|
|
96
|
+
raw: `#${cleanHex.substring(0, 6).toUpperCase()}`,
|
|
60
97
|
};
|
|
61
98
|
}
|
|
62
99
|
|
|
@@ -73,7 +110,7 @@ function updateHexColors(obj: any): any {
|
|
|
73
110
|
// If it’s a 3-digit hex, expand it to 6 digits
|
|
74
111
|
const expanded = v.replace(
|
|
75
112
|
/^#?([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])$/,
|
|
76
|
-
(_m, r, g, b) => `#${r}${r}${g}${g}${b}${b}
|
|
113
|
+
(_m, r, g, b) => `#${r}${r}${g}${g}${b}${b}`,
|
|
77
114
|
);
|
|
78
115
|
value[key] = hexToColorObject(expanded);
|
|
79
116
|
} else {
|
|
@@ -96,75 +133,68 @@ const replaceGlobalFont = (obj: any, font: string): void => {
|
|
|
96
133
|
});
|
|
97
134
|
};
|
|
98
135
|
|
|
99
|
-
function
|
|
136
|
+
function createBackgroundObject(type: string, fromColor: any, toColor: any): any {
|
|
100
137
|
return {
|
|
101
138
|
background: {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
139
|
+
type: type,
|
|
140
|
+
solid: {
|
|
141
|
+
color: fromColor,
|
|
105
142
|
},
|
|
143
|
+
gradient: { fromColor: fromColor, toColor: toColor },
|
|
106
144
|
},
|
|
107
145
|
};
|
|
108
146
|
}
|
|
109
147
|
|
|
110
|
-
|
|
111
|
-
|
|
148
|
+
function createBackgroundDesign(showcaseBackground: any, isApiRender: boolean = false): any {
|
|
149
|
+
// Helper to check if color is global reference
|
|
150
|
+
const isGlobalColor = (color: string) => typeof color === 'string' && color.startsWith('global.');
|
|
112
151
|
const defaultGrayColor = hexToColorObject('#F9F9F9');
|
|
113
|
-
return createBackgroundStructure('solid', {
|
|
114
|
-
solid: { color: defaultGrayColor },
|
|
115
|
-
color: 'global.color.background',
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Helper function to check if color is global reference
|
|
120
|
-
function isGlobalColor(color: string): boolean {
|
|
121
|
-
return typeof color === 'string' && color.startsWith('global.');
|
|
122
|
-
}
|
|
123
152
|
|
|
124
|
-
function createBackgroundDesign(showcaseBackground: any): any {
|
|
125
153
|
// No background or invalid background
|
|
126
154
|
if (!showcaseBackground) {
|
|
127
|
-
|
|
155
|
+
const defaultColor = 'global.color.background';
|
|
156
|
+
if (isApiRender) {
|
|
157
|
+
// API render: keep global as string
|
|
158
|
+
return createBackgroundObject('solid', defaultColor, defaultColor);
|
|
159
|
+
} else {
|
|
160
|
+
// Local preview: convert to color object
|
|
161
|
+
return createBackgroundObject('solid', defaultGrayColor, defaultGrayColor);
|
|
162
|
+
}
|
|
128
163
|
}
|
|
129
164
|
|
|
130
165
|
// Handle gradient backgrounds
|
|
131
166
|
if (showcaseBackground.style === 'GRADIENT' && Array.isArray(showcaseBackground.color)) {
|
|
132
167
|
const [fromColor, toColor] = showcaseBackground.color;
|
|
133
168
|
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
169
|
+
// Check if colors are global references
|
|
170
|
+
const fromIsGlobal = isGlobalColor(fromColor);
|
|
171
|
+
const toIsGlobal = isGlobalColor(toColor);
|
|
172
|
+
if (isApiRender) {
|
|
173
|
+
// Convert each color: keep global as string, convert hex to object
|
|
174
|
+
const fromColorValue = fromIsGlobal ? fromColor : hexToColorObject(fromColor);
|
|
175
|
+
const toColorValue = toIsGlobal ? toColor : hexToColorObject(toColor);
|
|
176
|
+
return createBackgroundObject('gradient', fromColorValue, toColorValue);
|
|
177
|
+
} else {
|
|
178
|
+
const fromColorValue = fromIsGlobal ? defaultGrayColor : hexToColorObject(fromColor);
|
|
179
|
+
const toColorValue = toIsGlobal ? defaultGrayColor : hexToColorObject(toColor);
|
|
180
|
+
return createBackgroundObject('gradient', fromColorValue, toColorValue);
|
|
137
181
|
}
|
|
138
|
-
|
|
139
|
-
// Create gradient background
|
|
140
|
-
return createBackgroundStructure('gradient', {
|
|
141
|
-
solid: { color: hexToColorObject(fromColor) },
|
|
142
|
-
gradient: {
|
|
143
|
-
fromColor: hexToColorObject(fromColor),
|
|
144
|
-
toColor: hexToColorObject(toColor),
|
|
145
|
-
},
|
|
146
|
-
color: `gradient(${fromColor}, ${toColor})`,
|
|
147
|
-
});
|
|
148
182
|
}
|
|
149
183
|
|
|
150
184
|
// Handle solid color backgrounds
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
solid
|
|
162
|
-
|
|
163
|
-
});
|
|
185
|
+
if (showcaseBackground.style === 'COLOR') {
|
|
186
|
+
// According to source code array is also possible
|
|
187
|
+
const showcaseColor = showcaseBackground.color;
|
|
188
|
+
const solidColor = Array.isArray(showcaseColor) ? showcaseColor[0] : showcaseColor;
|
|
189
|
+
if (isApiRender) {
|
|
190
|
+
const finalColor = isGlobalColor(solidColor) ? solidColor : hexToColorObject(solidColor);
|
|
191
|
+
return createBackgroundObject('solid', finalColor, finalColor);
|
|
192
|
+
} else {
|
|
193
|
+
// Hex color - convert to color object
|
|
194
|
+
const finalColor = isGlobalColor(solidColor) ? defaultGrayColor : hexToColorObject(solidColor);
|
|
195
|
+
return createBackgroundObject('solid', finalColor, finalColor);
|
|
196
|
+
}
|
|
164
197
|
}
|
|
165
|
-
|
|
166
|
-
// Fallback to default
|
|
167
|
-
return createDefaultBackground();
|
|
168
198
|
}
|
|
169
199
|
|
|
170
200
|
function overrideSettingsFromShowcase(content: any, showcases: any): any {
|
|
@@ -173,7 +203,7 @@ function overrideSettingsFromShowcase(content: any, showcases: any): any {
|
|
|
173
203
|
|
|
174
204
|
export function mergeDesign(
|
|
175
205
|
design: Record<string, any>,
|
|
176
|
-
showcase: Record<string, any
|
|
206
|
+
showcase: Record<string, any>,
|
|
177
207
|
): Record<string, any> {
|
|
178
208
|
const result: Record<string, any> = { ...design };
|
|
179
209
|
|
|
@@ -183,9 +213,9 @@ export function mergeDesign(
|
|
|
183
213
|
const override = showcase[key];
|
|
184
214
|
|
|
185
215
|
if (
|
|
186
|
-
base !== null && override !== null
|
|
187
|
-
typeof base === 'object' && typeof override === 'object'
|
|
188
|
-
!Array.isArray(base) && !Array.isArray(override)
|
|
216
|
+
base !== null && override !== null
|
|
217
|
+
&& typeof base === 'object' && typeof override === 'object'
|
|
218
|
+
&& !Array.isArray(base) && !Array.isArray(override)
|
|
189
219
|
) {
|
|
190
220
|
// shallow merge nested objects
|
|
191
221
|
result[key] = { ...base, ...override };
|
|
@@ -199,20 +229,36 @@ export function mergeDesign(
|
|
|
199
229
|
return result;
|
|
200
230
|
}
|
|
201
231
|
|
|
202
|
-
export function designTransformer(design: Record<string, any>, showCaseDesign: Record<string, any>
|
|
232
|
+
export function designTransformer(design: Record<string, any>, showCaseDesign: Record<string, any>): Record<string, any> {
|
|
203
233
|
const parsedDesign: Record<string, any> = {};
|
|
204
234
|
const parsedShowcaseDesign: Record<string, any> = {};
|
|
205
235
|
Object.entries(design).forEach(([key, comp]) => {
|
|
206
|
-
|
|
236
|
+
// Skip DIVIDER elements - they're UI-only and don't have runtime data
|
|
237
|
+
if (comp?.type === 'DIVIDER') {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
// Skip BACKGROUND - it's handled separately by createBackgroundDesign
|
|
241
|
+
if (comp?.type === 'BACKGROUND') {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Use defaults if available, otherwise use empty object
|
|
245
|
+
parsedDesign[key] = comp?.defaults || {};
|
|
207
246
|
});
|
|
208
247
|
|
|
209
248
|
Object.entries(showCaseDesign).forEach(([key, comp]) => {
|
|
210
|
-
//
|
|
249
|
+
// Skip DIVIDER elements - they're UI-only and don't have runtime data
|
|
250
|
+
if (comp?.type === 'DIVIDER') {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
// Skip BACKGROUND - it's handled separately by createBackgroundDesign
|
|
254
|
+
if (comp?.type === 'BACKGROUND') {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
211
257
|
const { type, ...withoutType } = comp;
|
|
212
|
-
parsedShowcaseDesign[key] = type == 'TEXT' ? { ...
|
|
258
|
+
parsedShowcaseDesign[key] = type == 'TEXT' ? { ...withoutType, visible: true } : withoutType;
|
|
213
259
|
});
|
|
214
260
|
|
|
215
|
-
let overridenDesign = mergeDesign(parsedDesign, parsedShowcaseDesign)
|
|
261
|
+
let overridenDesign = mergeDesign(parsedDesign, parsedShowcaseDesign);
|
|
216
262
|
overridenDesign = updateHexColors(overridenDesign);
|
|
217
263
|
replaceGlobalFont(overridenDesign, 'Roboto');
|
|
218
264
|
return overridenDesign;
|
|
@@ -231,13 +277,13 @@ function processImage(component: any, sectionName: string, key: string): Record<
|
|
|
231
277
|
[key]: {
|
|
232
278
|
set: newSet,
|
|
233
279
|
...(component.imageData?.borderInfo && { borderInfo: component.imageData.borderInfo }),
|
|
234
|
-
...(component.imageData && { bucket: {} })
|
|
280
|
+
...(component.imageData && { bucket: {} }),
|
|
235
281
|
},
|
|
236
282
|
};
|
|
237
283
|
}
|
|
238
284
|
|
|
239
285
|
function processComponent(key: string, component: any, translations: any, sectionName?: string): any {
|
|
240
|
-
if (!component?.type) return
|
|
286
|
+
if (!component?.type) return {};
|
|
241
287
|
switch (component.type) {
|
|
242
288
|
case 'INPUTBOX':
|
|
243
289
|
case 'TEXTAREA': {
|
|
@@ -281,9 +327,19 @@ function processComponent(key: string, component: any, translations: any, sectio
|
|
|
281
327
|
return { [key]: component.defaults.value };
|
|
282
328
|
case 'IMAGE':
|
|
283
329
|
return processImage(component, sectionName!, key);
|
|
330
|
+
case 'INFO':
|
|
331
|
+
return {
|
|
332
|
+
[key]: {
|
|
333
|
+
label: translations[component.label],
|
|
334
|
+
description: translations[component.description],
|
|
335
|
+
button: {
|
|
336
|
+
label: translations[component.button.label],
|
|
337
|
+
link: component.button.link,
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
};
|
|
284
341
|
default:
|
|
285
|
-
|
|
286
|
-
return '';
|
|
342
|
+
return {};
|
|
287
343
|
}
|
|
288
344
|
}
|
|
289
345
|
|
|
@@ -292,7 +348,7 @@ function getContentToRender(
|
|
|
292
348
|
showcase: any,
|
|
293
349
|
contentTranslations: any,
|
|
294
350
|
showcaseTranslations: any,
|
|
295
|
-
sectionName: string
|
|
351
|
+
sectionName: string,
|
|
296
352
|
): any {
|
|
297
353
|
const parsedContent = Object.entries(content).reduce((acc, [k, c]) => {
|
|
298
354
|
return { ...acc, ...processComponent(k, c, contentTranslations, sectionName) };
|
|
@@ -306,7 +362,7 @@ function getContentToRender(
|
|
|
306
362
|
}
|
|
307
363
|
|
|
308
364
|
export function dropdownOptions(showcaseModules: Record<string, any>): Array<{ value: string; label: string }> {
|
|
309
|
-
return Object.keys(showcaseModules).map(path => {
|
|
365
|
+
return Object.keys(showcaseModules).map((path) => {
|
|
310
366
|
const match = path.match(/\/sections\/([^/]+)\/js\/showcases\/(\d+)\.mjs$/);
|
|
311
367
|
if (!match) return null;
|
|
312
368
|
return {
|
|
@@ -323,89 +379,112 @@ export function loadSectionCss(sectionName: string): void {
|
|
|
323
379
|
document.head.appendChild(link);
|
|
324
380
|
}
|
|
325
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Renders a showcase in the browser (client-side).
|
|
384
|
+
*/
|
|
326
385
|
export async function renderShowcase(sectionName: string, showcaseId: string): Promise<void> {
|
|
327
|
-
|
|
328
|
-
const content = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/content.mjs`);
|
|
329
|
-
const contentTranslations = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/translations.mjs`);
|
|
330
|
-
const showcaseTranslations = await loadModule(`${distFolderPath}/sections/${sectionName}/js/showcases/translations.mjs`);
|
|
331
|
-
const showcase = await loadModule(`${distFolderPath}/sections/${sectionName}/js/showcases/${showcaseId}.mjs`);
|
|
332
|
-
const design = await loadModule(`${distFolderPath}/sections/${sectionName}/js/settings/design.mjs`);
|
|
386
|
+
if (!distFolderPath) throw new Error('distFolderPath not set');
|
|
333
387
|
|
|
334
|
-
|
|
335
|
-
const
|
|
336
|
-
|
|
388
|
+
const basePath = `${distFolderPath}/sections/${sectionName}`;
|
|
389
|
+
const [content, contentTranslations, showcaseTranslations, showcase, design]
|
|
390
|
+
= await loadShowcaseModules(sectionName, showcaseId, distFolderPath);
|
|
337
391
|
|
|
338
|
-
const
|
|
392
|
+
const client = await loadModule(`${basePath}/js/main/client/client.js`) as any;
|
|
339
393
|
|
|
340
|
-
|
|
394
|
+
// Prepare design
|
|
395
|
+
const showcaseBackground = (showcase as any).default?.design?.background;
|
|
396
|
+
const backgroundDesign = createBackgroundDesign(showcaseBackground, false); // Local preview
|
|
397
|
+
const overriddenDesign = designTransformer((design as any).default, (showcase as any).default.design || {});
|
|
398
|
+
overriddenDesign.background = backgroundDesign;
|
|
341
399
|
|
|
400
|
+
// Prepare content
|
|
342
401
|
const overriddenContent = getContentToRender(
|
|
343
|
-
content.default,
|
|
344
|
-
showcase.default.content || {},
|
|
345
|
-
contentTranslations.default.en,
|
|
346
|
-
showcaseTranslations.default.en,
|
|
347
|
-
sectionName
|
|
402
|
+
(content as any).default,
|
|
403
|
+
(showcase as any).default.content || {},
|
|
404
|
+
(contentTranslations as any).default.en,
|
|
405
|
+
(showcaseTranslations as any).default.en,
|
|
406
|
+
sectionName,
|
|
348
407
|
);
|
|
349
408
|
|
|
350
|
-
//
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
// Ensure background design always overrides any existing background
|
|
354
|
-
const finalDesign = { ...ovveridenDesign };
|
|
355
|
-
finalDesign.background = backgroundDesign.background;
|
|
409
|
+
// Load CSS
|
|
410
|
+
loadSectionCss(sectionName);
|
|
356
411
|
|
|
412
|
+
// Prepare state
|
|
357
413
|
const state = {
|
|
358
|
-
context: {
|
|
359
|
-
globalDesign: { color: 'global.color.background' },
|
|
360
|
-
},
|
|
414
|
+
context: { globalDesign: { color: 'global.color.background' } },
|
|
361
415
|
data: {
|
|
362
416
|
content: overriddenContent,
|
|
363
|
-
design:
|
|
417
|
+
design: overriddenDesign,
|
|
364
418
|
defaults: {},
|
|
365
419
|
background: {},
|
|
366
|
-
externalContent:
|
|
420
|
+
externalContent: getExternalContentMock(),
|
|
367
421
|
},
|
|
368
422
|
};
|
|
369
423
|
|
|
370
|
-
//
|
|
424
|
+
// Mount or update app
|
|
371
425
|
if (currentApp) {
|
|
372
426
|
currentApp.update(state);
|
|
373
427
|
} else {
|
|
374
|
-
const { mount, update, unmount } = client.default.init();
|
|
428
|
+
const { mount, update, unmount } = (client as any).default.init();
|
|
375
429
|
currentApp = { update, unmount };
|
|
376
430
|
mount('#app', state);
|
|
377
431
|
}
|
|
378
432
|
}
|
|
379
433
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
const
|
|
392
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Prepares showcase data (content and design) without rendering.
|
|
436
|
+
* Used by API routes to return tile data.
|
|
437
|
+
*/
|
|
438
|
+
export async function getShowcaseData(sectionName: string, showcaseId: string, distFolder: string) {
|
|
439
|
+
const [content, contentTranslations, showcaseTranslations, showcase, design]
|
|
440
|
+
= await loadShowcaseModules(sectionName, showcaseId, distFolder);
|
|
441
|
+
|
|
442
|
+
// Prepare design
|
|
443
|
+
const showcaseBackground = (showcase as any).default?.design?.background;
|
|
444
|
+
const backgroundDesign = createBackgroundDesign(showcaseBackground, true); // API render
|
|
445
|
+
const overriddenDesign = designTransformer((design as any).default, (showcase as any).default.design || {});
|
|
446
|
+
overriddenDesign.background = backgroundDesign;
|
|
447
|
+
overriddenDesign.layout = (showcase as any).default?.layoutId;
|
|
448
|
+
|
|
449
|
+
// Prepare content
|
|
393
450
|
const overriddenContent = getContentToRender(
|
|
394
|
-
content.default,
|
|
395
|
-
showcase.default.content || {},
|
|
396
|
-
contentTranslations.default.en,
|
|
397
|
-
showcaseTranslations.default.en,
|
|
398
|
-
sectionName
|
|
451
|
+
(content as any).default,
|
|
452
|
+
(showcase as any).default.content || {},
|
|
453
|
+
(contentTranslations as any).default.en,
|
|
454
|
+
(showcaseTranslations as any).default.en,
|
|
455
|
+
sectionName,
|
|
399
456
|
);
|
|
400
457
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
458
|
+
return {
|
|
459
|
+
content: overriddenContent,
|
|
460
|
+
design: overriddenDesign,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
404
463
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
464
|
+
/**
|
|
465
|
+
* Gets the showcase state by rendering server-side.
|
|
466
|
+
* Uses renderServerModule to get HTML directly from the SSR server.
|
|
467
|
+
*/
|
|
468
|
+
export async function getShowcaseState(sectionName: string, showcaseId: string, distFolder: string, context: Record<string, any>) {
|
|
469
|
+
const { content: overriddenContent, design: overriddenDesign }
|
|
470
|
+
= await getShowcaseData(sectionName, showcaseId, distFolder);
|
|
471
|
+
|
|
472
|
+
const data = {
|
|
473
|
+
content: overriddenContent,
|
|
474
|
+
design: overriddenDesign,
|
|
475
|
+
defaults: {},
|
|
476
|
+
background: {},
|
|
477
|
+
externalContent: getExternalContentMock(),
|
|
408
478
|
};
|
|
409
479
|
|
|
410
|
-
|
|
480
|
+
// Render server-side using the SSR server
|
|
481
|
+
const basePath = `${distFolder}/sections/${sectionName}`;
|
|
482
|
+
const serverModulePath = `${basePath}/js/main/server/server.js`;
|
|
483
|
+
const html = await renderServerModule(serverModulePath, context, data);
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
content: overriddenContent,
|
|
487
|
+
design: overriddenDesign,
|
|
488
|
+
html,
|
|
489
|
+
};
|
|
411
490
|
}
|