@dmitryvim/form-builder 0.1.22 → 0.1.24

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/demo.js CHANGED
@@ -3,572 +3,587 @@
3
3
 
4
4
  // Example schema for demonstration
5
5
  const EXAMPLE_SCHEMA = {
6
- "version": "0.3",
7
- "title": "Asset Uploader with Slides",
8
- "elements": [
6
+ version: "0.3",
7
+ title: "Asset Uploader with Slides",
8
+ elements: [
9
+ {
10
+ type: "file",
11
+ key: "cover",
12
+ label: "Изображение товара",
13
+ description:
14
+ "Source of facts: Use only the description text and visible labels/markings in the photo. Target scene: Show the product in a real-life usage context, highlighting its purpose and benefits. Product integrity: Do not change geometry, proportions, color, texture, hardware/stitching, or functional elements.",
15
+ required: true,
16
+ accept: {
17
+ extensions: ["png", "jpg", "gif"],
18
+ mime: ["image/png", "image/jpeg", "image/gif"],
19
+ },
20
+ maxSizeMB: 10,
21
+ },
22
+ {
23
+ type: "files",
24
+ key: "assets",
25
+ label: "Другие изображения",
26
+ description:
27
+ "Additional images that provide context, show different angles, or demonstrate product usage. These will be used to enhance the infographic composition.",
28
+ required: false,
29
+ accept: {
30
+ extensions: ["png", "jpg", "gif"],
31
+ mime: ["image/png", "image/jpeg", "image/gif"],
32
+ },
33
+ minCount: 0,
34
+ maxCount: 10,
35
+ maxSizeMB: 5,
36
+ },
37
+ {
38
+ type: "text",
39
+ key: "title",
40
+ label: "Название товара",
41
+ placeholder: "Введите название товара",
42
+ description:
43
+ "The exact product name as it should appear on the infographic. Use the official product name without marketing terms or decorative elements.",
44
+ required: true,
45
+ minLength: 1,
46
+ maxLength: 100,
47
+ pattern: "^[A-Za-zА-Яа-я0-9 _*-]+$",
48
+ default: "",
49
+ },
50
+ {
51
+ type: "textarea",
52
+ key: "description",
53
+ label: "Описание товара",
54
+ placeholder: "Введите описание товара",
55
+ description:
56
+ "Detailed product description focusing on key features, benefits, and usage scenarios. This text will be used to create compelling infographic content.",
57
+ required: false,
58
+ minLength: 0,
59
+ maxLength: 500,
60
+ pattern: "^[A-Za-zА-Яа-я0-9 ,.!?()_-]*$",
61
+ default: "",
62
+ },
63
+ {
64
+ type: "select",
65
+ key: "theme",
66
+ label: "Тема",
67
+ placeholder: "Выберите тему",
68
+ description:
69
+ "Visual theme that determines color scheme, typography, and overall aesthetic of the generated infographic.",
70
+ required: true,
71
+ options: [
72
+ { value: "", label: "Выберите тему" },
73
+ { value: "modern-minimal", label: "Современный минимализм" },
74
+ { value: "warm-vintage", label: "Теплый винтаж" },
75
+ { value: "tech-blue", label: "Технологичный синий" },
76
+ { value: "nature-green", label: "Природный зеленый" },
77
+ { value: "luxury-gold", label: "Роскошный золотой" },
78
+ ],
79
+ default: "",
80
+ },
81
+ {
82
+ type: "number",
83
+ key: "opacity",
84
+ label: "Прозрачность",
85
+ placeholder: "0.50",
86
+ description:
87
+ "Transparency level for background elements in the infographic. Values between 0 (fully transparent) and 1 (fully opaque).",
88
+ required: true,
89
+ min: 0,
90
+ max: 1,
91
+ decimals: 2,
92
+ step: 0.01,
93
+ default: 0.5,
94
+ },
95
+ {
96
+ type: "group",
97
+ key: "slides",
98
+ label: "Слайды",
99
+ description:
100
+ "Additional slides to showcase different aspects of your product. Each slide can have its own main image and decorative elements to create a comprehensive infographic presentation.",
101
+ repeat: { min: 1, max: 5 },
102
+ elements: [
9
103
  {
10
- "type": "file",
11
- "key": "cover",
12
- "label": "Изображение товара",
13
- "description": "Source of facts: Use only the description text and visible labels/markings in the photo. Target scene: Show the product in a real-life usage context, highlighting its purpose and benefits. Product integrity: Do not change geometry, proportions, color, texture, hardware/stitching, or functional elements.",
14
- "required": true,
15
- "accept": {
16
- "extensions": ["png", "jpg", "gif"],
17
- "mime": ["image/png", "image/jpeg", "image/gif"]
18
- },
19
- "maxSizeMB": 10
104
+ type: "text",
105
+ key: "title",
106
+ label: "Заголовок слайда",
107
+ required: true,
108
+ minLength: 1,
109
+ maxLength: 80,
110
+ pattern: "^[A-Za-zА-Яа-я0-9 _*-]+$",
111
+ default: "",
20
112
  },
21
113
  {
22
- "type": "files",
23
- "key": "assets",
24
- "label": "Другие изображения",
25
- "description": "Additional images that provide context, show different angles, or demonstrate product usage. These will be used to enhance the infographic composition.",
26
- "required": false,
27
- "accept": {
28
- "extensions": ["png", "jpg", "gif"],
29
- "mime": ["image/png", "image/jpeg", "image/gif"]
30
- },
31
- "minCount": 0,
32
- "maxCount": 10,
33
- "maxSizeMB": 5
114
+ type: "textarea",
115
+ key: "body",
116
+ label: "Текст слайда",
117
+ required: true,
118
+ minLength: 1,
119
+ maxLength: 1000,
120
+ pattern: "^[A-Za-zА-Яа-я0-9 ,.!?()_*-]*$",
121
+ default: "",
34
122
  },
35
- {
36
- "type": "text",
37
- "key": "title",
38
- "label": "Название товара",
39
- "placeholder": "Введите название товара",
40
- "description": "The exact product name as it should appear on the infographic. Use the official product name without marketing terms or decorative elements.",
41
- "required": true,
42
- "minLength": 1,
43
- "maxLength": 100,
44
- "pattern": "^[A-Za-zА-Яа-я0-9 _*-]+$",
45
- "default": ""
46
- },
47
- {
48
- "type": "textarea",
49
- "key": "description",
50
- "label": "Описание товара",
51
- "placeholder": "Введите описание товара",
52
- "description": "Detailed product description focusing on key features, benefits, and usage scenarios. This text will be used to create compelling infographic content.",
53
- "required": false,
54
- "minLength": 0,
55
- "maxLength": 500,
56
- "pattern": "^[A-Za-zА-Яа-я0-9 ,.!?()_-]*$",
57
- "default": ""
58
- },
59
- {
60
- "type": "select",
61
- "key": "theme",
62
- "label": "Тема",
63
- "placeholder": "Выберите тему",
64
- "description": "Visual theme that determines color scheme, typography, and overall aesthetic of the generated infographic.",
65
- "required": true,
66
- "options": [
67
- {"value": "", "label": "Выберите тему"},
68
- {"value": "modern-minimal", "label": "Современный минимализм"},
69
- {"value": "warm-vintage", "label": "Теплый винтаж"},
70
- {"value": "tech-blue", "label": "Технологичный синий"},
71
- {"value": "nature-green", "label": "Природный зеленый"},
72
- {"value": "luxury-gold", "label": "Роскошный золотой"}
73
- ],
74
- "default": ""
75
- },
76
- {
77
- "type": "number",
78
- "key": "opacity",
79
- "label": "Прозрачность",
80
- "placeholder": "0.50",
81
- "description": "Transparency level for background elements in the infographic. Values between 0 (fully transparent) and 1 (fully opaque).",
82
- "required": true,
83
- "min": 0,
84
- "max": 1,
85
- "decimals": 2,
86
- "step": 0.01,
87
- "default": 0.50
88
- },
89
- {
90
- "type": "group",
91
- "key": "slides",
92
- "label": "Слайды",
93
- "description": "Additional slides to showcase different aspects of your product. Each slide can have its own main image and decorative elements to create a comprehensive infographic presentation.",
94
- "repeat": {"min": 1, "max": 5},
95
- "elements": [
96
- {
97
- "type": "text",
98
- "key": "title",
99
- "label": "Заголовок слайда",
100
- "required": true,
101
- "minLength": 1,
102
- "maxLength": 80,
103
- "pattern": "^[A-Za-zА-Яа-я0-9 _*-]+$",
104
- "default": ""
105
- },
106
- {
107
- "type": "textarea",
108
- "key": "body",
109
- "label": "Текст слайда",
110
- "required": true,
111
- "minLength": 1,
112
- "maxLength": 1000,
113
- "pattern": "^[A-Za-zА-Яа-я0-9 ,.!?()_*-]*$",
114
- "default": ""
115
- }
116
- ]
117
- }
118
- ]
123
+ ],
124
+ },
125
+ ],
119
126
  };
