@zentto/studio 0.5.2 → 0.6.1
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 +749 -295
- package/dist/designer/zs-app-wizard.js.map +1 -1
- package/dist/designer/zs-page-designer.d.ts +27 -0
- package/dist/designer/zs-page-designer.d.ts.map +1 -1
- package/dist/designer/zs-page-designer.js +1396 -945
- 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 +313 -298
- package/dist/zentto-studio-renderer.js.map +1 -1
- package/package.json +74 -59
|
@@ -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,152 +73,238 @@ 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
|
-
static { this.styles = [studioTokens, fieldBaseStyles, css `
|
|
52
|
-
:host { display: block; font-family: var(--zs-font-family); }
|
|
53
|
-
|
|
54
|
-
.wizard {
|
|
55
|
-
max-width: 900px; margin: 0 auto;
|
|
56
|
-
background: var(--zs-bg); border-radius: 12px;
|
|
57
|
-
border: 1px solid var(--zs-border);
|
|
58
|
-
overflow: hidden;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* Steps bar */
|
|
62
|
-
.wizard-steps {
|
|
63
|
-
display: flex; background: var(--zs-bg-secondary);
|
|
64
|
-
border-bottom: 1px solid var(--zs-border); padding: 0;
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.wizard-step--
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.wizard-step
|
|
80
|
-
.wizard-step-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
.wizard-
|
|
85
|
-
.wizard-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.template-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
.template-card
|
|
103
|
-
.template-
|
|
104
|
-
.template-
|
|
105
|
-
.template-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
.form-
|
|
110
|
-
.form-
|
|
111
|
-
.form-row
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
.style-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
.nav-item-
|
|
147
|
-
.nav-item-
|
|
148
|
-
.nav-item-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
.btn--
|
|
192
|
-
.btn--
|
|
193
|
-
.btn--
|
|
194
|
-
.btn--
|
|
195
|
-
.btn
|
|
81
|
+
static { this.styles = [studioTokens, fieldBaseStyles, css `
|
|
82
|
+
:host { display: block; font-family: var(--zs-font-family); }
|
|
83
|
+
|
|
84
|
+
.wizard {
|
|
85
|
+
max-width: 900px; margin: 0 auto;
|
|
86
|
+
background: var(--zs-bg); border-radius: 12px;
|
|
87
|
+
border: 1px solid var(--zs-border);
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Steps bar */
|
|
92
|
+
.wizard-steps {
|
|
93
|
+
display: flex; background: var(--zs-bg-secondary);
|
|
94
|
+
border-bottom: 1px solid var(--zs-border); padding: 0;
|
|
95
|
+
overflow-x: auto;
|
|
96
|
+
}
|
|
97
|
+
.wizard-step {
|
|
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;
|
|
101
|
+
border-bottom: 3px solid transparent;
|
|
102
|
+
transition: all 150ms; min-width: 0; white-space: nowrap;
|
|
103
|
+
}
|
|
104
|
+
.wizard-step:hover { color: var(--zs-text-secondary); }
|
|
105
|
+
.wizard-step--active {
|
|
106
|
+
color: var(--zs-primary); border-bottom-color: var(--zs-primary);
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
}
|
|
109
|
+
.wizard-step--done { color: var(--zs-success); }
|
|
110
|
+
.wizard-step-icon { font-size: 16px; }
|
|
111
|
+
.wizard-step-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
112
|
+
|
|
113
|
+
/* Body */
|
|
114
|
+
.wizard-body { padding: 32px; min-height: 400px; }
|
|
115
|
+
.wizard-title { font-size: 22px; font-weight: 600; margin: 0 0 4px; color: var(--zs-text); }
|
|
116
|
+
.wizard-desc { font-size: 14px; color: var(--zs-text-secondary); margin: 0 0 24px; }
|
|
117
|
+
|
|
118
|
+
/* Footer */
|
|
119
|
+
.wizard-footer {
|
|
120
|
+
display: flex; justify-content: space-between;
|
|
121
|
+
padding: 16px 32px; border-top: 1px solid var(--zs-border);
|
|
122
|
+
background: var(--zs-bg-secondary);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Template cards */
|
|
126
|
+
.template-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; }
|
|
127
|
+
.template-card {
|
|
128
|
+
padding: 24px; border: 2px solid var(--zs-border);
|
|
129
|
+
border-radius: 12px; cursor: pointer;
|
|
130
|
+
transition: all 200ms; text-align: center;
|
|
131
|
+
}
|
|
132
|
+
.template-card:hover { border-color: var(--zs-primary); transform: translateY(-2px); box-shadow: 0 4px 12px var(--zs-shadow); }
|
|
133
|
+
.template-card--selected { border-color: var(--zs-primary); background: var(--zs-primary-light); }
|
|
134
|
+
.template-icon { font-size: 40px; margin-bottom: 12px; }
|
|
135
|
+
.template-title { font-size: 16px; font-weight: 600; color: var(--zs-text); }
|
|
136
|
+
.template-desc { font-size: 13px; color: var(--zs-text-secondary); margin-top: 4px; }
|
|
137
|
+
|
|
138
|
+
/* Form groups */
|
|
139
|
+
.form-group { margin-bottom: 20px; }
|
|
140
|
+
.form-label { font-size: 13px; font-weight: 500; color: var(--zs-text); margin-bottom: 6px; display: block; }
|
|
141
|
+
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
|
|
142
|
+
.form-row-3 { grid-template-columns: 1fr 1fr 1fr; }
|
|
143
|
+
.form-row-4 { grid-template-columns: 1fr 1fr 1fr 1fr; }
|
|
144
|
+
|
|
145
|
+
/* Color picker grid */
|
|
146
|
+
.color-grid { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
147
|
+
.color-swatch {
|
|
148
|
+
width: 36px; height: 36px; border-radius: 8px;
|
|
149
|
+
cursor: pointer; border: 3px solid transparent;
|
|
150
|
+
transition: all 150ms;
|
|
151
|
+
}
|
|
152
|
+
.color-swatch:hover { transform: scale(1.15); }
|
|
153
|
+
.color-swatch--selected { border-color: var(--zs-text); box-shadow: 0 0 0 2px white, 0 0 0 4px var(--zs-text); }
|
|
154
|
+
|
|
155
|
+
/* Sidebar style picker */
|
|
156
|
+
.style-grid { display: flex; gap: 12px; }
|
|
157
|
+
.style-option {
|
|
158
|
+
flex: 1; padding: 16px; text-align: center;
|
|
159
|
+
border: 2px solid var(--zs-border); border-radius: 8px;
|
|
160
|
+
cursor: pointer; transition: all 150ms;
|
|
161
|
+
}
|
|
162
|
+
.style-option:hover { border-color: var(--zs-primary); }
|
|
163
|
+
.style-option--selected { border-color: var(--zs-primary); background: var(--zs-primary-light); }
|
|
164
|
+
.style-preview {
|
|
165
|
+
width: 60px; height: 40px; border-radius: 4px;
|
|
166
|
+
margin: 0 auto 8px; border: 1px solid var(--zs-border);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* Nav editor */
|
|
170
|
+
.nav-list { border: 1px solid var(--zs-border); border-radius: 8px; overflow: hidden; }
|
|
171
|
+
.nav-item-row {
|
|
172
|
+
display: flex; align-items: center; gap: 8px;
|
|
173
|
+
padding: 10px 12px; border-bottom: 1px solid var(--zs-border);
|
|
174
|
+
font-size: 14px;
|
|
175
|
+
}
|
|
176
|
+
.nav-item-row:last-child { border-bottom: none; }
|
|
177
|
+
.nav-item-icon { font-size: 18px; cursor: pointer; }
|
|
178
|
+
.nav-item-title { flex: 1; }
|
|
179
|
+
.nav-item-actions { display: flex; gap: 4px; }
|
|
180
|
+
.nav-item-btn {
|
|
181
|
+
border: none; background: none; cursor: pointer;
|
|
182
|
+
font-size: 14px; padding: 2px 6px; border-radius: 4px;
|
|
183
|
+
color: var(--zs-text-muted); transition: all 100ms;
|
|
184
|
+
}
|
|
185
|
+
.nav-item-btn:hover { background: var(--zs-bg-hover); color: var(--zs-text); }
|
|
186
|
+
.nav-item-btn--danger:hover { color: var(--zs-danger); }
|
|
187
|
+
.add-btn {
|
|
188
|
+
display: flex; align-items: center; gap: 6px;
|
|
189
|
+
padding: 8px 16px; margin-top: 12px;
|
|
190
|
+
border: 1px dashed var(--zs-border); border-radius: 8px;
|
|
191
|
+
background: none; cursor: pointer;
|
|
192
|
+
font-family: var(--zs-font-family); font-size: 13px;
|
|
193
|
+
color: var(--zs-text-secondary); transition: all 150ms;
|
|
194
|
+
}
|
|
195
|
+
.add-btn:hover { border-color: var(--zs-primary); color: var(--zs-primary); }
|
|
196
|
+
|
|
197
|
+
/* Icon picker */
|
|
198
|
+
.icon-grid { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
199
|
+
.icon-btn {
|
|
200
|
+
width: 36px; height: 36px; display: flex; align-items: center; justify-content: center;
|
|
201
|
+
border: 1px solid transparent; border-radius: 6px;
|
|
202
|
+
cursor: pointer; font-size: 18px; background: none;
|
|
203
|
+
transition: all 100ms;
|
|
204
|
+
}
|
|
205
|
+
.icon-btn:hover { background: var(--zs-bg-hover); border-color: var(--zs-border); }
|
|
206
|
+
.icon-btn--selected { background: var(--zs-primary-light); border-color: var(--zs-primary); }
|
|
207
|
+
|
|
208
|
+
/* Preview */
|
|
209
|
+
.preview-container {
|
|
210
|
+
border: 1px solid var(--zs-border); border-radius: 8px;
|
|
211
|
+
height: 500px; overflow: hidden;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/* Buttons */
|
|
215
|
+
.btn {
|
|
216
|
+
padding: 10px 24px; border-radius: var(--zs-radius);
|
|
217
|
+
font-family: var(--zs-font-family); font-size: 14px;
|
|
218
|
+
font-weight: 500; cursor: pointer; border: 1px solid transparent;
|
|
219
|
+
transition: all 150ms;
|
|
220
|
+
}
|
|
221
|
+
.btn--primary { background: var(--zs-primary); color: white; }
|
|
222
|
+
.btn--primary:hover { background: var(--zs-primary-hover); }
|
|
223
|
+
.btn--secondary { background: var(--zs-bg); color: var(--zs-text); border-color: var(--zs-border); }
|
|
224
|
+
.btn--secondary:hover { background: var(--zs-bg-hover); }
|
|
225
|
+
.btn--success { background: var(--zs-success); color: white; }
|
|
226
|
+
.btn--success:hover { opacity: 0.9; }
|
|
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,55 +338,72 @@ 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
|
-
return html `
|
|
232
|
-
<div class="wizard">
|
|
233
|
-
<div class="wizard-steps">
|
|
234
|
-
${STEPS.map((step, i) => html `
|
|
235
|
-
<div class="wizard-step ${i === this.currentStep ? 'wizard-step--active' : i < this.currentStep ? 'wizard-step--done' : ''}"
|
|
356
|
+
return html `
|
|
357
|
+
<div class="wizard">
|
|
358
|
+
<div class="wizard-steps">
|
|
359
|
+
${STEPS.map((step, i) => html `
|
|
360
|
+
<div class="wizard-step ${i === this.currentStep ? 'wizard-step--active' : i < this.currentStep ? 'wizard-step--done' : ''}"
|
|
236
361
|
@click="${() => { if (i <= this.currentStep || this.config)
|
|
237
|
-
this.currentStep = i; }}">
|
|
238
|
-
<span class="wizard-step-icon">${i < this.currentStep ? '✓' : step.icon}</span>
|
|
239
|
-
<span class="wizard-step-title">${step.title}</span>
|
|
240
|
-
</div>
|
|
241
|
-
`)}
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
<div class="wizard-body">
|
|
245
|
-
<h2 class="wizard-title">${STEPS[this.currentStep].title}</h2>
|
|
246
|
-
<p class="wizard-desc">${STEPS[this.currentStep].description}</p>
|
|
247
|
-
|
|
362
|
+
this.currentStep = i; }}">
|
|
363
|
+
<span class="wizard-step-icon">${i < this.currentStep ? '✓' : step.icon}</span>
|
|
364
|
+
<span class="wizard-step-title">${step.title}</span>
|
|
365
|
+
</div>
|
|
366
|
+
`)}
|
|
367
|
+
</div>
|
|
368
|
+
|
|
369
|
+
<div class="wizard-body">
|
|
370
|
+
<h2 class="wizard-title">${STEPS[this.currentStep].title}</h2>
|
|
371
|
+
<p class="wizard-desc">${STEPS[this.currentStep].description}</p>
|
|
372
|
+
|
|
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.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
376
|
+
this.currentStep === 3 ? this.renderDataSourcesStep() :
|
|
377
|
+
this.currentStep === 4 ? this.renderPagesStep() :
|
|
378
|
+
this.currentStep === 5 ? this.renderThemeStep() :
|
|
379
|
+
this.renderPreviewStep()}
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<div class="wizard-footer">
|
|
383
|
+
<button class="btn btn--secondary" ?disabled="${this.currentStep === 0}" @click="${this.prev}">Anterior</button>
|
|
257
384
|
${this.currentStep < STEPS.length - 1
|
|
258
385
|
? html `<button class="btn btn--primary" ?disabled="${!this.canNext()}" @click="${this.next}">Siguiente</button>`
|
|
259
|
-
: html `<button class="btn btn--success" @click="${this.finish}">Crear Aplicacion</button>`}
|
|
260
|
-
</div>
|
|
261
|
-
</div>
|
|
386
|
+
: html `<button class="btn btn--success" @click="${this.finish}">Crear Aplicacion</button>`}
|
|
387
|
+
</div>
|
|
388
|
+
</div>
|
|
389
|
+
|
|
390
|
+
${this.codeModalOpen ? this.renderCodeModal() : nothing}
|
|
262
391
|
`;
|
|
263
392
|
}
|
|
264
393
|
// ─── Step 1: Template Selection ──────────────────
|
|
265
394
|
renderTemplateStep() {
|
|
266
395
|
const templates = listAppTemplates();
|
|
267
|
-
return html `
|
|
268
|
-
<div class="template-grid">
|
|
269
|
-
${templates.map(t => html `
|
|
270
|
-
<div class="template-card ${this.selectedTemplate === t.id ? 'template-card--selected' : ''}"
|
|
271
|
-
@click="${() => { this.selectedTemplate = t.id; }}">
|
|
272
|
-
<div class="template-icon">${t.icon}</div>
|
|
273
|
-
<div class="template-title">${t.title}</div>
|
|
274
|
-
<div class="template-desc">${t.description}</div>
|
|
275
|
-
</div>
|
|
276
|
-
`)}
|
|
277
|
-
</div>
|
|
396
|
+
return html `
|
|
397
|
+
<div class="template-grid">
|
|
398
|
+
${templates.map(t => html `
|
|
399
|
+
<div class="template-card ${this.selectedTemplate === t.id ? 'template-card--selected' : ''}"
|
|
400
|
+
@click="${() => { this.selectedTemplate = t.id; }}">
|
|
401
|
+
<div class="template-icon">${t.icon}</div>
|
|
402
|
+
<div class="template-title">${t.title}</div>
|
|
403
|
+
<div class="template-desc">${t.description}</div>
|
|
404
|
+
</div>
|
|
405
|
+
`)}
|
|
406
|
+
</div>
|
|
278
407
|
`;
|
|
279
408
|
}
|
|
280
409
|
// ─── Step 2: Branding ─────────────────────────────
|
|
@@ -282,92 +411,92 @@ let ZsAppWizard = class ZsAppWizard extends LitElement {
|
|
|
282
411
|
if (!this.config)
|
|
283
412
|
return nothing;
|
|
284
413
|
const b = this.config.branding;
|
|
285
|
-
return html `
|
|
286
|
-
<div class="form-row">
|
|
287
|
-
<div class="form-group">
|
|
288
|
-
<label class="form-label">Nombre de la App</label>
|
|
289
|
-
<input class="zs-input" .value="${b.title ?? ''}" @input="${(e) => { this.config.branding.title = e.target.value; this.requestUpdate(); }}" />
|
|
290
|
-
</div>
|
|
291
|
-
<div class="form-group">
|
|
292
|
-
<label class="form-label">Subtitulo</label>
|
|
293
|
-
<input class="zs-input" .value="${b.subtitle ?? ''}" @input="${(e) => { this.config.branding.subtitle = e.target.value; this.requestUpdate(); }}" />
|
|
294
|
-
</div>
|
|
295
|
-
</div>
|
|
296
|
-
|
|
297
|
-
<div class="form-group">
|
|
298
|
-
<label class="form-label">Color Principal</label>
|
|
299
|
-
<div class="color-grid">
|
|
300
|
-
${COLOR_PALETTE.map(color => html `
|
|
301
|
-
<div class="color-swatch ${b.primaryColor === color ? 'color-swatch--selected' : ''}"
|
|
302
|
-
style="background: ${color}"
|
|
303
|
-
@click="${() => { this.config.branding.primaryColor = color; this.requestUpdate(); }}"
|
|
304
|
-
></div>
|
|
305
|
-
`)}
|
|
306
|
-
</div>
|
|
307
|
-
</div>
|
|
308
|
-
|
|
309
|
-
<div class="form-group">
|
|
310
|
-
<label class="form-label">Estilo del Sidebar</label>
|
|
311
|
-
<div class="style-grid">
|
|
312
|
-
${SIDEBAR_STYLES.map(s => html `
|
|
313
|
-
<div class="style-option ${b.sidebarStyle === s.value ? 'style-option--selected' : ''}"
|
|
314
|
-
@click="${() => { this.config.branding.sidebarStyle = s.value; this.requestUpdate(); }}">
|
|
315
|
-
<div class="style-preview" style="background: ${s.preview}"></div>
|
|
316
|
-
<div style="font-size:13px;font-weight:500;">${s.label}</div>
|
|
317
|
-
</div>
|
|
318
|
-
`)}
|
|
319
|
-
</div>
|
|
320
|
-
</div>
|
|
321
|
-
|
|
322
|
-
<div class="form-group">
|
|
323
|
-
<label class="form-label">URL del Logo (opcional)</label>
|
|
324
|
-
<input class="zs-input" .value="${b.logo ?? ''}" placeholder="https://..." @input="${(e) => { this.config.branding.logo = e.target.value; this.requestUpdate(); }}" />
|
|
325
|
-
</div>
|
|
414
|
+
return html `
|
|
415
|
+
<div class="form-row">
|
|
416
|
+
<div class="form-group">
|
|
417
|
+
<label class="form-label">Nombre de la App</label>
|
|
418
|
+
<input class="zs-input" .value="${b.title ?? ''}" @input="${(e) => { this.config.branding.title = e.target.value; this.requestUpdate(); }}" />
|
|
419
|
+
</div>
|
|
420
|
+
<div class="form-group">
|
|
421
|
+
<label class="form-label">Subtitulo</label>
|
|
422
|
+
<input class="zs-input" .value="${b.subtitle ?? ''}" @input="${(e) => { this.config.branding.subtitle = e.target.value; this.requestUpdate(); }}" />
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
<div class="form-group">
|
|
427
|
+
<label class="form-label">Color Principal</label>
|
|
428
|
+
<div class="color-grid">
|
|
429
|
+
${COLOR_PALETTE.map(color => html `
|
|
430
|
+
<div class="color-swatch ${b.primaryColor === color ? 'color-swatch--selected' : ''}"
|
|
431
|
+
style="background: ${color}"
|
|
432
|
+
@click="${() => { this.config.branding.primaryColor = color; this.requestUpdate(); }}"
|
|
433
|
+
></div>
|
|
434
|
+
`)}
|
|
435
|
+
</div>
|
|
436
|
+
</div>
|
|
437
|
+
|
|
438
|
+
<div class="form-group">
|
|
439
|
+
<label class="form-label">Estilo del Sidebar</label>
|
|
440
|
+
<div class="style-grid">
|
|
441
|
+
${SIDEBAR_STYLES.map(s => html `
|
|
442
|
+
<div class="style-option ${b.sidebarStyle === s.value ? 'style-option--selected' : ''}"
|
|
443
|
+
@click="${() => { this.config.branding.sidebarStyle = s.value; this.requestUpdate(); }}">
|
|
444
|
+
<div class="style-preview" style="background: ${s.preview}"></div>
|
|
445
|
+
<div style="font-size:13px;font-weight:500;">${s.label}</div>
|
|
446
|
+
</div>
|
|
447
|
+
`)}
|
|
448
|
+
</div>
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<div class="form-group">
|
|
452
|
+
<label class="form-label">URL del Logo (opcional)</label>
|
|
453
|
+
<input class="zs-input" .value="${b.logo ?? ''}" placeholder="https://..." @input="${(e) => { this.config.branding.logo = e.target.value; this.requestUpdate(); }}" />
|
|
454
|
+
</div>
|
|
326
455
|
`;
|
|
327
456
|
}
|
|
328
457
|
// ─── Step 3: Navigation Editor ────────────────────
|
|
329
458
|
renderNavStep() {
|
|
330
459
|
if (!this.config)
|
|
331
460
|
return nothing;
|
|
332
|
-
return html `
|
|
333
|
-
<div class="nav-list">
|
|
334
|
-
${this.config.navigation.map((item, i) => html `
|
|
335
|
-
<div class="nav-item-row">
|
|
336
|
-
<span class="nav-item-icon" @click="${() => { this.editingNavIndex = i; this.showIconPicker = !this.showIconPicker; }}">${item.icon ?? (item.kind === 'header' ? '📌' : item.kind === 'divider' ? '—' : '📄')}</span>
|
|
461
|
+
return html `
|
|
462
|
+
<div class="nav-list">
|
|
463
|
+
${this.config.navigation.map((item, i) => html `
|
|
464
|
+
<div class="nav-item-row">
|
|
465
|
+
<span class="nav-item-icon" @click="${() => { this.editingNavIndex = i; this.showIconPicker = !this.showIconPicker; }}">${item.icon ?? (item.kind === 'header' ? '📌' : item.kind === 'divider' ? '—' : '📄')}</span>
|
|
337
466
|
${item.kind === 'divider'
|
|
338
467
|
? html `<span class="nav-item-title" style="color:var(--zs-text-muted);font-style:italic;">Separador</span>`
|
|
339
|
-
: html `<input class="zs-input" style="height:32px;font-size:13px;" .value="${item.title ?? ''}" @input="${(e) => { this.config.navigation[i].title = e.target.value; this.requestUpdate(); }}" />`}
|
|
340
|
-
<div class="nav-item-actions">
|
|
341
|
-
<button class="nav-item-btn" title="Subir" @click="${() => this.moveNav(i, -1)}">↑</button>
|
|
342
|
-
<button class="nav-item-btn" title="Bajar" @click="${() => this.moveNav(i, 1)}">↓</button>
|
|
343
|
-
<button class="nav-item-btn nav-item-btn--danger" title="Eliminar" @click="${() => { this.config.navigation.splice(i, 1); this.requestUpdate(); }}">✕</button>
|
|
344
|
-
</div>
|
|
345
|
-
</div>
|
|
346
|
-
`)}
|
|
347
|
-
</div>
|
|
348
|
-
|
|
349
|
-
${this.showIconPicker ? html `
|
|
350
|
-
<div style="margin-top:12px;padding:12px;border:1px solid var(--zs-border);border-radius:8px;">
|
|
351
|
-
<div style="font-size:12px;color:var(--zs-text-secondary);margin-bottom:8px;">Selecciona un icono:</div>
|
|
352
|
-
<div class="icon-grid">
|
|
353
|
-
${ICON_PALETTE.map(icon => html `
|
|
468
|
+
: html `<input class="zs-input" style="height:32px;font-size:13px;" .value="${item.title ?? ''}" @input="${(e) => { this.config.navigation[i].title = e.target.value; this.requestUpdate(); }}" />`}
|
|
469
|
+
<div class="nav-item-actions">
|
|
470
|
+
<button class="nav-item-btn" title="Subir" @click="${() => this.moveNav(i, -1)}">↑</button>
|
|
471
|
+
<button class="nav-item-btn" title="Bajar" @click="${() => this.moveNav(i, 1)}">↓</button>
|
|
472
|
+
<button class="nav-item-btn nav-item-btn--danger" title="Eliminar" @click="${() => { this.config.navigation.splice(i, 1); this.requestUpdate(); }}">✕</button>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
|
+
`)}
|
|
476
|
+
</div>
|
|
477
|
+
|
|
478
|
+
${this.showIconPicker ? html `
|
|
479
|
+
<div style="margin-top:12px;padding:12px;border:1px solid var(--zs-border);border-radius:8px;">
|
|
480
|
+
<div style="font-size:12px;color:var(--zs-text-secondary);margin-bottom:8px;">Selecciona un icono:</div>
|
|
481
|
+
<div class="icon-grid">
|
|
482
|
+
${ICON_PALETTE.map(icon => html `
|
|
354
483
|
<button class="icon-btn" @click="${() => {
|
|
355
484
|
if (this.editingNavIndex >= 0) {
|
|
356
485
|
this.config.navigation[this.editingNavIndex].icon = icon;
|
|
357
486
|
this.showIconPicker = false;
|
|
358
487
|
this.requestUpdate();
|
|
359
488
|
}
|
|
360
|
-
}}">${icon}</button>
|
|
361
|
-
`)}
|
|
362
|
-
</div>
|
|
363
|
-
</div>
|
|
364
|
-
` : ''}
|
|
365
|
-
|
|
366
|
-
<div style="display:flex;gap:8px;">
|
|
367
|
-
<button class="add-btn" @click="${() => this.addNavItem('page')}">➕ Pagina</button>
|
|
368
|
-
<button class="add-btn" @click="${() => this.addNavItem('header')}">📌 Seccion</button>
|
|
369
|
-
<button class="add-btn" @click="${() => this.addNavItem('divider')}">— Separador</button>
|
|
370
|
-
</div>
|
|
489
|
+
}}">${icon}</button>
|
|
490
|
+
`)}
|
|
491
|
+
</div>
|
|
492
|
+
</div>
|
|
493
|
+
` : ''}
|
|
494
|
+
|
|
495
|
+
<div style="display:flex;gap:8px;">
|
|
496
|
+
<button class="add-btn" @click="${() => this.addNavItem('page')}">➕ Pagina</button>
|
|
497
|
+
<button class="add-btn" @click="${() => this.addNavItem('header')}">📌 Seccion</button>
|
|
498
|
+
<button class="add-btn" @click="${() => this.addNavItem('divider')}">— Separador</button>
|
|
499
|
+
</div>
|
|
371
500
|
`;
|
|
372
501
|
}
|
|
373
502
|
addNavItem(kind) {
|
|
@@ -397,41 +526,149 @@ 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;
|
|
404
|
-
return html `
|
|
405
|
-
<div class="nav-list">
|
|
406
|
-
${this.config.pages.map((page, i) => html `
|
|
407
|
-
<div class="nav-item-row">
|
|
408
|
-
<span class="nav-item-icon">${this.getContentIcon(page.content)}</span>
|
|
409
|
-
<div style="flex:1;">
|
|
410
|
-
<input class="zs-input" style="height:30px;font-size:13px;margin-bottom:4px;" .value="${page.title}" @input="${(e) => { this.config.pages[i].title = e.target.value; this.requestUpdate(); }}" />
|
|
411
|
-
<div style="display:flex;gap:8px;align-items:center;">
|
|
412
|
-
<span style="font-size:11px;color:var(--zs-text-muted);">/${page.segment}</span>
|
|
413
|
-
<select style="font-size:11px;padding:2px 6px;border:1px solid var(--zs-border);border-radius:4px;" .value="${page.content}" @change="${(e) => { this.config.pages[i].content = e.target.value; this.requestUpdate(); }}">
|
|
414
|
-
<option value="empty">Vacia</option>
|
|
415
|
-
<option value="cards">Cards/Dashboard</option>
|
|
416
|
-
<option value="datagrid">Grid de Datos</option>
|
|
417
|
-
<option value="schema">Formulario</option>
|
|
418
|
-
<option value="chart">Graficos</option>
|
|
419
|
-
<option value="html">HTML</option>
|
|
420
|
-
<option value="iframe">iFrame</option>
|
|
421
|
-
<option value="tabs">Tabs</option>
|
|
422
|
-
<option value="custom">Custom</option>
|
|
423
|
-
</select>
|
|
424
|
-
</div>
|
|
425
|
-
</div>
|
|
426
|
-
<button class="nav-item-btn nav-item-btn--danger" @click="${() => { this.config.pages.splice(i, 1); this.requestUpdate(); }}">✕</button>
|
|
427
|
-
</div>
|
|
428
|
-
`)}
|
|
429
|
-
</div>
|
|
641
|
+
return html `
|
|
642
|
+
<div class="nav-list">
|
|
643
|
+
${this.config.pages.map((page, i) => html `
|
|
644
|
+
<div class="nav-item-row">
|
|
645
|
+
<span class="nav-item-icon">${this.getContentIcon(page.content)}</span>
|
|
646
|
+
<div style="flex:1;">
|
|
647
|
+
<input class="zs-input" style="height:30px;font-size:13px;margin-bottom:4px;" .value="${page.title}" @input="${(e) => { this.config.pages[i].title = e.target.value; this.requestUpdate(); }}" />
|
|
648
|
+
<div style="display:flex;gap:8px;align-items:center;">
|
|
649
|
+
<span style="font-size:11px;color:var(--zs-text-muted);">/${page.segment}</span>
|
|
650
|
+
<select style="font-size:11px;padding:2px 6px;border:1px solid var(--zs-border);border-radius:4px;" .value="${page.content}" @change="${(e) => { this.config.pages[i].content = e.target.value; this.requestUpdate(); }}">
|
|
651
|
+
<option value="empty">Vacia</option>
|
|
652
|
+
<option value="cards">Cards/Dashboard</option>
|
|
653
|
+
<option value="datagrid">Grid de Datos</option>
|
|
654
|
+
<option value="schema">Formulario</option>
|
|
655
|
+
<option value="chart">Graficos</option>
|
|
656
|
+
<option value="html">HTML</option>
|
|
657
|
+
<option value="iframe">iFrame</option>
|
|
658
|
+
<option value="tabs">Tabs</option>
|
|
659
|
+
<option value="custom">Custom</option>
|
|
660
|
+
</select>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
<button class="nav-item-btn nav-item-btn--danger" @click="${() => { this.config.pages.splice(i, 1); this.requestUpdate(); }}">✕</button>
|
|
664
|
+
</div>
|
|
665
|
+
`)}
|
|
666
|
+
</div>
|
|
430
667
|
<button class="add-btn" @click="${() => {
|
|
431
668
|
const id = `page-${Date.now()}`;
|
|
432
669
|
this.config.pages.push({ id, segment: id, title: 'Nueva Pagina', content: 'empty' });
|
|
433
670
|
this.requestUpdate();
|
|
434
|
-
}}">➕ Nueva Pagina</button>
|
|
671
|
+
}}">➕ Nueva Pagina</button>
|
|
435
672
|
`;
|
|
436
673
|
}
|
|
437
674
|
getContentIcon(type) {
|
|
@@ -442,26 +679,234 @@ 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;
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
803
|
+
const navPageCount = this.config.navigation.filter(n => n.kind !== 'divider' && n.kind !== 'header').length;
|
|
804
|
+
const dsCount = this.config.dataSources?.length ?? 0;
|
|
805
|
+
return html `
|
|
806
|
+
<div style="display:flex;gap:16px;margin-bottom:16px;align-items:center;flex-wrap:wrap;">
|
|
807
|
+
<div style="flex:1;min-width:200px;">
|
|
808
|
+
<div style="font-size:13px;color:var(--zs-text-secondary);margin-bottom:4px;">Resumen:</div>
|
|
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>
|
|
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
|
-
}}"
|
|
461
|
-
</div>
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
827
|
+
}}">📎 Copiar JSON</button>
|
|
828
|
+
</div>
|
|
829
|
+
|
|
830
|
+
<div class="preview-container">
|
|
831
|
+
<zentto-studio-app .config="${this.config}"></zentto-studio-app>
|
|
832
|
+
</div>
|
|
833
|
+
`;
|
|
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>
|
|
465
910
|
`;
|
|
466
911
|
}
|
|
467
912
|
};
|
|
@@ -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);
|