@zentto/studio 0.5.1 → 0.6.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/dist/designer/zs-app-wizard.d.ts +15 -0
- package/dist/designer/zs-app-wizard.d.ts.map +1 -1
- package/dist/designer/zs-app-wizard.js +470 -16
- package/dist/designer/zs-app-wizard.js.map +1 -1
- package/dist/designer/zs-page-designer.d.ts +21 -0
- package/dist/designer/zs-page-designer.d.ts.map +1 -1
- package/dist/designer/zs-page-designer.js +547 -2
- package/dist/designer/zs-page-designer.js.map +1 -1
- package/dist/fields/zs-field-datagrid.d.ts +42 -0
- package/dist/fields/zs-field-datagrid.d.ts.map +1 -0
- package/dist/fields/zs-field-datagrid.js +206 -0
- package/dist/fields/zs-field-datagrid.js.map +1 -0
- package/dist/fields/zs-field-report.d.ts +36 -0
- package/dist/fields/zs-field-report.d.ts.map +1 -0
- package/dist/fields/zs-field-report.js +168 -0
- package/dist/fields/zs-field-report.js.map +1 -0
- package/dist/zentto-studio-renderer.d.ts +2 -0
- package/dist/zentto-studio-renderer.d.ts.map +1 -1
- package/dist/zentto-studio-renderer.js +15 -0
- package/dist/zentto-studio-renderer.js.map +1 -1
- package/package.json +12 -1
|
@@ -10,20 +10,35 @@ export declare class ZsAppWizard extends LitElement {
|
|
|
10
10
|
private editingNavIndex;
|
|
11
11
|
private showIconPicker;
|
|
12
12
|
private iconPickerTarget;
|
|
13
|
+
private codeModalOpen;
|
|
14
|
+
private codeModalTitle;
|
|
15
|
+
private codeModalContent;
|
|
13
16
|
connectedCallback(): void;
|
|
14
17
|
private canNext;
|
|
15
18
|
private next;
|
|
16
19
|
private prev;
|
|
17
20
|
private finish;
|
|
21
|
+
private ensureTheme;
|
|
22
|
+
private ensureDataSources;
|
|
18
23
|
render(): import("lit-html").TemplateResult<1>;
|
|
19
24
|
private renderTemplateStep;
|
|
20
25
|
private renderBrandingStep;
|
|
21
26
|
private renderNavStep;
|
|
22
27
|
private addNavItem;
|
|
23
28
|
private moveNav;
|
|
29
|
+
private renderDataSourcesStep;
|
|
24
30
|
private renderPagesStep;
|
|
25
31
|
private getContentIcon;
|
|
32
|
+
private renderThemeStep;
|
|
26
33
|
private renderPreviewStep;
|
|
34
|
+
private exportJson;
|
|
35
|
+
private exportReactCode;
|
|
36
|
+
private exportNextCode;
|
|
37
|
+
private openCodeModal;
|
|
38
|
+
private closeCodeModal;
|
|
39
|
+
private copyCodeToClipboard;
|
|
40
|
+
private downloadCode;
|
|
41
|
+
private renderCodeModal;
|
|
27
42
|
}
|
|
28
43
|
declare global {
|
|
29
44
|
interface HTMLElementTagNameMap {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zs-app-wizard.d.ts","sourceRoot":"","sources":["../../src/designer/zs-app-wizard.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAiD,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"zs-app-wizard.d.ts","sourceRoot":"","sources":["../../src/designer/zs-app-wizard.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAsB,MAAM,KAAK,CAAC;AAGrD,OAAO,KAAK,EAAE,SAAS,EAAiD,MAAM,qBAAqB,CAAC;AAOpG,OAAO,yBAAyB,CAAC;AAkEjC,qBACa,WAAY,SAAQ,UAAU;IACzC,MAAM,CAAC,MAAM,4BAmOV;IAEyB,aAAa,EAAE,SAAS,GAAG,IAAI,CAAQ;IAE1D,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,gBAAgB,CAA8B;IACtD,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,gBAAgB,CAA6B;IAGrD,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,cAAc,CAAM;IAC5B,OAAO,CAAC,gBAAgB,CAAM;IAEvC,iBAAiB;IAUjB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,IAAI;IAIZ,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,WAAW;IAOnB,OAAO,CAAC,iBAAiB;IASzB,MAAM;IAyCN,OAAO,CAAC,kBAAkB;IAmB1B,OAAO,CAAC,kBAAkB;IAkD1B,OAAO,CAAC,aAAa;IA8CrB,OAAO,CAAC,UAAU;IAelB,OAAO,CAAC,OAAO;IAWf,OAAO,CAAC,qBAAqB;IAuG7B,OAAO,CAAC,eAAe;IAqCvB,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,eAAe;IAuHvB,OAAO,CAAC,iBAAiB;IAyCzB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,cAAc;IAMtB,OAAO,CAAC,mBAAmB;IAO3B,OAAO,CAAC,YAAY;IAiBpB,OAAO,CAAC,eAAe;CAqBxB;AAED,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,qBAAqB;QAC7B,eAAe,EAAE,WAAW,CAAC;KAC9B;CACF"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @zentto/studio — App Creation Wizard
|
|
2
2
|
// Step-by-step wizard to create an AppConfig from a template
|
|
3
|
-
// Steps: 1) Template → 2) Branding → 3)
|
|
3
|
+
// Steps: 1) Template → 2) Branding → 3) Menu → 4) Data Sources → 5) Pages → 6) Style & Theme → 7) Preview
|
|
4
4
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
5
5
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
6
6
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -11,14 +11,17 @@ import { LitElement, html, css, nothing } from 'lit';
|
|
|
11
11
|
import { customElement, property, state } from 'lit/decorators.js';
|
|
12
12
|
import { studioTokens, fieldBaseStyles } from '../styles/tokens.js';
|
|
13
13
|
import { listAppTemplates, getAppTemplate } from '@zentto/studio-core';
|
|
14
|
+
import { generateAppPage } from '@zentto/studio-core';
|
|
14
15
|
// Import the app component for preview
|
|
15
16
|
import '../zentto-studio-app.js';
|
|
16
17
|
const STEPS = [
|
|
17
18
|
{ id: 'template', title: 'Plantilla', icon: '📋', description: 'Elige una plantilla base' },
|
|
18
19
|
{ id: 'branding', title: 'Marca', icon: '🎨', description: 'Personaliza tu aplicacion' },
|
|
19
20
|
{ id: 'navigation', title: 'Menu', icon: '📑', description: 'Configura el sidebar' },
|
|
21
|
+
{ id: 'datasources', title: 'Fuentes de Datos', icon: '🔗', description: 'Conecta tus APIs y servicios' },
|
|
20
22
|
{ id: 'pages', title: 'Paginas', icon: '📄', description: 'Agrega paginas y contenido' },
|
|
21
|
-
{ id: '
|
|
23
|
+
{ id: 'theme', title: 'Estilo y Tema', icon: '🎭', description: 'Personaliza colores y tipografia' },
|
|
24
|
+
{ id: 'preview', title: 'Vista Previa', icon: '👁️', description: 'Revisa, exporta y finaliza' },
|
|
22
25
|
];
|
|
23
26
|
const SIDEBAR_STYLES = [
|
|
24
27
|
{ value: 'dark', label: 'Oscuro', preview: '#1e1e2d' },
|
|
@@ -37,6 +40,29 @@ const COLOR_PALETTE = [
|
|
|
37
40
|
'#ff6b6b', '#6bcb77', '#4ecdc4', '#45b7d1', '#96ceb4',
|
|
38
41
|
'#ffd93d', '#ff69b4', '#a29bfe', '#fd79a8', '#2d3436',
|
|
39
42
|
];
|
|
43
|
+
const HTTP_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
44
|
+
const THEME_MODES = [
|
|
45
|
+
{ value: 'light', label: 'Claro', icon: '☀️' },
|
|
46
|
+
{ value: 'dark', label: 'Oscuro', icon: '🌙' },
|
|
47
|
+
{ value: 'auto', label: 'Automatico', icon: '🔄' },
|
|
48
|
+
];
|
|
49
|
+
const FONT_OPTIONS = [
|
|
50
|
+
'Inter, sans-serif',
|
|
51
|
+
'Roboto, sans-serif',
|
|
52
|
+
'Open Sans, sans-serif',
|
|
53
|
+
'Poppins, sans-serif',
|
|
54
|
+
'Nunito, sans-serif',
|
|
55
|
+
'system-ui, sans-serif',
|
|
56
|
+
'Georgia, serif',
|
|
57
|
+
'Fira Code, monospace',
|
|
58
|
+
];
|
|
59
|
+
const RADIUS_OPTIONS = [
|
|
60
|
+
{ value: 0, label: 'Sin bordes' },
|
|
61
|
+
{ value: 4, label: 'Sutil (4px)' },
|
|
62
|
+
{ value: 8, label: 'Medio (8px)' },
|
|
63
|
+
{ value: 12, label: 'Redondeado (12px)' },
|
|
64
|
+
{ value: 20, label: 'Pill (20px)' },
|
|
65
|
+
];
|
|
40
66
|
let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
41
67
|
constructor() {
|
|
42
68
|
super(...arguments);
|
|
@@ -47,6 +73,10 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
47
73
|
this.editingNavIndex = -1;
|
|
48
74
|
this.showIconPicker = false;
|
|
49
75
|
this.iconPickerTarget = 'nav';
|
|
76
|
+
// Code export modal
|
|
77
|
+
this.codeModalOpen = false;
|
|
78
|
+
this.codeModalTitle = '';
|
|
79
|
+
this.codeModalContent = '';
|
|
50
80
|
}
|
|
51
81
|
static { this.styles = [studioTokens, fieldBaseStyles, css `
|
|
52
82
|
:host { display: block; font-family: var(--zs-font-family); }
|
|
@@ -62,13 +92,14 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
62
92
|
.wizard-steps {
|
|
63
93
|
display: flex; background: var(--zs-bg-secondary);
|
|
64
94
|
border-bottom: 1px solid var(--zs-border); padding: 0;
|
|
95
|
+
overflow-x: auto;
|
|
65
96
|
}
|
|
66
97
|
.wizard-step {
|
|
67
|
-
flex: 1; display: flex; align-items: center; gap:
|
|
68
|
-
padding:
|
|
69
|
-
color: var(--zs-text-muted); font-size:
|
|
98
|
+
flex: 1; display: flex; align-items: center; gap: 6px;
|
|
99
|
+
padding: 14px 12px; cursor: pointer;
|
|
100
|
+
color: var(--zs-text-muted); font-size: 12px;
|
|
70
101
|
border-bottom: 3px solid transparent;
|
|
71
|
-
transition: all 150ms;
|
|
102
|
+
transition: all 150ms; min-width: 0; white-space: nowrap;
|
|
72
103
|
}
|
|
73
104
|
.wizard-step:hover { color: var(--zs-text-secondary); }
|
|
74
105
|
.wizard-step--active {
|
|
@@ -76,8 +107,8 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
76
107
|
font-weight: 500;
|
|
77
108
|
}
|
|
78
109
|
.wizard-step--done { color: var(--zs-success); }
|
|
79
|
-
.wizard-step-icon { font-size:
|
|
80
|
-
.wizard-step-title { white-space: nowrap; }
|
|
110
|
+
.wizard-step-icon { font-size: 16px; }
|
|
111
|
+
.wizard-step-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
81
112
|
|
|
82
113
|
/* Body */
|
|
83
114
|
.wizard-body { padding: 32px; min-height: 400px; }
|
|
@@ -109,6 +140,7 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
109
140
|
.form-label { font-size: 13px; font-weight: 500; color: var(--zs-text); margin-bottom: 6px; display: block; }
|
|
110
141
|
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
111
142
|
.form-row-3 { grid-template-columns: 1fr 1fr 1fr; }
|
|
143
|
+
.form-row-4 { grid-template-columns: 1fr 1fr 1fr 1fr; }
|
|
112
144
|
|
|
113
145
|
/* Color picker grid */
|
|
114
146
|
.color-grid { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
@@ -193,6 +225,86 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
193
225
|
.btn--success { background: var(--zs-success); color: white; }
|
|
194
226
|
.btn--success:hover { opacity: 0.9; }
|
|
195
227
|
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
228
|
+
|
|
229
|
+
/* Data source rows */
|
|
230
|
+
.ds-card {
|
|
231
|
+
border: 1px solid var(--zs-border); border-radius: 8px;
|
|
232
|
+
padding: 16px; margin-bottom: 12px;
|
|
233
|
+
background: var(--zs-bg);
|
|
234
|
+
}
|
|
235
|
+
.ds-card-header {
|
|
236
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
237
|
+
margin-bottom: 12px;
|
|
238
|
+
}
|
|
239
|
+
.ds-card-title { font-size: 14px; font-weight: 500; color: var(--zs-text); }
|
|
240
|
+
.ds-method-badge {
|
|
241
|
+
display: inline-block; padding: 2px 8px; border-radius: 4px;
|
|
242
|
+
font-size: 11px; font-weight: 600; color: white;
|
|
243
|
+
}
|
|
244
|
+
.ds-method-GET { background: #27ae60; }
|
|
245
|
+
.ds-method-POST { background: #3498db; }
|
|
246
|
+
.ds-method-PUT { background: #e67e22; }
|
|
247
|
+
.ds-method-PATCH { background: #9b59b6; }
|
|
248
|
+
.ds-method-DELETE { background: #e74c3c; }
|
|
249
|
+
|
|
250
|
+
/* Theme preview card */
|
|
251
|
+
.theme-preview-card {
|
|
252
|
+
border: 1px solid var(--zs-border); border-radius: 12px;
|
|
253
|
+
padding: 24px; margin-top: 20px;
|
|
254
|
+
transition: all 200ms;
|
|
255
|
+
}
|
|
256
|
+
.theme-preview-header {
|
|
257
|
+
font-size: 16px; font-weight: 600; margin-bottom: 8px;
|
|
258
|
+
}
|
|
259
|
+
.theme-preview-text {
|
|
260
|
+
font-size: 13px; margin-bottom: 16px; opacity: 0.7;
|
|
261
|
+
}
|
|
262
|
+
.theme-preview-btn {
|
|
263
|
+
display: inline-block; padding: 8px 20px;
|
|
264
|
+
border: none; border-radius: 8px;
|
|
265
|
+
color: white; font-size: 13px; font-weight: 500;
|
|
266
|
+
}
|
|
267
|
+
.theme-preview-input {
|
|
268
|
+
display: inline-block; padding: 6px 12px;
|
|
269
|
+
border: 1px solid; border-radius: 8px;
|
|
270
|
+
font-size: 13px; margin-left: 8px; width: 150px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/* Code modal */
|
|
274
|
+
.code-modal-overlay {
|
|
275
|
+
position: fixed; inset: 0; z-index: 9999;
|
|
276
|
+
background: rgba(0,0,0,0.5); display: flex;
|
|
277
|
+
align-items: center; justify-content: center;
|
|
278
|
+
}
|
|
279
|
+
.code-modal {
|
|
280
|
+
background: var(--zs-bg); border-radius: 12px;
|
|
281
|
+
width: 90%; max-width: 800px; max-height: 80vh;
|
|
282
|
+
display: flex; flex-direction: column;
|
|
283
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
284
|
+
}
|
|
285
|
+
.code-modal-header {
|
|
286
|
+
display: flex; align-items: center; justify-content: space-between;
|
|
287
|
+
padding: 16px 20px; border-bottom: 1px solid var(--zs-border);
|
|
288
|
+
}
|
|
289
|
+
.code-modal-header h3 { margin: 0; font-size: 16px; }
|
|
290
|
+
.code-modal-body {
|
|
291
|
+
flex: 1; overflow: auto; padding: 0;
|
|
292
|
+
}
|
|
293
|
+
.code-modal-body pre {
|
|
294
|
+
margin: 0; padding: 20px; font-size: 13px;
|
|
295
|
+
font-family: 'Fira Code', 'Cascadia Code', monospace;
|
|
296
|
+
line-height: 1.5; white-space: pre-wrap; word-break: break-word;
|
|
297
|
+
background: var(--zs-bg-secondary); min-height: 200px;
|
|
298
|
+
}
|
|
299
|
+
.code-modal-footer {
|
|
300
|
+
display: flex; gap: 8px; justify-content: flex-end;
|
|
301
|
+
padding: 12px 20px; border-top: 1px solid var(--zs-border);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/* Export buttons row */
|
|
305
|
+
.export-row {
|
|
306
|
+
display: flex; gap: 8px; flex-wrap: wrap;
|
|
307
|
+
}
|
|
196
308
|
`]; }
|
|
197
309
|
connectedCallback() {
|
|
198
310
|
super.connectedCallback();
|
|
@@ -226,6 +338,19 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
226
338
|
bubbles: true, composed: true,
|
|
227
339
|
}));
|
|
228
340
|
}
|
|
341
|
+
// ─── Helpers ──────────────────────────────────────
|
|
342
|
+
ensureTheme() {
|
|
343
|
+
if (!this.config.theme) {
|
|
344
|
+
this.config.theme = { mode: 'light' };
|
|
345
|
+
}
|
|
346
|
+
return this.config.theme;
|
|
347
|
+
}
|
|
348
|
+
ensureDataSources() {
|
|
349
|
+
if (!this.config.dataSources) {
|
|
350
|
+
this.config.dataSources = [];
|
|
351
|
+
}
|
|
352
|
+
return this.config.dataSources;
|
|
353
|
+
}
|
|
229
354
|
// ─── Render ───────────────────────────────────────
|
|
230
355
|
render() {
|
|
231
356
|
return html `
|
|
@@ -248,8 +373,10 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
248
373
|
${this.currentStep === 0 ? this.renderTemplateStep() :
|
|
249
374
|
this.currentStep === 1 ? this.renderBrandingStep() :
|
|
250
375
|
this.currentStep === 2 ? this.renderNavStep() :
|
|
251
|
-
this.currentStep === 3 ? this.
|
|
252
|
-
this.
|
|
376
|
+
this.currentStep === 3 ? this.renderDataSourcesStep() :
|
|
377
|
+
this.currentStep === 4 ? this.renderPagesStep() :
|
|
378
|
+
this.currentStep === 5 ? this.renderThemeStep() :
|
|
379
|
+
this.renderPreviewStep()}
|
|
253
380
|
</div>
|
|
254
381
|
|
|
255
382
|
<div class="wizard-footer">
|
|
@@ -259,6 +386,8 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
259
386
|
: html `<button class="btn btn--success" @click="${this.finish}">Crear Aplicacion</button>`}
|
|
260
387
|
</div>
|
|
261
388
|
</div>
|
|
389
|
+
|
|
390
|
+
${this.codeModalOpen ? this.renderCodeModal() : nothing}
|
|
262
391
|
`;
|
|
263
392
|
}
|
|
264
393
|
// ─── Step 1: Template Selection ──────────────────
|
|
@@ -397,7 +526,115 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
397
526
|
[items[index], items[newIndex]] = [items[newIndex], items[index]];
|
|
398
527
|
this.requestUpdate();
|
|
399
528
|
}
|
|
400
|
-
// ─── Step 4:
|
|
529
|
+
// ─── Step 4: Data Sources ─────────────────────────
|
|
530
|
+
renderDataSourcesStep() {
|
|
531
|
+
if (!this.config)
|
|
532
|
+
return nothing;
|
|
533
|
+
const sources = this.ensureDataSources();
|
|
534
|
+
return html `
|
|
535
|
+
${sources.length === 0 ? html `
|
|
536
|
+
<div style="text-align:center;padding:40px 20px;color:var(--zs-text-muted);">
|
|
537
|
+
<div style="font-size:40px;margin-bottom:12px;">🔗</div>
|
|
538
|
+
<div style="font-size:14px;">No hay fuentes de datos configuradas.</div>
|
|
539
|
+
<div style="font-size:13px;margin-top:4px;">Agrega APIs REST para conectar tus paginas con datos reales.</div>
|
|
540
|
+
</div>
|
|
541
|
+
` : nothing}
|
|
542
|
+
|
|
543
|
+
${sources.map((ds, i) => html `
|
|
544
|
+
<div class="ds-card">
|
|
545
|
+
<div class="ds-card-header">
|
|
546
|
+
<span class="ds-card-title">${ds.name || `Fuente ${i + 1}`}</span>
|
|
547
|
+
<div style="display:flex;gap:8px;align-items:center;">
|
|
548
|
+
<span class="ds-method-badge ds-method-${ds.method ?? 'GET'}">${ds.method ?? 'GET'}</span>
|
|
549
|
+
<button class="nav-item-btn nav-item-btn--danger" title="Eliminar"
|
|
550
|
+
@click="${() => { sources.splice(i, 1); this.requestUpdate(); }}">✕</button>
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
<div class="form-row" style="margin-bottom:12px;">
|
|
555
|
+
<div class="form-group" style="margin-bottom:0;">
|
|
556
|
+
<label class="form-label">ID</label>
|
|
557
|
+
<input class="zs-input" .value="${ds.id}" @input="${(e) => {
|
|
558
|
+
ds.id = e.target.value;
|
|
559
|
+
this.requestUpdate();
|
|
560
|
+
}}" placeholder="customers" />
|
|
561
|
+
</div>
|
|
562
|
+
<div class="form-group" style="margin-bottom:0;">
|
|
563
|
+
<label class="form-label">Nombre</label>
|
|
564
|
+
<input class="zs-input" .value="${ds.name}" @input="${(e) => {
|
|
565
|
+
ds.name = e.target.value;
|
|
566
|
+
this.requestUpdate();
|
|
567
|
+
}}" placeholder="Clientes" />
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
|
|
571
|
+
<div class="form-row" style="margin-bottom:12px;">
|
|
572
|
+
<div class="form-group" style="margin-bottom:0;grid-column:span 1;">
|
|
573
|
+
<label class="form-label">Metodo</label>
|
|
574
|
+
<select class="zs-input" .value="${ds.method ?? 'GET'}" @change="${(e) => {
|
|
575
|
+
ds.method = e.target.value;
|
|
576
|
+
this.requestUpdate();
|
|
577
|
+
}}">
|
|
578
|
+
${HTTP_METHODS.map(m => html `<option value="${m}" ?selected="${ds.method === m}">${m}</option>`)}
|
|
579
|
+
</select>
|
|
580
|
+
</div>
|
|
581
|
+
<div class="form-group" style="margin-bottom:0;">
|
|
582
|
+
<label class="form-label">Intervalo de refresco (ms)</label>
|
|
583
|
+
<input class="zs-input" type="number" .value="${String(ds.refreshInterval ?? '')}" placeholder="0 = sin polling"
|
|
584
|
+
@input="${(e) => {
|
|
585
|
+
const v = parseInt(e.target.value);
|
|
586
|
+
ds.refreshInterval = isNaN(v) || v <= 0 ? undefined : v;
|
|
587
|
+
this.requestUpdate();
|
|
588
|
+
}}" />
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
|
|
592
|
+
<div class="form-group" style="margin-bottom:12px;">
|
|
593
|
+
<label class="form-label">URL</label>
|
|
594
|
+
<input class="zs-input" .value="${ds.url ?? ''}" @input="${(e) => {
|
|
595
|
+
ds.url = e.target.value;
|
|
596
|
+
this.requestUpdate();
|
|
597
|
+
}}" placeholder="https://api.ejemplo.com/v1/resource" />
|
|
598
|
+
</div>
|
|
599
|
+
|
|
600
|
+
<div class="form-group" style="margin-bottom:0;">
|
|
601
|
+
<label class="form-label">Headers (JSON, opcional)</label>
|
|
602
|
+
<textarea class="zs-input" rows="2" style="font-family:monospace;font-size:12px;resize:vertical;"
|
|
603
|
+
.value="${ds.headers ? JSON.stringify(ds.headers, null, 2) : ''}"
|
|
604
|
+
placeholder='{ "Authorization": "Bearer ..." }'
|
|
605
|
+
@change="${(e) => {
|
|
606
|
+
const raw = e.target.value.trim();
|
|
607
|
+
if (!raw) {
|
|
608
|
+
ds.headers = undefined;
|
|
609
|
+
this.requestUpdate();
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
ds.headers = JSON.parse(raw);
|
|
614
|
+
}
|
|
615
|
+
catch { /* ignore invalid JSON */ }
|
|
616
|
+
this.requestUpdate();
|
|
617
|
+
}}"
|
|
618
|
+
></textarea>
|
|
619
|
+
</div>
|
|
620
|
+
</div>
|
|
621
|
+
`)}
|
|
622
|
+
|
|
623
|
+
<button class="add-btn" @click="${() => {
|
|
624
|
+
const id = `ds-${Date.now()}`;
|
|
625
|
+
this.ensureDataSources().push({
|
|
626
|
+
id,
|
|
627
|
+
name: '',
|
|
628
|
+
type: 'rest',
|
|
629
|
+
url: '',
|
|
630
|
+
method: 'GET',
|
|
631
|
+
autoFetch: true,
|
|
632
|
+
});
|
|
633
|
+
this.requestUpdate();
|
|
634
|
+
}}">🔗 Nueva Fuente de Datos</button>
|
|
635
|
+
`;
|
|
636
|
+
}
|
|
637
|
+
// ─── Step 5: Pages ────────────────────────────────
|
|
401
638
|
renderPagesStep() {
|
|
402
639
|
if (!this.config)
|
|
403
640
|
return nothing;
|
|
@@ -442,28 +679,236 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
442
679
|
};
|
|
443
680
|
return icons[type] ?? '📄';
|
|
444
681
|
}
|
|
445
|
-
// ─── Step
|
|
682
|
+
// ─── Step 6: Style & Theme ────────────────────────
|
|
683
|
+
renderThemeStep() {
|
|
684
|
+
if (!this.config)
|
|
685
|
+
return nothing;
|
|
686
|
+
const theme = this.ensureTheme();
|
|
687
|
+
return html `
|
|
688
|
+
<!-- Theme mode -->
|
|
689
|
+
<div class="form-group">
|
|
690
|
+
<label class="form-label">Modo de Tema</label>
|
|
691
|
+
<div class="style-grid">
|
|
692
|
+
${THEME_MODES.map(m => html `
|
|
693
|
+
<div class="style-option ${theme.mode === m.value ? 'style-option--selected' : ''}"
|
|
694
|
+
@click="${() => { theme.mode = m.value; this.requestUpdate(); }}">
|
|
695
|
+
<div style="font-size:28px;margin-bottom:6px;">${m.icon}</div>
|
|
696
|
+
<div style="font-size:13px;font-weight:500;">${m.label}</div>
|
|
697
|
+
</div>
|
|
698
|
+
`)}
|
|
699
|
+
</div>
|
|
700
|
+
</div>
|
|
701
|
+
|
|
702
|
+
<!-- Primary color -->
|
|
703
|
+
<div class="form-group">
|
|
704
|
+
<label class="form-label">Color Primario (--zs-primary)</label>
|
|
705
|
+
<div class="color-grid">
|
|
706
|
+
${COLOR_PALETTE.map(color => html `
|
|
707
|
+
<div class="color-swatch ${theme.primaryColor === color ? 'color-swatch--selected' : ''}"
|
|
708
|
+
style="background: ${color}"
|
|
709
|
+
@click="${() => { theme.primaryColor = color; this.requestUpdate(); }}"
|
|
710
|
+
></div>
|
|
711
|
+
`)}
|
|
712
|
+
</div>
|
|
713
|
+
</div>
|
|
714
|
+
|
|
715
|
+
<!-- Font family -->
|
|
716
|
+
<div class="form-row">
|
|
717
|
+
<div class="form-group">
|
|
718
|
+
<label class="form-label">Tipografia (--zs-font)</label>
|
|
719
|
+
<select class="zs-input" .value="${theme.fontFamily ?? 'Inter, sans-serif'}" @change="${(e) => {
|
|
720
|
+
theme.fontFamily = e.target.value;
|
|
721
|
+
this.requestUpdate();
|
|
722
|
+
}}">
|
|
723
|
+
${FONT_OPTIONS.map(f => html `<option value="${f}" ?selected="${theme.fontFamily === f}">${f.split(',')[0]}</option>`)}
|
|
724
|
+
</select>
|
|
725
|
+
</div>
|
|
726
|
+
<div class="form-group">
|
|
727
|
+
<label class="form-label">Border Radius (--zs-radius)</label>
|
|
728
|
+
<select class="zs-input" .value="${String(theme.borderRadius ?? 8)}" @change="${(e) => {
|
|
729
|
+
theme.borderRadius = parseInt(e.target.value);
|
|
730
|
+
this.requestUpdate();
|
|
731
|
+
}}">
|
|
732
|
+
${RADIUS_OPTIONS.map(r => html `<option value="${r.value}" ?selected="${theme.borderRadius === r.value}">${r.label}</option>`)}
|
|
733
|
+
</select>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
|
|
737
|
+
<!-- Accent color -->
|
|
738
|
+
<div class="form-group">
|
|
739
|
+
<label class="form-label">Color de Acento (--zs-accent, opcional)</label>
|
|
740
|
+
<div class="color-grid">
|
|
741
|
+
${COLOR_PALETTE.map(color => html `
|
|
742
|
+
<div class="color-swatch ${theme.accentColor === color ? 'color-swatch--selected' : ''}"
|
|
743
|
+
style="background: ${color}"
|
|
744
|
+
@click="${() => { theme.accentColor = color; this.requestUpdate(); }}"
|
|
745
|
+
></div>
|
|
746
|
+
`)}
|
|
747
|
+
</div>
|
|
748
|
+
</div>
|
|
749
|
+
|
|
750
|
+
<!-- Font size & spacing -->
|
|
751
|
+
<div class="form-row">
|
|
752
|
+
<div class="form-group">
|
|
753
|
+
<label class="form-label">Tamano de Fuente Base (px)</label>
|
|
754
|
+
<input class="zs-input" type="number" min="10" max="22" .value="${String(theme.fontSize ?? 14)}" @input="${(e) => {
|
|
755
|
+
theme.fontSize = parseInt(e.target.value) || 14;
|
|
756
|
+
this.requestUpdate();
|
|
757
|
+
}}" />
|
|
758
|
+
</div>
|
|
759
|
+
<div class="form-group">
|
|
760
|
+
<label class="form-label">Espaciado Base (px)</label>
|
|
761
|
+
<input class="zs-input" type="number" min="2" max="16" .value="${String(theme.spacing ?? 8)}" @input="${(e) => {
|
|
762
|
+
theme.spacing = parseInt(e.target.value) || 8;
|
|
763
|
+
this.requestUpdate();
|
|
764
|
+
}}" />
|
|
765
|
+
</div>
|
|
766
|
+
</div>
|
|
767
|
+
|
|
768
|
+
<!-- Mini preview -->
|
|
769
|
+
<div class="theme-preview-card" style="
|
|
770
|
+
background: ${theme.mode === 'dark' ? '#1e1e2d' : '#ffffff'};
|
|
771
|
+
color: ${theme.mode === 'dark' ? '#e0e0e0' : '#333333'};
|
|
772
|
+
font-family: ${theme.fontFamily ?? 'Inter, sans-serif'};
|
|
773
|
+
font-size: ${theme.fontSize ?? 14}px;
|
|
774
|
+
border-radius: ${theme.borderRadius ?? 8}px;
|
|
775
|
+
">
|
|
776
|
+
<div class="theme-preview-header" style="color: ${theme.primaryColor ?? '#3498db'};">
|
|
777
|
+
Vista previa del tema
|
|
778
|
+
</div>
|
|
779
|
+
<div class="theme-preview-text">
|
|
780
|
+
Asi se vera tu aplicacion con estos ajustes de estilo.
|
|
781
|
+
</div>
|
|
782
|
+
<span class="theme-preview-btn" style="
|
|
783
|
+
background: ${theme.primaryColor ?? '#3498db'};
|
|
784
|
+
border-radius: ${theme.borderRadius ?? 8}px;
|
|
785
|
+
">Boton Primario</span>
|
|
786
|
+
<input class="theme-preview-input" value="Campo de texto"
|
|
787
|
+
style="
|
|
788
|
+
border-color: ${theme.mode === 'dark' ? '#444' : '#ccc'};
|
|
789
|
+
border-radius: ${theme.borderRadius ?? 8}px;
|
|
790
|
+
background: ${theme.mode === 'dark' ? '#2a2a3d' : '#f9f9f9'};
|
|
791
|
+
color: ${theme.mode === 'dark' ? '#e0e0e0' : '#333'};
|
|
792
|
+
font-family: ${theme.fontFamily ?? 'Inter, sans-serif'};
|
|
793
|
+
"
|
|
794
|
+
readonly
|
|
795
|
+
/>
|
|
796
|
+
</div>
|
|
797
|
+
`;
|
|
798
|
+
}
|
|
799
|
+
// ─── Step 7: Preview ──────────────────────────────
|
|
446
800
|
renderPreviewStep() {
|
|
447
801
|
if (!this.config)
|
|
448
802
|
return nothing;
|
|
803
|
+
const navPageCount = this.config.navigation.filter(n => n.kind !== 'divider' && n.kind !== 'header').length;
|
|
804
|
+
const dsCount = this.config.dataSources?.length ?? 0;
|
|
449
805
|
return html `
|
|
450
|
-
<div style="display:flex;gap:16px;margin-bottom:16px;">
|
|
451
|
-
<div style="flex:1;">
|
|
806
|
+
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;flex-wrap:wrap;">
|
|
807
|
+
<div style="flex:1;min-width:200px;">
|
|
452
808
|
<div style="font-size:13px;color:var(--zs-text-secondary);margin-bottom:4px;">Resumen:</div>
|
|
453
|
-
<div style="font-size:14px;"
|
|
809
|
+
<div style="font-size:14px;">
|
|
810
|
+
<strong>${this.config.branding.title}</strong> —
|
|
811
|
+
${this.config.pages.length} paginas,
|
|
812
|
+
${navPageCount} items en menu${dsCount > 0 ? `, ${dsCount} fuentes de datos` : ''}
|
|
813
|
+
</div>
|
|
454
814
|
</div>
|
|
815
|
+
</div>
|
|
816
|
+
|
|
817
|
+
<!-- Export buttons -->
|
|
818
|
+
<div class="export-row" style="margin-bottom:16px;">
|
|
819
|
+
<button class="btn btn--secondary" @click="${this.exportJson}">📋 Exportar JSON</button>
|
|
820
|
+
<button class="btn btn--secondary" @click="${this.exportReactCode}">⚛️ Exportar Codigo React</button>
|
|
821
|
+
<button class="btn btn--secondary" @click="${this.exportNextCode}">▲ Exportar Codigo Next.js</button>
|
|
455
822
|
<button class="btn btn--secondary" @click="${() => {
|
|
456
823
|
const json = JSON.stringify(this.config, null, 2);
|
|
457
824
|
navigator.clipboard.writeText(json).then(() => {
|
|
458
825
|
alert('JSON copiado al portapapeles');
|
|
459
826
|
});
|
|
460
|
-
}}"
|
|
827
|
+
}}">📎 Copiar JSON</button>
|
|
461
828
|
</div>
|
|
829
|
+
|
|
462
830
|
<div class="preview-container">
|
|
463
831
|
<zentto-studio-app .config="${this.config}"></zentto-studio-app>
|
|
464
832
|
</div>
|
|
465
833
|
`;
|
|
466
834
|
}
|
|
835
|
+
// ─── Export Handlers ──────────────────────────────
|
|
836
|
+
exportJson() {
|
|
837
|
+
if (!this.config)
|
|
838
|
+
return;
|
|
839
|
+
const json = JSON.stringify(this.config, null, 2);
|
|
840
|
+
this.openCodeModal('Exportar JSON — AppConfig', json);
|
|
841
|
+
}
|
|
842
|
+
exportReactCode() {
|
|
843
|
+
if (!this.config)
|
|
844
|
+
return;
|
|
845
|
+
const code = generateAppPage(this.config, {
|
|
846
|
+
framework: 'react',
|
|
847
|
+
useClientDirective: false,
|
|
848
|
+
});
|
|
849
|
+
this.openCodeModal('Exportar Codigo React', code);
|
|
850
|
+
}
|
|
851
|
+
exportNextCode() {
|
|
852
|
+
if (!this.config)
|
|
853
|
+
return;
|
|
854
|
+
const code = generateAppPage(this.config, {
|
|
855
|
+
framework: 'nextjs',
|
|
856
|
+
useClientDirective: true,
|
|
857
|
+
});
|
|
858
|
+
this.openCodeModal('Exportar Codigo Next.js', code);
|
|
859
|
+
}
|
|
860
|
+
openCodeModal(title, content) {
|
|
861
|
+
this.codeModalTitle = title;
|
|
862
|
+
this.codeModalContent = content;
|
|
863
|
+
this.codeModalOpen = true;
|
|
864
|
+
}
|
|
865
|
+
closeCodeModal() {
|
|
866
|
+
this.codeModalOpen = false;
|
|
867
|
+
this.codeModalTitle = '';
|
|
868
|
+
this.codeModalContent = '';
|
|
869
|
+
}
|
|
870
|
+
copyCodeToClipboard() {
|
|
871
|
+
navigator.clipboard.writeText(this.codeModalContent).then(() => {
|
|
872
|
+
// Briefly change button text via a subtle approach
|
|
873
|
+
alert('Codigo copiado al portapapeles');
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
downloadCode() {
|
|
877
|
+
const isJson = this.codeModalTitle.includes('JSON');
|
|
878
|
+
const ext = isJson ? 'json' : 'tsx';
|
|
879
|
+
const mime = isJson ? 'application/json' : 'text/typescript';
|
|
880
|
+
const filename = isJson ? 'app-config.json' : 'StudioApp.tsx';
|
|
881
|
+
const blob = new Blob([this.codeModalContent], { type: mime });
|
|
882
|
+
const url = URL.createObjectURL(blob);
|
|
883
|
+
const a = document.createElement('a');
|
|
884
|
+
a.href = url;
|
|
885
|
+
a.download = filename;
|
|
886
|
+
a.click();
|
|
887
|
+
URL.revokeObjectURL(url);
|
|
888
|
+
}
|
|
889
|
+
// ─── Code Modal ───────────────────────────────────
|
|
890
|
+
renderCodeModal() {
|
|
891
|
+
return html `
|
|
892
|
+
<div class="code-modal-overlay" @click="${(e) => {
|
|
893
|
+
if (e.target === e.currentTarget)
|
|
894
|
+
this.closeCodeModal();
|
|
895
|
+
}}">
|
|
896
|
+
<div class="code-modal">
|
|
897
|
+
<div class="code-modal-header">
|
|
898
|
+
<h3>${this.codeModalTitle}</h3>
|
|
899
|
+
<button class="nav-item-btn" @click="${this.closeCodeModal}" style="font-size:18px;">✕</button>
|
|
900
|
+
</div>
|
|
901
|
+
<div class="code-modal-body">
|
|
902
|
+
<pre>${this.codeModalContent}</pre>
|
|
903
|
+
</div>
|
|
904
|
+
<div class="code-modal-footer">
|
|
905
|
+
<button class="btn btn--secondary" @click="${this.downloadCode}">💾 Descargar</button>
|
|
906
|
+
<button class="btn btn--primary" @click="${this.copyCodeToClipboard}">📋 Copiar</button>
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
</div>
|
|
910
|
+
`;
|
|
911
|
+
}
|
|
467
912
|
};
|
|
468
913
|
__decorate([
|
|
469
914
|
property({ type: Object })
|
|
@@ -486,6 +931,15 @@ __decorate([
|
|
|
486
931
|
__decorate([
|
|
487
932
|
state()
|
|
488
933
|
], ZsAppWizard.prototype, "iconPickerTarget", void 0);
|
|
934
|
+
__decorate([
|
|
935
|
+
state()
|
|
936
|
+
], ZsAppWizard.prototype, "codeModalOpen", void 0);
|
|
937
|
+
__decorate([
|
|
938
|
+
state()
|
|
939
|
+
], ZsAppWizard.prototype, "codeModalTitle", void 0);
|
|
940
|
+
__decorate([
|
|
941
|
+
state()
|
|
942
|
+
], ZsAppWizard.prototype, "codeModalContent", void 0);
|
|
489
943
|
ZsAppWizard = __decorate([
|
|
490
944
|
customElement('zs-app-wizard')
|
|
491
945
|
], ZsAppWizard);
|