120
127
 
121
128
  // DOM element references for demo app
122
129
  const el = {
123
- schemaInput: document.getElementById('schemaInput'),
124
- schemaErrors: document.getElementById('schemaErrors'),
125
- applySchemaBtn: document.getElementById('applySchemaBtn'),
126
- resetSchemaBtn: document.getElementById('resetSchemaBtn'),
127
- prettySchemaBtn: document.getElementById('prettySchemaBtn'),
128
- downloadSchemaBtn: document.getElementById('downloadSchemaBtn'),
129
- formContainer: document.getElementById('formContainer'),
130
- formErrors: document.getElementById('formErrors'),
131
- submitBtn: document.getElementById('submitBtn'),
132
- saveDraftBtn: document.getElementById('saveDraftBtn'),
133
- clearFormBtn: document.getElementById('clearFormBtn'),
134
- outputJson: document.getElementById('outputJson'),
135
- copyOutputBtn: document.getElementById('copyOutputBtn'),
136
- downloadOutputBtn: document.getElementById('downloadOutputBtn'),
137
- shareUrlBtn: document.getElementById('shareUrlBtn'),
138
- prefillInput: document.getElementById('prefillInput'),
139
- loadPrefillBtn: document.getElementById('loadPrefillBtn'),
140
- copyTemplateBtn: document.getElementById('copyTemplateBtn'),
141
- prefillErrors: document.getElementById('prefillErrors'),
142
- urlInfo: document.getElementById('urlInfo'),
143
- // Readonly demo elements
144
- readonlySchemaInput: document.getElementById('readonlySchemaInput'),
145
- applyReadonlyBtn: document.getElementById('applyReadonlyBtn'),
146
- clearReadonlyBtn: document.getElementById('clearReadonlyBtn'),
147
- readonlyErrors: document.getElementById('readonlyErrors'),
148
- readonlyDemoContainer: document.getElementById('readonlyDemoContainer')
130
+ schemaInput: document.getElementById("schemaInput"),
131
+ schemaErrors: document.getElementById("schemaErrors"),
132
+ applySchemaBtn: document.getElementById("applySchemaBtn"),
133
+ resetSchemaBtn: document.getElementById("resetSchemaBtn"),
134
+ prettySchemaBtn: document.getElementById("prettySchemaBtn"),
135
+ downloadSchemaBtn: document.getElementById("downloadSchemaBtn"),
136
+ formContainer: document.getElementById("formContainer"),
137
+ formErrors: document.getElementById("formErrors"),
138
+ submitBtn: document.getElementById("submitBtn"),
139
+ saveDraftBtn: document.getElementById("saveDraftBtn"),
140
+ clearFormBtn: document.getElementById("clearFormBtn"),
141
+ outputJson: document.getElementById("outputJson"),
142
+ copyOutputBtn: document.getElementById("copyOutputBtn"),
143
+ downloadOutputBtn: document.getElementById("downloadOutputBtn"),
144
+ shareUrlBtn: document.getElementById("shareUrlBtn"),
145
+ prefillInput: document.getElementById("prefillInput"),
146
+ loadPrefillBtn: document.getElementById("loadPrefillBtn"),
147
+ copyTemplateBtn: document.getElementById("copyTemplateBtn"),
148
+ prefillErrors: document.getElementById("prefillErrors"),
149
+ urlInfo: document.getElementById("urlInfo"),
150
+ // Readonly demo elements
151
+ readonlySchemaInput: document.getElementById("readonlySchemaInput"),
152
+ applyReadonlyBtn: document.getElementById("applyReadonlyBtn"),
153
+ clearReadonlyBtn: document.getElementById("clearReadonlyBtn"),
154
+ readonlyErrors: document.getElementById("readonlyErrors"),
155
+ readonlyDemoContainer: document.getElementById("readonlyDemoContainer"),
149
156
  };
