@developer_tribe/react-builder 1.2.40 → 1.2.42
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/dist/attributes-editor/FallbackLocalizationField.d.ts +6 -0
- package/dist/build-components/PaywallFooter/PaywallFooter.d.ts +5 -0
- package/dist/build-components/PaywallFooter/PaywallFooterProps.generated.d.ts +68 -0
- package/dist/build-components/index.d.ts +2 -1
- package/dist/build-components/patterns.generated.d.ts +495 -3
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.web.cjs.js +4 -4
- package/dist/index.web.cjs.js.map +1 -1
- package/dist/index.web.d.ts +8 -0
- package/dist/index.web.esm.js +4 -4
- package/dist/index.web.esm.js.map +1 -1
- package/dist/modals/IconPickerModal.d.ts +1 -1
- package/dist/product-base/types.d.ts +3 -0
- package/dist/store.d.ts +25 -0
- package/dist/styles.css +1 -1
- package/dist/types/PreviewConfig.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/public/bin.js +0 -0
- package/src/RenderPage.tsx +1 -1
- package/src/assets/meta.json +1 -1
- package/src/assets/prompt-scheme-onboard.generated.ts +1 -1
- package/src/assets/prompt-scheme-paywall.generated.ts +1 -1
- package/src/assets/samples/paywall-1.json +18 -1
- package/src/assets/samples/paywall-2.json +2 -2
- package/src/assets/samples/paywall-app-delete-offer.json +2 -2
- package/src/assets/samples/paywall-app-open-offer.json +2 -2
- package/src/assets/samples/paywall-back-offer.json +1 -1
- package/src/assets/samples/paywall-notification-offer.json +1 -1
- package/src/assets/samples/vpn-onboard-1.json +3 -3
- package/src/assets/samples/vpn-onboard-2.json +3 -3
- package/src/assets/samples/vpn-onboard-3.json +3 -3
- package/src/assets/samples/vpn-onboard-4.json +3 -3
- package/src/assets/samples/vpn-onboard-5.json +3 -3
- package/src/assets/samples/vpn-onboard-6.json +3 -3
- package/src/assets/samples/vpn-onboard-7.json +3 -3
- package/src/attributes-editor/AttributesEditorFields.tsx +1 -1
- package/src/attributes-editor/AttributesEditorView.tsx +17 -6
- package/src/attributes-editor/FallbackLocalizationField.tsx +384 -0
- package/src/build-components/BIcon/BIcon.tsx +1 -1
- package/src/build-components/OnboardButton/OnboardButton.tsx +1 -1
- package/src/build-components/OnboardButton/pattern.json +1 -1
- package/src/build-components/OnboardFooter/OnboardFooter.tsx +29 -20
- package/src/build-components/OnboardFooter/pattern.json +2 -1
- package/src/build-components/OnboardProvider/pattern.json +1 -1
- package/src/build-components/PaywallCloseButton/PaywallCloseButton.tsx +1 -1
- package/src/build-components/PaywallFooter/PaywallFooter.tsx +242 -0
- package/src/build-components/PaywallFooter/PaywallFooterProps.generated.ts +85 -0
- package/src/build-components/PaywallFooter/pattern.json +86 -0
- package/src/build-components/RenderNode.generated.tsx +5 -0
- package/src/build-components/index.ts +5 -0
- package/src/build-components/patterns.generated.ts +511 -3
- package/src/components/BottomBar.tsx +136 -32
- package/src/components/DeviceNavigationBar.tsx +2 -2
- package/src/hooks/useLocalize.ts +13 -1
- package/src/index.web.ts +19 -0
- package/src/mockOS/managers/mockPermissionManager.ts +5 -3
- package/src/mockOS/managers/navigationManager.ts +6 -4
- package/src/modals/IconPickerModal.tsx +1 -1
- package/src/modals/InspectModal.tsx +106 -0
- package/src/product-base/buildPaywallLocalizationParams.ts +3 -0
- package/src/product-base/types.ts +3 -0
- package/src/store.ts +39 -0
- package/src/styles/base/_global.scss +1 -1
- package/src/types/PreviewConfig.ts +30 -6
- package/dist/build-components/index.generated.d.ts +0 -38
- package/dist/types/Icons.generated.d.ts +0 -2
- package/src/build-components/index.generated.ts +0 -184
- package/src/types/Icons.generated.ts +0 -244
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React, { useMemo, useState } from 'react';
|
|
1
|
+
import React, { useMemo, useState, useCallback, useRef } from 'react';
|
|
2
2
|
import { Icon } from './Icon.generated';
|
|
3
|
-
import type { IconsType } from '../types/Icons
|
|
3
|
+
import type { IconsType } from '../types/Icons';
|
|
4
4
|
import { useRenderStore } from '../store';
|
|
5
5
|
import type { Localication } from '../types/PreviewConfig';
|
|
6
6
|
import {
|
|
@@ -29,7 +29,6 @@ export function BottomBar({
|
|
|
29
29
|
setData,
|
|
30
30
|
project,
|
|
31
31
|
}: BottomBarProps) {
|
|
32
|
-
const rtlIcon: IconsType = 'translate';
|
|
33
32
|
const magicCursorIcon: IconsType = 'magicpen';
|
|
34
33
|
const debugIcon: IconsType = 'speedometer-03';
|
|
35
34
|
const localizationIcon: IconsType = 'globe-01';
|
|
@@ -45,8 +44,8 @@ export function BottomBar({
|
|
|
45
44
|
setDefaultLanguage,
|
|
46
45
|
previewMode,
|
|
47
46
|
setPreviewMode,
|
|
48
|
-
isRtl,
|
|
49
47
|
setIsRtl,
|
|
48
|
+
languageColumns,
|
|
50
49
|
} = useRenderStore((s) => ({
|
|
51
50
|
localization: s.localization,
|
|
52
51
|
setLocalization: s.setLocalization,
|
|
@@ -56,24 +55,55 @@ export function BottomBar({
|
|
|
56
55
|
setDefaultLanguage: s.setDefaultLanguage,
|
|
57
56
|
previewMode: s.previewMode,
|
|
58
57
|
setPreviewMode: s.setPreviewMode,
|
|
59
|
-
isRtl: s.isRtl,
|
|
60
58
|
setIsRtl: s.setIsRtl,
|
|
59
|
+
languageColumns: s.languageColumns,
|
|
61
60
|
}));
|
|
62
61
|
|
|
63
62
|
const [isDebugOpen, setIsDebugOpen] = useState(false);
|
|
64
63
|
const [isLocalizationOpen, setIsLocalizationOpen] = useState(false);
|
|
65
64
|
const [isInspectOpen, setIsInspectOpen] = useState(false);
|
|
66
65
|
const [isPromptManagerOpen, setIsPromptManagerOpen] = useState(false);
|
|
66
|
+
const [langDropdownOpen, setLangDropdownOpen] = useState(false);
|
|
67
|
+
const langTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
68
|
+
|
|
69
|
+
// Use languageColumns from API, fallback to localization object keys
|
|
70
|
+
const availableLanguages = useMemo(() => {
|
|
71
|
+
if (languageColumns.length > 0) return languageColumns;
|
|
72
|
+
const locKeys = Object.keys(localization ?? {});
|
|
73
|
+
return locKeys.map((code) => ({
|
|
74
|
+
id: 0,
|
|
75
|
+
code: code.toUpperCase(),
|
|
76
|
+
title: code,
|
|
77
|
+
iso_code: code.toUpperCase(),
|
|
78
|
+
is_right_alignment: false,
|
|
79
|
+
}));
|
|
80
|
+
}, [languageColumns, localization]);
|
|
67
81
|
|
|
68
|
-
const languages = useMemo(() => ['en', 'tr', 'ar'], []);
|
|
69
82
|
const activeLanguage = defaultLanguage;
|
|
70
83
|
|
|
84
|
+
const handleSelectLanguage = useCallback(
|
|
85
|
+
(lang: { code: string; is_right_alignment: boolean }) => {
|
|
86
|
+
const code = lang.code.toLowerCase();
|
|
87
|
+
setDefaultLanguage(code);
|
|
88
|
+
setIsRtl(lang.is_right_alignment);
|
|
89
|
+
setLangDropdownOpen(false);
|
|
90
|
+
},
|
|
91
|
+
[setDefaultLanguage, setIsRtl],
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const handleLangMouseEnter = () => {
|
|
95
|
+
if (langTimeoutRef.current) clearTimeout(langTimeoutRef.current);
|
|
96
|
+
setLangDropdownOpen(true);
|
|
97
|
+
};
|
|
98
|
+
const handleLangMouseLeave = () => {
|
|
99
|
+
langTimeoutRef.current = setTimeout(() => setLangDropdownOpen(false), 200);
|
|
100
|
+
};
|
|
101
|
+
|
|
71
102
|
const handleLocalicationChange = (next: Localication) => {
|
|
72
103
|
setLocalization(next);
|
|
73
104
|
};
|
|
74
105
|
|
|
75
106
|
const themeIsActive = theme === 'dark';
|
|
76
|
-
const rtlIsActive = isRtl;
|
|
77
107
|
const previewIsActive = previewMode;
|
|
78
108
|
const themeIcon: IconsType = themeIsActive ? 'moon-bold' : 'sun';
|
|
79
109
|
|
|
@@ -90,19 +120,6 @@ export function BottomBar({
|
|
|
90
120
|
<Icon iconType={themeIcon} size={20} color="currentColor" alt="" />
|
|
91
121
|
</button>
|
|
92
122
|
|
|
93
|
-
<button
|
|
94
|
-
type="button"
|
|
95
|
-
className={`rb-bottom-bar__button rb-bottom-bar__button--rtl${
|
|
96
|
-
rtlIsActive ? ' is-active' : ''
|
|
97
|
-
}`}
|
|
98
|
-
aria-label="RTL"
|
|
99
|
-
aria-pressed={rtlIsActive}
|
|
100
|
-
onClick={() => setIsRtl(!isRtl)}
|
|
101
|
-
>
|
|
102
|
-
<Icon iconType={rtlIcon} size={18} color="currentColor" alt="" />
|
|
103
|
-
<span className="rb-bottom-bar__rtl-text">RTL</span>
|
|
104
|
-
</button>
|
|
105
|
-
|
|
106
123
|
<button
|
|
107
124
|
type="button"
|
|
108
125
|
className={`rb-bottom-bar__button rb-bottom-bar__button--preview${
|
|
@@ -171,19 +188,106 @@ export function BottomBar({
|
|
|
171
188
|
|
|
172
189
|
<div className="rb-bottom-bar__spacer" />
|
|
173
190
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
191
|
+
{/* Language selector with hover-up dropdown */}
|
|
192
|
+
<div
|
|
193
|
+
className="rb-bottom-bar__lang-selector"
|
|
194
|
+
onMouseEnter={handleLangMouseEnter}
|
|
195
|
+
onMouseLeave={handleLangMouseLeave}
|
|
196
|
+
style={{ position: 'relative' }}
|
|
197
|
+
>
|
|
198
|
+
<button
|
|
199
|
+
type="button"
|
|
200
|
+
className="rb-bottom-bar__lang is-active"
|
|
201
|
+
style={{
|
|
202
|
+
display: 'flex',
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
gap: 4,
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
{activeLanguage.toUpperCase()}
|
|
208
|
+
<span style={{ fontSize: 8, opacity: 0.6 }}>▲</span>
|
|
209
|
+
</button>
|
|
210
|
+
|
|
211
|
+
{langDropdownOpen && availableLanguages.length > 0 && (
|
|
212
|
+
<div
|
|
213
|
+
className="rb-bottom-bar__lang-dropdown"
|
|
214
|
+
style={{
|
|
215
|
+
position: 'absolute',
|
|
216
|
+
bottom: '100%',
|
|
217
|
+
right: 0,
|
|
218
|
+
marginBottom: 4,
|
|
219
|
+
background: 'var(--rb-bg-secondary, #2a2a2a)',
|
|
220
|
+
border: '1px solid var(--rb-border, #444)',
|
|
221
|
+
borderRadius: 6,
|
|
222
|
+
padding: '4px 0',
|
|
223
|
+
minWidth: 150,
|
|
224
|
+
maxHeight: 260,
|
|
225
|
+
overflowY: 'auto',
|
|
226
|
+
boxShadow: '0 -4px 16px rgba(0,0,0,0.3)',
|
|
227
|
+
zIndex: 100,
|
|
228
|
+
}}
|
|
183
229
|
>
|
|
184
|
-
{
|
|
185
|
-
|
|
186
|
-
|
|
230
|
+
{availableLanguages.map((lang) => {
|
|
231
|
+
const isActive =
|
|
232
|
+
lang.code.toLowerCase() === activeLanguage.toLowerCase();
|
|
233
|
+
return (
|
|
234
|
+
<button
|
|
235
|
+
key={lang.code}
|
|
236
|
+
type="button"
|
|
237
|
+
onClick={() => handleSelectLanguage(lang)}
|
|
238
|
+
style={{
|
|
239
|
+
display: 'flex',
|
|
240
|
+
alignItems: 'center',
|
|
241
|
+
gap: 6,
|
|
242
|
+
width: '100%',
|
|
243
|
+
padding: '5px 10px',
|
|
244
|
+
border: 'none',
|
|
245
|
+
background: isActive
|
|
246
|
+
? 'var(--rb-accent, #6366f1)'
|
|
247
|
+
: 'transparent',
|
|
248
|
+
color: isActive
|
|
249
|
+
? '#fff'
|
|
250
|
+
: 'var(--rb-text-primary, #e0e0e0)',
|
|
251
|
+
cursor: 'pointer',
|
|
252
|
+
fontSize: 12,
|
|
253
|
+
textAlign: 'left',
|
|
254
|
+
}}
|
|
255
|
+
onMouseEnter={(e) => {
|
|
256
|
+
if (!isActive)
|
|
257
|
+
e.currentTarget.style.background =
|
|
258
|
+
'var(--rb-bg-hover, #333)';
|
|
259
|
+
}}
|
|
260
|
+
onMouseLeave={(e) => {
|
|
261
|
+
if (!isActive)
|
|
262
|
+
e.currentTarget.style.background = 'transparent';
|
|
263
|
+
}}
|
|
264
|
+
>
|
|
265
|
+
<span
|
|
266
|
+
style={{
|
|
267
|
+
fontWeight: 600,
|
|
268
|
+
width: 24,
|
|
269
|
+
flexShrink: 0,
|
|
270
|
+
}}
|
|
271
|
+
>
|
|
272
|
+
{lang.code}
|
|
273
|
+
</span>
|
|
274
|
+
<span style={{ opacity: 0.7, flex: 1 }}>{lang.title}</span>
|
|
275
|
+
{lang.is_right_alignment && (
|
|
276
|
+
<span
|
|
277
|
+
style={{
|
|
278
|
+
fontSize: 9,
|
|
279
|
+
opacity: 0.5,
|
|
280
|
+
flexShrink: 0,
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
RTL
|
|
284
|
+
</span>
|
|
285
|
+
)}
|
|
286
|
+
</button>
|
|
287
|
+
);
|
|
288
|
+
})}
|
|
289
|
+
</div>
|
|
290
|
+
)}
|
|
187
291
|
</div>
|
|
188
292
|
</div>
|
|
189
293
|
|
|
@@ -49,7 +49,7 @@ export function DeviceNavigationBar({
|
|
|
49
49
|
context.navigation('launchscreen');
|
|
50
50
|
}
|
|
51
51
|
} else {
|
|
52
|
-
|
|
52
|
+
console.warn('Navigation: Back\n(Mock OS context not available)');
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
@@ -57,7 +57,7 @@ export function DeviceNavigationBar({
|
|
|
57
57
|
if (context) {
|
|
58
58
|
context.navigation('launchscreen');
|
|
59
59
|
} else {
|
|
60
|
-
|
|
60
|
+
console.warn('Navigation: Home\n(Mock OS context not available)');
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
package/src/hooks/useLocalize.ts
CHANGED
|
@@ -23,7 +23,19 @@ export function useLocalize(options?: {
|
|
|
23
23
|
|
|
24
24
|
return useCallback(
|
|
25
25
|
(keyOrText: string) => {
|
|
26
|
-
const
|
|
26
|
+
const langMap = localization?.[defaultLanguage];
|
|
27
|
+
const raw = langMap?.[keyOrText] ?? keyOrText;
|
|
28
|
+
// DEBUG: trace localization lookup
|
|
29
|
+
if (keyOrText.includes('.')) {
|
|
30
|
+
console.log('[useLocalize]', {
|
|
31
|
+
keyOrText,
|
|
32
|
+
defaultLanguage,
|
|
33
|
+
hasLangMap: !!langMap,
|
|
34
|
+
langMapKeyCount: langMap ? Object.keys(langMap).length : 0,
|
|
35
|
+
resolved: raw !== keyOrText ? raw : '❌ NOT FOUND',
|
|
36
|
+
allLangs: Object.keys(localization ?? {}),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
27
39
|
return replaceLocalizationParams(raw, params);
|
|
28
40
|
},
|
|
29
41
|
[defaultLanguage, localization, params],
|
package/src/index.web.ts
CHANGED
|
@@ -11,3 +11,22 @@ export * from './index';
|
|
|
11
11
|
export * from './build-components';
|
|
12
12
|
export { default as useNode } from './build-components/useNode';
|
|
13
13
|
export type { EventObjectGenerated } from './build-components/OnboardButton/OnboardButtonProps.generated';
|
|
14
|
+
|
|
15
|
+
// Host-app API: let parent apps inject a custom "Text" field renderer.
|
|
16
|
+
import { useRenderStore } from './store';
|
|
17
|
+
import type { ComponentType } from 'react';
|
|
18
|
+
import type { LocalizationApiConfig, LanguageColumn } from './store';
|
|
19
|
+
|
|
20
|
+
export function setBuilderStringChildrenField(
|
|
21
|
+
comp?: ComponentType<{ value: string; onChange: (v: string) => void }>,
|
|
22
|
+
) {
|
|
23
|
+
useRenderStore.getState().setRenderStringChildrenField(comp);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function setBuilderLocalizationConfig(config?: LocalizationApiConfig) {
|
|
27
|
+
useRenderStore.getState().setLocalizationApiConfig(config);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function setBuilderLanguageColumns(cols: LanguageColumn[]) {
|
|
31
|
+
useRenderStore.getState().setLanguageColumns(cols);
|
|
32
|
+
}
|
|
@@ -22,7 +22,7 @@ export class MockPermissionManager {
|
|
|
22
22
|
permission: PermissionType | string,
|
|
23
23
|
): Promise<PermissionStatus> {
|
|
24
24
|
if (this.context === null) {
|
|
25
|
-
|
|
25
|
+
console.warn(
|
|
26
26
|
`Permission requested: ${permission}\n(Mock OS context not available)`,
|
|
27
27
|
);
|
|
28
28
|
return 'not-determined';
|
|
@@ -40,7 +40,9 @@ export class MockPermissionManager {
|
|
|
40
40
|
|
|
41
41
|
checkPermission(permission: PermissionType | string): PermissionStatus {
|
|
42
42
|
if (this.context === null) {
|
|
43
|
-
|
|
43
|
+
console.warn(
|
|
44
|
+
`Permission check: ${permission}\n(Mock OS context not available)`,
|
|
45
|
+
);
|
|
44
46
|
return 'not-determined';
|
|
45
47
|
}
|
|
46
48
|
|
|
@@ -49,7 +51,7 @@ export class MockPermissionManager {
|
|
|
49
51
|
|
|
50
52
|
openSettings(): void {
|
|
51
53
|
if (this.context === null) {
|
|
52
|
-
|
|
54
|
+
console.warn('Opening Settings\n(Mock OS context not available)');
|
|
53
55
|
return;
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -22,7 +22,7 @@ export class MockNavigationManager {
|
|
|
22
22
|
|
|
23
23
|
goToHome(): void {
|
|
24
24
|
if (this.context === null) {
|
|
25
|
-
|
|
25
|
+
console.warn('Navigate to Home\n(Mock OS context not available)');
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -35,7 +35,9 @@ export class MockNavigationManager {
|
|
|
35
35
|
|
|
36
36
|
goToSubscriptions(): void {
|
|
37
37
|
if (this.context === null) {
|
|
38
|
-
|
|
38
|
+
console.warn(
|
|
39
|
+
'Navigate to Subscriptions\n(Mock OS context not available)',
|
|
40
|
+
);
|
|
39
41
|
return;
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -48,7 +50,7 @@ export class MockNavigationManager {
|
|
|
48
50
|
|
|
49
51
|
goToLaunchApp(): void {
|
|
50
52
|
if (this.context === null) {
|
|
51
|
-
|
|
53
|
+
console.warn('Navigate to Launch App\n(Mock OS context not available)');
|
|
52
54
|
return;
|
|
53
55
|
}
|
|
54
56
|
|
|
@@ -65,7 +67,7 @@ export class MockNavigationManager {
|
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
if (this.context === null) {
|
|
68
|
-
|
|
70
|
+
console.warn('Go Back\n(Mock OS context not available)');
|
|
69
71
|
return false;
|
|
70
72
|
}
|
|
71
73
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useMemo, useState } from 'react';
|
|
2
2
|
import Modal from './Modal';
|
|
3
|
-
import { Icons, type IconsType } from '../types/Icons
|
|
3
|
+
import { Icons, type IconsType } from '../types/Icons';
|
|
4
4
|
import { Icon } from '../components/Icon.generated';
|
|
5
5
|
|
|
6
6
|
type IconPickerModalProps = {
|
|
@@ -113,8 +113,41 @@ function LocalizationsTab({
|
|
|
113
113
|
);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
const handleDownloadCSV = () => {
|
|
117
|
+
let csvContent = 'Key,Value,Source\n';
|
|
118
|
+
allKeys.forEach((key) => {
|
|
119
|
+
const hasCustom = key in langKeys;
|
|
120
|
+
const customValue = langKeys[key];
|
|
121
|
+
const defaultValue = defaultLangKeys[key];
|
|
122
|
+
|
|
123
|
+
const isCustom =
|
|
124
|
+
hasCustom && customValue !== defaultValue && customValue !== undefined;
|
|
125
|
+
const value = isCustom ? customValue : (defaultValue ?? '');
|
|
126
|
+
|
|
127
|
+
const escape = (str: string) => `"${String(str).replace(/"/g, '""')}"`;
|
|
128
|
+
csvContent += `${escape(key)},${escape(String(value))},${isCustom ? 'custom' : 'fallback'}\n`;
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
downloadCSV(`localizations-${language}.csv`, csvContent);
|
|
132
|
+
};
|
|
133
|
+
|
|
116
134
|
return (
|
|
117
135
|
<div className="inspect-modal__table-wrap">
|
|
136
|
+
<div
|
|
137
|
+
style={{
|
|
138
|
+
display: 'flex',
|
|
139
|
+
justifyContent: 'flex-end',
|
|
140
|
+
marginBottom: '8px',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
<button
|
|
144
|
+
type="button"
|
|
145
|
+
className="editor-button"
|
|
146
|
+
onClick={handleDownloadCSV}
|
|
147
|
+
>
|
|
148
|
+
Download CSV
|
|
149
|
+
</button>
|
|
150
|
+
</div>
|
|
118
151
|
<table className="inspect-modal__table">
|
|
119
152
|
<thead>
|
|
120
153
|
<tr>
|
|
@@ -184,8 +217,38 @@ function ParamsTab({
|
|
|
184
217
|
);
|
|
185
218
|
}
|
|
186
219
|
|
|
220
|
+
const handleDownloadCSV = () => {
|
|
221
|
+
let csvContent = 'Param,Value,Type\n';
|
|
222
|
+
const escape = (str: string) => `"${String(str).replace(/"/g, '""')}"`;
|
|
223
|
+
|
|
224
|
+
flatEntries.forEach(([key, value]) => {
|
|
225
|
+
csvContent += `${escape(key)},${escape(formatValue(value))},Flat\n`;
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
nestedEntries.forEach(([key, value]) => {
|
|
229
|
+
csvContent += `${escape(key)},${escape(formatValue(value))},Nested\n`;
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
downloadCSV('params.csv', csvContent);
|
|
233
|
+
};
|
|
234
|
+
|
|
187
235
|
return (
|
|
188
236
|
<div className="inspect-modal__table-wrap">
|
|
237
|
+
<div
|
|
238
|
+
style={{
|
|
239
|
+
display: 'flex',
|
|
240
|
+
justifyContent: 'flex-end',
|
|
241
|
+
marginBottom: '8px',
|
|
242
|
+
}}
|
|
243
|
+
>
|
|
244
|
+
<button
|
|
245
|
+
type="button"
|
|
246
|
+
className="editor-button"
|
|
247
|
+
onClick={handleDownloadCSV}
|
|
248
|
+
>
|
|
249
|
+
Download CSV
|
|
250
|
+
</button>
|
|
251
|
+
</div>
|
|
189
252
|
<table className="inspect-modal__table">
|
|
190
253
|
<thead>
|
|
191
254
|
<tr>
|
|
@@ -249,8 +312,40 @@ function ColorsTab({
|
|
|
249
312
|
return <p className="inspect-modal__empty">No project colors defined.</p>;
|
|
250
313
|
}
|
|
251
314
|
|
|
315
|
+
const handleDownloadCSV = () => {
|
|
316
|
+
let csvContent = 'Type,Token,Value,Source\n';
|
|
317
|
+
const escape = (str: string) => `"${String(str).replace(/"/g, '""')}"`;
|
|
318
|
+
|
|
319
|
+
staticEntries.forEach(([token, hex]) => {
|
|
320
|
+
const isDefault = defaultStatic[token] === hex;
|
|
321
|
+
csvContent += `Static,${escape(token)},${escape(String(hex))},${isDefault ? 'fallback' : 'custom'}\n`;
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
themeEntries.forEach(([token, hex]) => {
|
|
325
|
+
const isDefault = defaultThemeMap[token] === hex;
|
|
326
|
+
csvContent += `Theme (${theme}),${escape(token)},${escape(String(hex))},${isDefault ? 'fallback' : 'custom'}\n`;
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
downloadCSV('colors.csv', csvContent);
|
|
330
|
+
};
|
|
331
|
+
|
|
252
332
|
return (
|
|
253
333
|
<div className="inspect-modal__table-wrap">
|
|
334
|
+
<div
|
|
335
|
+
style={{
|
|
336
|
+
display: 'flex',
|
|
337
|
+
justifyContent: 'flex-end',
|
|
338
|
+
marginBottom: '8px',
|
|
339
|
+
}}
|
|
340
|
+
>
|
|
341
|
+
<button
|
|
342
|
+
type="button"
|
|
343
|
+
className="editor-button"
|
|
344
|
+
onClick={handleDownloadCSV}
|
|
345
|
+
>
|
|
346
|
+
Download CSV
|
|
347
|
+
</button>
|
|
348
|
+
</div>
|
|
254
349
|
{staticEntries.length > 0 && (
|
|
255
350
|
<>
|
|
256
351
|
<h4 className="inspect-modal__section-title">Static Colors</h4>
|
|
@@ -357,3 +452,14 @@ function formatValue(v: unknown): string {
|
|
|
357
452
|
if (typeof v === 'number' || typeof v === 'boolean') return String(v);
|
|
358
453
|
return JSON.stringify(v);
|
|
359
454
|
}
|
|
455
|
+
|
|
456
|
+
function downloadCSV(filename: string, csvContent: string) {
|
|
457
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
458
|
+
const url = URL.createObjectURL(blob);
|
|
459
|
+
const link = document.createElement('a');
|
|
460
|
+
link.setAttribute('href', url);
|
|
461
|
+
link.setAttribute('download', filename);
|
|
462
|
+
document.body.appendChild(link);
|
|
463
|
+
link.click();
|
|
464
|
+
document.body.removeChild(link);
|
|
465
|
+
}
|
|
@@ -105,5 +105,8 @@ export function buildPaywallLocalizationParams(
|
|
|
105
105
|
productCurreny: extractedParams.currency,
|
|
106
106
|
productId: String(product.productId ?? ''),
|
|
107
107
|
productSelected: 'true',
|
|
108
|
+
subscribe: hasTrial
|
|
109
|
+
? resolve('base.builder.paywall.button.subscribe-free')
|
|
110
|
+
: resolve('base.builder.paywall.button.subscribe'),
|
|
108
111
|
};
|
|
109
112
|
}
|
|
@@ -116,6 +116,8 @@ export interface Product {
|
|
|
116
116
|
displayPrice?: string;
|
|
117
117
|
localizedPrice?: string;
|
|
118
118
|
price?: string;
|
|
119
|
+
promoPrice?: string;
|
|
120
|
+
localizedPromoPrice?: string;
|
|
119
121
|
currency?: string;
|
|
120
122
|
currencyCode?: string;
|
|
121
123
|
platform?: 'android' | 'ios';
|
|
@@ -169,6 +171,7 @@ export interface ProductParams {
|
|
|
169
171
|
productCurreny: string;
|
|
170
172
|
productId: string;
|
|
171
173
|
productSelected: string;
|
|
174
|
+
subscribe: string;
|
|
172
175
|
}
|
|
173
176
|
|
|
174
177
|
/** Keys excluded from single (per-option) params — these are global/context-level. */
|
package/src/store.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
|
|
3
|
+
export type LocalizationApiConfig = {
|
|
4
|
+
forgeUrl: string;
|
|
5
|
+
forgeToken: string;
|
|
6
|
+
forgeAppId?: string | number | null;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type LanguageColumn = {
|
|
10
|
+
id: number;
|
|
11
|
+
code: string;
|
|
12
|
+
title: string;
|
|
13
|
+
iso_code: string;
|
|
14
|
+
is_right_alignment: boolean;
|
|
15
|
+
};
|
|
1
16
|
import { createWithEqualityFn } from 'zustand/traditional';
|
|
2
17
|
import { shallow } from 'zustand/shallow';
|
|
3
18
|
import type { Device } from './types/Device';
|
|
@@ -43,6 +58,8 @@ type RenderStore = {
|
|
|
43
58
|
setLocalization: (localization: Localication) => void;
|
|
44
59
|
isRtl: boolean;
|
|
45
60
|
setIsRtl: (isRtl: boolean) => void;
|
|
61
|
+
languageColumns: LanguageColumn[];
|
|
62
|
+
setLanguageColumns: (cols: LanguageColumn[]) => void;
|
|
46
63
|
previewMode: boolean;
|
|
47
64
|
setPreviewMode: (previewMode: boolean) => void;
|
|
48
65
|
// Mockable: products (persisted)
|
|
@@ -101,6 +118,18 @@ type RenderStore = {
|
|
|
101
118
|
|
|
102
119
|
favoriteDevices: string[];
|
|
103
120
|
toggleFavoriteDevice: (name: string) => void;
|
|
121
|
+
|
|
122
|
+
// Host-provided custom renderer for the "Text" (string children) field
|
|
123
|
+
renderStringChildrenField?: ComponentType<{
|
|
124
|
+
value: string;
|
|
125
|
+
onChange: (v: string) => void;
|
|
126
|
+
}>;
|
|
127
|
+
setRenderStringChildrenField: (
|
|
128
|
+
comp?: ComponentType<{ value: string; onChange: (v: string) => void }>,
|
|
129
|
+
) => void;
|
|
130
|
+
|
|
131
|
+
localizationApiConfig?: LocalizationApiConfig;
|
|
132
|
+
setLocalizationApiConfig: (config?: LocalizationApiConfig) => void;
|
|
104
133
|
};
|
|
105
134
|
|
|
106
135
|
export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
@@ -129,6 +158,8 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
|
129
158
|
setLocalization: (localization) => set({ localization }),
|
|
130
159
|
isRtl: false,
|
|
131
160
|
setIsRtl: (isRtl) => set({ isRtl }),
|
|
161
|
+
languageColumns: [],
|
|
162
|
+
setLanguageColumns: (cols) => set({ languageColumns: cols }),
|
|
132
163
|
previewMode: false,
|
|
133
164
|
setPreviewMode: (previewMode) => set({ previewMode }),
|
|
134
165
|
products: [],
|
|
@@ -296,6 +327,14 @@ export const useRenderStore = createWithEqualityFn<RenderStore>()(
|
|
|
296
327
|
: [...devs, name],
|
|
297
328
|
};
|
|
298
329
|
}),
|
|
330
|
+
|
|
331
|
+
renderStringChildrenField: undefined,
|
|
332
|
+
setRenderStringChildrenField: (comp) =>
|
|
333
|
+
set({ renderStringChildrenField: comp }),
|
|
334
|
+
|
|
335
|
+
localizationApiConfig: undefined,
|
|
336
|
+
setLocalizationApiConfig: (config) =>
|
|
337
|
+
set({ localizationApiConfig: config }),
|
|
299
338
|
}),
|
|
300
339
|
{
|
|
301
340
|
name: 'render-store',
|
|
@@ -80,6 +80,7 @@ body,
|
|
|
80
80
|
background-position: center;
|
|
81
81
|
background-repeat: repeat;
|
|
82
82
|
background-size: 500px auto;
|
|
83
|
+
padding-bottom: 120px;
|
|
83
84
|
> * {
|
|
84
85
|
position: relative;
|
|
85
86
|
z-index: 1;
|
|
@@ -369,7 +370,6 @@ body,
|
|
|
369
370
|
min-width: 0;
|
|
370
371
|
min-height: calc(100vh - 120px);
|
|
371
372
|
padding: sizes.$spaceComfy;
|
|
372
|
-
padding-bottom: 120px;
|
|
373
373
|
position: relative;
|
|
374
374
|
}
|
|
375
375
|
|