150
157
 
151
158
  // Utility functions (using FormBuilder.pretty)
152
159
  function pretty(obj) {
153
- return FormBuilder.pretty(obj);
160
+ return FormBuilder.pretty(obj);
154
161
  }
155
162
 
156
163
  function showError(element, message) {
157
- if (element) {
158
- element.textContent = message;
159
- element.classList.remove('hidden');
160
- }
164
+ if (element) {
165
+ element.textContent = message;
166
+ element.classList.remove("hidden");
167
+ }
161
168
  }
162
169
 
163
170
  function clearError(element) {
164
- if (element) {
165
- element.textContent = '';
166
- element.classList.add('hidden');
167
- }
171
+ if (element) {
172
+ element.textContent = "";
173
+ element.classList.add("hidden");
174
+ }
168
175
  }
169
176
 
170
177
  function downloadFile(filename, content) {
171
- const blob = new Blob([content], { type: 'text/plain' });
172
- const url = URL.createObjectURL(blob);
173
- const a = document.createElement('a');
174
- a.href = url;
175
- a.download = filename;
176
- a.click();
177
- URL.revokeObjectURL(url);
178
+ const blob = new Blob([content], { type: "text/plain" });
179
+ const url = URL.createObjectURL(blob);
180
+ const a = document.createElement("a");
181
+ a.href = url;
182
+ a.download = filename;
183
+ a.click();
184
+ URL.revokeObjectURL(url);
178
185
  }
179
186
 
180
187
  // Demo event handlers
181
- el.applySchemaBtn.addEventListener('click', () => {
182
- clearError(el.schemaErrors);
183
- try {
184
- const schema = JSON.parse(el.schemaInput.value);
185
- const errors = FormBuilder.validateSchema(schema);
186
-
187
- if (errors.length > 0) {
188
- showError(el.schemaErrors, 'Schema validation errors: ' + errors.join(', '));
189
- return;
190
- }
191
-
192
- FormBuilder.renderForm(schema, {});
193
- el.outputJson.value = '';
194
- clearError(el.formErrors);
195
- } catch (e) {
196
- showError(el.schemaErrors, 'JSON parse error: ' + e.message);
188
+ el.applySchemaBtn.addEventListener("click", () => {
189
+ clearError(el.schemaErrors);
190
+ try {
191
+ const schema = JSON.parse(el.schemaInput.value);
192
+ const errors = FormBuilder.validateSchema(schema);
193
+
194
+ if (errors.length > 0) {
195
+ showError(
196
+ el.schemaErrors,
197
+ "Schema validation errors: " + errors.join(", "),
198
+ );
199
+ return;
197
200
  }
201
+
202
+ FormBuilder.renderForm(schema, {});
203
+ el.outputJson.value = "";
204
+ clearError(el.formErrors);
205
+ } catch (e) {
206
+ showError(el.schemaErrors, "JSON parse error: " + e.message);
207
+ }
198
208
  });
199
209
 
200
- el.resetSchemaBtn.addEventListener('click', () => {
201
- el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
202
- clearError(el.schemaErrors);
203
- FormBuilder.renderForm(EXAMPLE_SCHEMA, {});
204
- el.outputJson.value = '';
205
- el.prefillInput.value = '';
206
- clearError(el.prefillErrors);
210
+ el.resetSchemaBtn.addEventListener("click", () => {
211
+ el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
212
+ clearError(el.schemaErrors);
213
+ FormBuilder.renderForm(EXAMPLE_SCHEMA, {});
214
+ el.outputJson.value = "";
215
+ el.prefillInput.value = "";
216
+ clearError(el.prefillErrors);
207
217
  });
208
218
 
209
- el.prettySchemaBtn.addEventListener('click', () => {
210
- try {
211
- const parsed = JSON.parse(el.schemaInput.value);
212
- el.schemaInput.value = pretty(parsed);
213
- } catch (e) {
214
- showError(el.schemaErrors, 'Prettify: JSON parse error: ' + e.message);
215
- }
219
+ el.prettySchemaBtn.addEventListener("click", () => {
220
+ try {
221
+ const parsed = JSON.parse(el.schemaInput.value);
222
+ el.schemaInput.value = pretty(parsed);
223
+ } catch (e) {
224
+ showError(el.schemaErrors, "Prettify: JSON parse error: " + e.message);
225
+ }
216
226
  });
217
227
 
218
- el.downloadSchemaBtn.addEventListener('click', () => {
219
- downloadFile('schema.json', el.schemaInput.value || pretty(EXAMPLE_SCHEMA));
228
+ el.downloadSchemaBtn.addEventListener("click", () => {
229
+ downloadFile("schema.json", el.schemaInput.value || pretty(EXAMPLE_SCHEMA));
220
230
  });
221
231
 
222
- el.clearFormBtn.addEventListener('click', () => {
223
- FormBuilder.clearForm();
224
- clearError(el.formErrors);
232
+ el.clearFormBtn.addEventListener("click", () => {
233
+ FormBuilder.clearForm();
234
+ clearError(el.formErrors);
225
235
  });
226
236
 
227
- el.copyOutputBtn.addEventListener('click', async () => {
228
- try {
229
- await navigator.clipboard.writeText(el.outputJson.value || '');
230
- el.copyOutputBtn.textContent = 'Copied!';
231
- setTimeout(() => {
232
- el.copyOutputBtn.textContent = 'Copy JSON';
233
- }, 1000);
234
- } catch (e) {
235
- console.warn('Copy failed:', e);
236
- }
237
+ el.copyOutputBtn.addEventListener("click", async () => {
238
+ try {
239
+ await navigator.clipboard.writeText(el.outputJson.value || "");
240
+ el.copyOutputBtn.textContent = "Copied!";
241
+ setTimeout(() => {
242
+ el.copyOutputBtn.textContent = "Copy JSON";
243
+ }, 1000);
244
+ } catch (e) {
245
+ console.warn("Copy failed:", e);
246
+ }
237
247
  });
238
248
 
239
- el.downloadOutputBtn.addEventListener('click', () => {
240
- downloadFile('form-data.json', el.outputJson.value || '{}');
249
+ el.downloadOutputBtn.addEventListener("click", () => {
250
+ downloadFile("form-data.json", el.outputJson.value || "{}");
241
251
  });
242
252
 
243
- el.shareUrlBtn.addEventListener('click', () => {
244
- try {
245
- const schema = JSON.parse(el.schemaInput.value);
246
- const schemaBase64 = btoa(JSON.stringify(schema));
247
- const url = `${window.location.origin}${window.location.pathname}?schema=${schemaBase64}`;
248
- navigator.clipboard.writeText(url);
249
- el.shareUrlBtn.textContent = 'URL Copied!';
250
- setTimeout(() => {
251
- el.shareUrlBtn.textContent = 'Share URL';
252
- }, 2000);
253
- } catch (e) {
254
- alert('Please apply a valid schema first');
255
- }
253
+ el.shareUrlBtn.addEventListener("click", () => {
254
+ try {
255
+ const schema = JSON.parse(el.schemaInput.value);
256
+ const schemaBase64 = btoa(JSON.stringify(schema));
257
+ const url = `${window.location.origin}${window.location.pathname}?schema=${schemaBase64}`;
258
+ navigator.clipboard.writeText(url);
259
+ el.shareUrlBtn.textContent = "URL Copied!";
260
+ setTimeout(() => {
261
+ el.shareUrlBtn.textContent = "Share URL";
262
+ }, 2000);
263
+ } catch (e) {
264
+ alert("Please apply a valid schema first");
265
+ }
256
266
  });
257
267
 
258
- el.loadPrefillBtn.addEventListener('click', () => {
259
- clearError(el.prefillErrors);
260
- try {
261
- const pre = JSON.parse(el.prefillInput.value || '{}');
262
- const currentSchema = JSON.parse(el.schemaInput.value);
263
- FormBuilder.renderForm(currentSchema, pre);
264
- el.outputJson.value = '';
265
- clearError(el.formErrors);
266
- } catch (e) {
267
- showError(el.prefillErrors, 'JSON parse error: ' + e.message);
268
- }
268
+ el.loadPrefillBtn.addEventListener("click", () => {
269
+ clearError(el.prefillErrors);
270
+ try {
271
+ const pre = JSON.parse(el.prefillInput.value || "{}");
272
+ const currentSchema = JSON.parse(el.schemaInput.value);
273
+ FormBuilder.renderForm(currentSchema, pre);
274
+ el.outputJson.value = "";
275
+ clearError(el.formErrors);
276
+ } catch (e) {
277
+ showError(el.prefillErrors, "JSON parse error: " + e.message);
278
+ }
269
279
  });
270
280
 
271
- el.copyTemplateBtn.addEventListener('click', () => {
272
- try {
273
- const schema = JSON.parse(el.schemaInput.value);
274
- const template = {};
275
-
276
- function processElements(elements, target) {
277
- elements.forEach(element => {
278
- if (element.type === 'group' && element.repeat) {
279
- target[element.key] = [{}];
280
- if (element.elements) {
281
- processElements(element.elements, target[element.key][0]);
282
- }
283
- } else if (element.type === 'group') {
284
- target[element.key] = {};
285
- if (element.elements) {
286
- processElements(element.elements, target[element.key]);
287
- }
288
- } else {
289
- target[element.key] = element.default || null;
290
- }
291
- });
281
+ el.copyTemplateBtn.addEventListener("click", () => {
282
+ try {
283
+ const schema = JSON.parse(el.schemaInput.value);
284
+ const template = {};
285
+
286
+ function processElements(elements, target) {
287
+ elements.forEach((element) => {
288
+ if (element.type === "group" && element.repeat) {
289
+ target[element.key] = [{}];
290
+ if (element.elements) {
291
+ processElements(element.elements, target[element.key][0]);
292
+ }
293
+ } else if (element.type === "group") {
294
+ target[element.key] = {};
295
+ if (element.elements) {
296
+ processElements(element.elements, target[element.key]);
297
+ }
298
+ } else {
299
+ target[element.key] = element.default || null;
292
300
  }
293
-
294
- processElements(schema.elements, template);
295
- el.prefillInput.value = pretty(template);
296
- } catch (e) {
297
- showError(el.prefillErrors, 'Schema parse error: ' + e.message);
301
+ });
298
302
  }
303
+
304
+ processElements(schema.elements, template);
305
+ el.prefillInput.value = pretty(template);
306
+ } catch (e) {
307
+ showError(el.prefillErrors, "Schema parse error: " + e.message);
308
+ }
299
309
  });
300
310
 
301
311
  // Form submission handlers
302
312
  function submitFormEnhanced() {
303
- clearError(el.formErrors);
304
-
305
- const result = FormBuilder.getFormData();
306
-
307
- if (!result.valid) {
308
- showError(el.formErrors, 'Validation errors: ' + result.errors.join(', '));
309
- return;
310
- }
311
-
312
- // Convert resource IDs to file info for demo
313
- const processedData = JSON.parse(JSON.stringify(result.data));
314
-
315
- function replaceResourceIds(obj) {
316
- for (const key in obj) {
317
- if (typeof obj[key] === 'string' && obj[key].startsWith('res_')) {
318
- const meta = FormBuilder.state.resourceIndex.get(obj[key]);
319
- if (meta) {
320
- obj[key] = {
321
- resourceId: obj[key],
322
- name: meta.name,
323
- type: meta.type,
324
- size: meta.size
325
- };
326
- }
327
- } else if (Array.isArray(obj[key])) {
328
- obj[key].forEach(item => {
329
- if (typeof item === 'string' && item.startsWith('res_')) {
330
- const index = obj[key].indexOf(item);
331
- const meta = FormBuilder.state.resourceIndex.get(item);
332
- if (meta) {
333
- obj[key][index] = {
334
- resourceId: item,
335
- name: meta.name,
336
- type: meta.type,
337
- size: meta.size
338
- };
339
- }
340
- } else if (typeof item === 'object') {
341
- replaceResourceIds(item);
342
- }
343
- });
344
- } else if (typeof obj[key] === 'object' && obj[key] !== null) {
345
- replaceResourceIds(obj[key]);
346
- }
313
+ clearError(el.formErrors);
314
+
315
+ const result = FormBuilder.getFormData();
316
+
317
+ if (!result.valid) {
318
+ showError(el.formErrors, "Validation errors: " + result.errors.join(", "));
319
+ return;
320
+ }
321
+
322
+ // Convert resource IDs to file info for demo
323
+ const processedData = JSON.parse(JSON.stringify(result.data));
324
+
325
+ function replaceResourceIds(obj) {
326
+ for (const key in obj) {
327
+ if (typeof obj[key] === "string" && obj[key].startsWith("res_")) {
328
+ const meta = FormBuilder.state.resourceIndex.get(obj[key]);
329
+ if (meta) {
330
+ obj[key] = {
331
+ resourceId: obj[key],
332
+ name: meta.name,
333
+ type: meta.type,
334
+ size: meta.size,
335
+ };
347
336
  }
337
+ } else if (Array.isArray(obj[key])) {
338
+ obj[key].forEach((item) => {
339
+ if (typeof item === "string" && item.startsWith("res_")) {
340
+ const index = obj[key].indexOf(item);
341
+ const meta = FormBuilder.state.resourceIndex.get(item);
342
+ if (meta) {
343
+ obj[key][index] = {
344
+ resourceId: item,
345
+ name: meta.name,
346
+ type: meta.type,
347
+ size: meta.size,
348
+ };
349
+ }
350
+ } else if (typeof item === "object") {
351
+ replaceResourceIds(item);
352
+ }
353
+ });
354
+ } else if (typeof obj[key] === "object" && obj[key] !== null) {
355
+ replaceResourceIds(obj[key]);
356
+ }
348
357
  }
349
-
350
- replaceResourceIds(processedData);
351
-
352
- el.outputJson.value = pretty(processedData);
358
+ }
359
+
360
+ replaceResourceIds(processedData);
361
+
362
+ el.outputJson.value = pretty(processedData);
353
363
  }
354
364
 
355
- el.submitBtn.addEventListener('click', submitFormEnhanced);
356
- el.saveDraftBtn.addEventListener('click', () => {
357
- const result = FormBuilder.getFormData();
358
- el.outputJson.value = pretty(result.data);
365
+ el.submitBtn.addEventListener("click", submitFormEnhanced);
366
+ el.saveDraftBtn.addEventListener("click", () => {
367
+ const result = FormBuilder.getFormData();
368
+ el.outputJson.value = pretty(result.data);
359
369
  });
360
370
 
361
371
  // Readonly demo handlers
362
- el.applyReadonlyBtn.addEventListener('click', () => {
363
- clearError(el.readonlyErrors);
372
+ el.applyReadonlyBtn.addEventListener("click", () => {
373
+ clearError(el.readonlyErrors);
374
+ try {
375
+ const schema = JSON.parse(el.readonlySchemaInput.value);
376
+ const errors = FormBuilder.validateSchema(schema);
377
+
378
+ if (errors.length > 0) {
379
+ showError(
380
+ el.readonlyErrors,
381
+ "Schema validation errors: " + errors.join(", "),
382
+ );
383
+ return;
384
+ }
385
+
386
+ // Clear container
387
+ el.readonlyDemoContainer.innerHTML = "";
388
+
389
+ // Store original form settings
390
+ const originalFormRoot = FormBuilder.state.formRoot;
391
+ const originalMode = FormBuilder.state.config.readonly;
392
+
364
393
  try {
365
- const schema = JSON.parse(el.readonlySchemaInput.value);
366
- const errors = FormBuilder.validateSchema(schema);
367
-
368
- if (errors.length > 0) {
369
- showError(el.readonlyErrors, 'Schema validation errors: ' + errors.join(', '));
370
- return;
371
- }
372
-
373
- // Clear container
374
- el.readonlyDemoContainer.innerHTML = '';
375
-
376
- // Store original form settings
377
- const originalFormRoot = FormBuilder.state.formRoot;
378
- const originalMode = FormBuilder.state.config.readonly;
379
-
380
- try {
381
- // Temporarily use readonly container
382
- FormBuilder.setFormRoot(el.readonlyDemoContainer);
383
- FormBuilder.setMode('readonly');
384
- FormBuilder.renderForm(schema, DEFAULT_READONLY_DATA);
385
- } finally {
386
- // Restore original settings
387
- FormBuilder.setFormRoot(originalFormRoot);
388
- FormBuilder.state.config.readonly = originalMode;
389
- }
390
-
391
- } catch (e) {
392
- showError(el.readonlyErrors, 'JSON parse error: ' + e.message);
394
+ // Temporarily use readonly container
395
+ FormBuilder.setFormRoot(el.readonlyDemoContainer);
396
+ FormBuilder.setMode("readonly");
397
+ FormBuilder.renderForm(schema, DEFAULT_READONLY_DATA);
398
+ } finally {
399
+ // Restore original settings
400
+ FormBuilder.setFormRoot(originalFormRoot);
401
+ FormBuilder.state.config.readonly = originalMode;
393
402
  }
403
+ } catch (e) {
404
+ showError(el.readonlyErrors, "JSON parse error: " + e.message);
405
+ }
394
406
  });
395
407
 
396
- el.clearReadonlyBtn.addEventListener('click', () => {
397
- el.readonlyDemoContainer.innerHTML = '<div class="text-center text-gray-500 py-8">Apply a schema to see readonly mode</div>';
398
- clearError(el.readonlyErrors);
408
+ el.clearReadonlyBtn.addEventListener("click", () => {
409
+ el.readonlyDemoContainer.innerHTML =
410
+ '<div class="text-center text-gray-500 py-8">Apply a schema to see readonly mode</div>';
411
+ clearError(el.readonlyErrors);
399
412
  });
400
413
 
401
414
  // URL schema loading
402
415
  function loadSchemaFromURL() {
403
- const urlParams = new URLSearchParams(window.location.search);
404
- const schemaParam = urlParams.get('schema');
405
-
406
- if (schemaParam) {
407
- try {
408
- const schema = JSON.parse(atob(schemaParam));
409
- el.schemaInput.value = pretty(schema);
410
- FormBuilder.renderForm(schema, {});
411
- el.urlInfo.classList.remove('hidden');
412
- } catch (e) {
413
- console.warn('Failed to load schema from URL:', e);
414
- }
416
+ const urlParams = new URLSearchParams(window.location.search);
417
+ const schemaParam = urlParams.get("schema");
418
+
419
+ if (schemaParam) {
420
+ try {
421
+ const schema = JSON.parse(atob(schemaParam));
422
+ el.schemaInput.value = pretty(schema);
423
+ FormBuilder.renderForm(schema, {});
424
+ el.urlInfo.classList.remove("hidden");
425
+ } catch (e) {
426
+ console.warn("Failed to load schema from URL:", e);
415
427
  }
428
+ }
416
429
  }
417
430
 
418
431
  // Read-only demo functions
419
432
  function downloadDemoFile(filePath, fileName) {
420
- console.log('downloadDemoFile called with:', filePath, fileName);
421
-
422
- // Check if we're running on a local dev server
423
- const isLocalDev = window.location.protocol === 'http:' && window.location.hostname === 'localhost';
424
-
425
- if (isLocalDev) {
426
- console.log('Running on local dev server, using fetch method');
427
- // Force download by fetching the file and creating a blob
428
- fetch(filePath)
429
- .then(response => {
430
- console.log('Fetch response:', response.ok, response.status);
431
- return response.blob();
432
- })
433
- .then(blob => {
434
- console.log('Creating download link for blob:', blob.size, 'bytes');
435
- const link = document.createElement('a');
436
- link.href = URL.createObjectURL(blob);
437
- link.download = fileName;
438
- document.body.appendChild(link);
439
- link.click();
440
- document.body.removeChild(link);
441
- URL.revokeObjectURL(link.href);
442
- console.log('Download completed successfully');
443
- })
444
- .catch(error => {
445
- console.error('Fetch download failed:', error);
446
- simpleLinkDownload(filePath, fileName);
447
- });
448
- } else {
449
- console.log('Not on local dev server, using simple link method');
433
+ console.log("downloadDemoFile called with:", filePath, fileName);
434
+
435
+ // Check if we're running on a local dev server
436
+ const isLocalDev =
437
+ window.location.protocol === "http:" &&
438
+ window.location.hostname === "localhost";
439
+
440
+ if (isLocalDev) {
441
+ console.log("Running on local dev server, using fetch method");
442
+ // Force download by fetching the file and creating a blob
443
+ fetch(filePath)
444
+ .then((response) => {
445
+ console.log("Fetch response:", response.ok, response.status);
446
+ return response.blob();
447
+ })
448
+ .then((blob) => {
449
+ console.log("Creating download link for blob:", blob.size, "bytes");
450
+ const link = document.createElement("a");
451
+ link.href = URL.createObjectURL(blob);
452
+ link.download = fileName;
453
+ document.body.appendChild(link);
454
+ link.click();
455
+ document.body.removeChild(link);
456
+ URL.revokeObjectURL(link.href);
457
+ console.log("Download completed successfully");
458
+ })
459
+ .catch((error) => {
460
+ console.error("Fetch download failed:", error);
450
461
  simpleLinkDownload(filePath, fileName);
451
- }
462
+ });
463
+ } else {
464
+ console.log("Not on local dev server, using simple link method");
465
+ simpleLinkDownload(filePath, fileName);
466
+ }
452
467
  }
453
468
 
454
469
  function simpleLinkDownload(filePath, fileName) {
455
- console.log('Using simple link download:', filePath, fileName);
456
- const link = document.createElement('a');
457
- link.href = filePath;
458
- link.download = fileName;
459
- link.style.display = 'none';
460
- document.body.appendChild(link);
461
- link.click();
462
- document.body.removeChild(link);
463
- console.log('Simple link download triggered');
470
+ console.log("Using simple link download:", filePath, fileName);
471
+ const link = document.createElement("a");
472
+ link.href = filePath;
473
+ link.download = fileName;
474
+ link.style.display = "none";
475
+ document.body.appendChild(link);
476
+ link.click();
477
+ document.body.removeChild(link);
478
+ console.log("Simple link download triggered");
464
479
  }
465
480
 
466
481
  // Configure download handler for readonly demo
467
482
  function setupReadonlyDemo() {
468
- // Set up download handler that works with local demo files
469
- FormBuilder.setDownloadHandler((resourceId, fileName) => {
470
- const demoFileMap = {
471
- 'demo_infographic': 'images/infographic_draft.jpg',
472
- 'demo_video': 'images/final_video.mp4'
473
- };
474
-
475
- const filePath = demoFileMap[resourceId] || `images/${fileName}`;
476
- downloadDemoFile(filePath, fileName);
477
- });
478
-
479
- // Set up thumbnail handler for demo (returns URL directly, not Promise)
480
- FormBuilder.setThumbnailHandler((resourceId) => {
481
- const demoFileMap = {
482
- 'demo_infographic': 'images/infographic_draft.jpg',
483
- 'demo_video': 'images/final_video.mp4'
484
- };
485
-
486
- return demoFileMap[resourceId] || null;
487
- });
488
-
489
- // Pre-populate demo resource metadata
490
- FormBuilder.state.resourceIndex.set('demo_infographic', {
491
- name: 'infographic_result.jpg',
492
- type: 'image/jpeg',
493
- size: 150000,
494
- file: null
495
- });
496
-
497
- FormBuilder.state.resourceIndex.set('demo_video', {
498
- name: 'final_video.mp4',
499
- type: 'video/mp4',
500
- size: 2500000,
501
- file: null
502
- });
483
+ // Set up download handler that works with local demo files
484
+ FormBuilder.setDownloadHandler((resourceId, fileName) => {
485
+ const demoFileMap = {
486
+ demo_infographic: "images/infographic_draft.jpg",
487
+ demo_video: "images/final_video.mp4",
488
+ };
489
+
490
+ const filePath = demoFileMap[resourceId] || `images/${fileName}`;
491
+ downloadDemoFile(filePath, fileName);
492
+ });
493
+
494
+ // Set up thumbnail handler for demo (returns URL directly, not Promise)
495
+ FormBuilder.setThumbnailHandler((resourceId) => {
496
+ const demoFileMap = {
497
+ demo_infographic: "images/infographic_draft.jpg",
498
+ demo_video: "images/final_video.mp4",
499
+ };
500
+
501
+ return demoFileMap[resourceId] || null;
502
+ });
503
+
504
+ // Pre-populate demo resource metadata
505
+ FormBuilder.state.resourceIndex.set("demo_infographic", {
506
+ name: "infographic_result.jpg",
507
+ type: "image/jpeg",
508
+ size: 150000,
509
+ file: null,
510
+ });
511
+
512
+ FormBuilder.state.resourceIndex.set("demo_video", {
513
+ name: "final_video.mp4",
514
+ type: "video/mp4",
515
+ size: 2500000,
516
+ file: null,
517
+ });
503
518
  }
504
519
 
505
520
  // Default readonly schema for demo
506
521
  const DEFAULT_READONLY_SCHEMA = {
507
- "version": "0.3",
508
- "title": "Результаты работы",
509
- "elements": [
510
- {
511
- "type": "file",
512
- "key": "result_image",
513
- "label": "Изображение результата",
514
- "description": "Готовая инфографика на основе загруженных данных"
515
- },
516
- {
517
- "type": "file",
518
- "key": "result_video",
519
- "label": "Видео результат",
520
- "description": "Финальное видео презентации"
521
- }
522
- ]
522
+ version: "0.3",
523
+ title: "Результаты работы",
524
+ elements: [
525
+ {
526
+ type: "file",
527
+ key: "result_image",
528
+ label: "Изображение результата",
529
+ description: "Готовая инфографика на основе загруженных данных",
530
+ },
531
+ {
532
+ type: "file",
533
+ key: "result_video",
534
+ label: "Видео результат",
535
+ description: "Финальное видео презентации",
536
+ },
537
+ ],
523
538
  };
524
539
 
525
- // Default readonly data
540
+ // Default readonly data
526
541
  const DEFAULT_READONLY_DATA = {
527
- "result_image": "demo_infographic",
528
- "result_video": "demo_video"
542
+ result_image: "demo_infographic",
543
+ result_video: "demo_video",
529
544
  };
530
545
 
531
546
  function renderReadonlyDemo() {
532
- // Set default schema in textarea
533
- const textarea = document.getElementById('readonlySchemaInput');
534
- if (textarea && !textarea.value.trim()) {
535
- textarea.value = pretty(DEFAULT_READONLY_SCHEMA);
536
- }
547
+ // Set default schema in textarea
548
+ const textarea = document.getElementById("readonlySchemaInput");
549
+ if (textarea && !textarea.value.trim()) {
550
+ textarea.value = pretty(DEFAULT_READONLY_SCHEMA);
551
+ }
537
552
  }
538
553
 
539
554
  // Initialize demo application
540
555
  function initDemo() {
541
- // Set up the form builder for main form
542
- FormBuilder.setFormRoot(el.formContainer);
543
-
544
- // Set up handlers for readonly demo
545
- setupReadonlyDemo();
546
-
547
- // Initialize with example schema
548
- el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
549
- FormBuilder.setMode('edit'); // Ensure main form is in edit mode
550
- FormBuilder.renderForm(EXAMPLE_SCHEMA, {});
551
-
552
- // Load schema from URL if present
553
- loadSchemaFromURL();
554
-
555
- // Initialize read-only demo
556
- setTimeout(renderReadonlyDemo, 500);
556
+ // Set up the form builder for main form
557
+ FormBuilder.setFormRoot(el.formContainer);
558
+
559
+ // Set up handlers for readonly demo
560
+ setupReadonlyDemo();
561
+
562
+ // Initialize with example schema
563
+ el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
564
+ FormBuilder.setMode("edit"); // Ensure main form is in edit mode
565
+ FormBuilder.renderForm(EXAMPLE_SCHEMA, {});
566
+
567
+ // Load schema from URL if present
568
+ loadSchemaFromURL();
569
+
570
+ // Initialize read-only demo
571
+ setTimeout(renderReadonlyDemo, 500);
557
572
  }
558
573
 
559
574
  // Start the demo when the page loads
560
- document.addEventListener('DOMContentLoaded', () => {
561
- // Wait for FormBuilder to be available
562
- if (typeof FormBuilder !== 'undefined') {
575
+ document.addEventListener("DOMContentLoaded", () => {
576
+ // Wait for FormBuilder to be available
577
+ if (typeof FormBuilder !== "undefined") {
578
+ initDemo();
579
+ } else {
580
+ // Fallback: wait a bit for scripts to load
581
+ setTimeout(() => {
582
+ if (typeof FormBuilder !== "undefined") {
563
583
  initDemo();
564
- } else {
565
- // Fallback: wait a bit for scripts to load
566
- setTimeout(() => {
567
- if (typeof FormBuilder !== 'undefined') {
568
- initDemo();
569
- } else {
570
- console.error('FormBuilder not found!');
571
- }
572
- }, 100);
573
- }
584
+ } else {
585
+ console.error("FormBuilder not found!");
586
+ }
587
+ }, 100);
588
+ }
574
589
  });