@dmitryvim/form-builder 0.1.25 → 0.1.28

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
@@ -1,4 +1,4 @@
1
- // Demo Application for Form Builder
1
+ // Demo Application for Form Builder - Redesigned 3-Column Layout
2
2
  // This file contains demo-specific code and should not be part of the core library
3
3
 
4
4
  // Example schema for demonstration
@@ -92,6 +92,19 @@ const EXAMPLE_SCHEMA = {
92
92
  step: 0.01,
93
93
  default: 0.5,
94
94
  },
95
+ {
96
+ type: "file",
97
+ key: "video",
98
+ label: "Video Content",
99
+ description:
100
+ "Optional video content to enhance the infographic. Supports MP4, WebM, and MOV formats for dynamic product demonstrations.",
101
+ required: false,
102
+ accept: {
103
+ extensions: ["mp4", "webm", "mov"],
104
+ mime: ["video/mp4", "video/webm", "video/quicktime"],
105
+ },
106
+ maxSizeMB: 50,
107
+ },
95
108
  {
96
109
  type: "group",
97
110
  key: "slides",
@@ -125,37 +138,129 @@ const EXAMPLE_SCHEMA = {
125
138
  ],
126
139
  };
127
140
 
128
- // DOM element references for demo app
141
+ // In-Memory File Storage System
142
+ class InMemoryFileStorage {
143
+ constructor() {
144
+ this.files = new Map(); // resourceId -> { blob, metadata }
145
+ this.resourceCounter = 0;
146
+ }
147
+
148
+ // Generate unique resource ID
149
+ generateResourceId() {
150
+ return `res_${Date.now()}_${++this.resourceCounter}`;
151
+ }
152
+
153
+ // Store file in memory and return resource ID
154
+ async storeFile(file) {
155
+ const resourceId = this.generateResourceId();
156
+
157
+ // Create blob from file
158
+ const blob = new Blob([file], { type: file.type });
159
+
160
+ // Store file data
161
+ this.files.set(resourceId, {
162
+ blob,
163
+ metadata: {
164
+ name: file.name,
165
+ type: file.type,
166
+ size: file.size,
167
+ uploadedAt: new Date().toISOString(),
168
+ },
169
+ });
170
+
171
+ return resourceId;
172
+ }
173
+
174
+ // Retrieve file blob by resource ID
175
+ getFile(resourceId) {
176
+ const fileData = this.files.get(resourceId);
177
+ return fileData ? fileData.blob : null;
178
+ }
179
+
180
+ // Get file metadata
181
+ getMetadata(resourceId) {
182
+ const fileData = this.files.get(resourceId);
183
+ return fileData ? fileData.metadata : null;
184
+ }
185
+
186
+ // Generate thumbnail URL for preview
187
+ getThumbnailUrl(resourceId) {
188
+ const blob = this.getFile(resourceId);
189
+ if (!blob) return null;
190
+
191
+ // For images and videos, return object URL for preview
192
+ if (blob.type.startsWith("image/") || blob.type.startsWith("video/")) {
193
+ return URL.createObjectURL(blob);
194
+ }
195
+
196
+ // For other files, return null (no thumbnail)
197
+ return null;
198
+ }
199
+
200
+ // Download file from memory
201
+ downloadFile(resourceId, fileName) {
202
+ const blob = this.getFile(resourceId);
203
+ const metadata = this.getMetadata(resourceId);
204
+
205
+ if (!blob) {
206
+ console.error("File not found:", resourceId);
207
+ return;
208
+ }
209
+
210
+ // Use provided fileName or fall back to original name
211
+ const downloadName = fileName || metadata.name;
212
+
213
+ // Create download link
214
+ const url = URL.createObjectURL(blob);
215
+ const link = document.createElement("a");
216
+ link.href = url;
217
+ link.download = downloadName;
218
+ document.body.appendChild(link);
219
+ link.click();
220
+ document.body.removeChild(link);
221
+
222
+ // Clean up object URL
223
+ setTimeout(() => URL.revokeObjectURL(url), 100);
224
+ }
225
+
226
+ // Clear all stored files
227
+ clear() {
228
+ // Revoke any object URLs that might be in use
229
+ this.files.forEach((fileData, resourceId) => {
230
+ const thumbnailUrl = this.getThumbnailUrl(resourceId);
231
+ if (thumbnailUrl) {
232
+ URL.revokeObjectURL(thumbnailUrl);
233
+ }
234
+ });
235
+
236
+ this.files.clear();
237
+ this.resourceCounter = 0;
238
+ }
239
+ }
240
+
241
+ // Initialize file storage
242
+ const fileStorage = new InMemoryFileStorage();
243
+
244
+ // DOM element references
129
245
  const el = {
130
246
  schemaInput: document.getElementById("schemaInput"),
131
247
  schemaErrors: document.getElementById("schemaErrors"),
132
248
  applySchemaBtn: document.getElementById("applySchemaBtn"),
133
249
  resetSchemaBtn: document.getElementById("resetSchemaBtn"),
134
- prettySchemaBtn: document.getElementById("prettySchemaBtn"),
135
- downloadSchemaBtn: document.getElementById("downloadSchemaBtn"),
250
+ formatSchemaBtn: document.getElementById("formatSchemaBtn"),
251
+ readOnlyToggle: document.getElementById("readOnlyToggle"),
136
252
  formContainer: document.getElementById("formContainer"),
137
253
  formErrors: document.getElementById("formErrors"),
138
254
  submitBtn: document.getElementById("submitBtn"),
139
- saveDraftBtn: document.getElementById("saveDraftBtn"),
140
255
  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"),
256
+ dataTextarea: document.getElementById("dataTextarea"),
257
+ prefillBtn: document.getElementById("prefillBtn"),
258
+ copyDataBtn: document.getElementById("copyDataBtn"),
259
+ downloadDataBtn: document.getElementById("downloadDataBtn"),
260
+ dataErrors: document.getElementById("dataErrors"),
156
261
  };
157
262
 
158
- // Utility functions (using FormBuilder.pretty)
263
+ // Utility functions
159
264
  function pretty(obj) {
160
265
  return FormBuilder.pretty(obj);
161
266
  }
@@ -180,13 +285,51 @@ function downloadFile(filename, content) {
180
285
  const a = document.createElement("a");
181
286
  a.href = url;
182
287
  a.download = filename;
288
+ document.body.appendChild(a);
183
289
  a.click();
290
+ document.body.removeChild(a);
184
291
  URL.revokeObjectURL(url);
185
292
  }
186
293
 
187
- // Demo event handlers
188
- el.applySchemaBtn.addEventListener("click", () => {
294
+ // Configure FormBuilder with in-memory handlers
295
+ function setupFormBuilder() {
296
+ // Set form container
297
+ FormBuilder.setFormRoot(el.formContainer);
298
+
299
+ // Upload handler - store file in memory and return resource ID
300
+ FormBuilder.setUploadHandler(async (file) => {
301
+ try {
302
+ console.log("Uploading file to memory:", file.name);
303
+ const resourceId = await fileStorage.storeFile(file);
304
+ console.log("File stored with resource ID:", resourceId);
305
+ return resourceId;
306
+ } catch (error) {
307
+ console.error("Upload failed:", error);
308
+ throw error;
309
+ }
310
+ });
311
+
312
+ // Download handler - download file from memory
313
+ FormBuilder.setDownloadHandler((resourceId, fileName) => {
314
+ console.log("Downloading file from memory:", resourceId, fileName);
315
+ fileStorage.downloadFile(resourceId, fileName);
316
+ });
317
+
318
+ // Thumbnail handler - get thumbnail URL for preview
319
+ FormBuilder.setThumbnailHandler((resourceId) => {
320
+ const thumbnailUrl = fileStorage.getThumbnailUrl(resourceId);
321
+ console.log("Getting thumbnail for:", resourceId, thumbnailUrl);
322
+ return thumbnailUrl;
323
+ });
324
+
325
+ console.log("FormBuilder configured with in-memory file handlers");
326
+ }
327
+
328
+ // Schema management functions
329
+ function applyCurrentSchema() {
189
330
  clearError(el.schemaErrors);
331
+ clearError(el.formErrors);
332
+
190
333
  try {
191
334
  const schema = JSON.parse(el.schemaInput.value);
192
335
  const errors = FormBuilder.validateSchema(schema);
@@ -194,160 +337,119 @@ el.applySchemaBtn.addEventListener("click", () => {
194
337
  if (errors.length > 0) {
195
338
  showError(
196
339
  el.schemaErrors,
197
- "Schema validation errors: " + errors.join(", "),
340
+ `Schema validation errors: ${errors.join(", ")}`,
198
341
  );
199
- return;
342
+ return false;
200
343
  }
201
344
 
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
- }
208
- });
345
+ // Set mode based on toggle
346
+ const isReadOnly = el.readOnlyToggle.checked;
347
+ FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
209
348
 
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);
217
- });
349
+ // Get current data to preserve during re-render
350
+ let currentData = {};
351
+ try {
352
+ const formResult = FormBuilder.getFormData();
353
+ if (formResult.valid) {
354
+ currentData = formResult.data;
355
+ }
356
+ } catch {
357
+ // Ignore errors when getting current data
358
+ }
218
359
 
219
- el.prettySchemaBtn.addEventListener("click", () => {
220
- try {
221
- const parsed = JSON.parse(el.schemaInput.value);
222
- el.schemaInput.value = pretty(parsed);
360
+ // Render form with current data
361
+ FormBuilder.renderForm(schema, currentData);
362
+
363
+ console.log(`Form rendered in ${isReadOnly ? "readonly" : "edit"} mode`);
364
+ return true;
223
365
  } catch (e) {
224
- showError(el.schemaErrors, "Prettify: JSON parse error: " + e.message);
366
+ showError(el.schemaErrors, `JSON parse error: ${e.message}`);
367
+ return false;
225
368
  }
226
- });
369
+ }
227
370
 
228
- el.downloadSchemaBtn.addEventListener("click", () => {
229
- downloadFile("schema.json", el.schemaInput.value || pretty(EXAMPLE_SCHEMA));
371
+ // Event handlers
372
+ el.applySchemaBtn.addEventListener("click", () => {
373
+ applyCurrentSchema();
230
374
  });
231
375
 
232
- el.clearFormBtn.addEventListener("click", () => {
233
- FormBuilder.clearForm();
376
+ el.resetSchemaBtn.addEventListener("click", () => {
377
+ el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
378
+ clearError(el.schemaErrors);
234
379
  clearError(el.formErrors);
235
- });
380
+ clearError(el.dataErrors);
381
+ el.dataTextarea.value = "";
236
382
 
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
- }
247
- });
383
+ // Clear file storage
384
+ fileStorage.clear();
248
385
 
249
- el.downloadOutputBtn.addEventListener("click", () => {
250
- downloadFile("form-data.json", el.outputJson.value || "{}");
386
+ // Apply schema
387
+ applyCurrentSchema();
251
388
  });
252
389
 
253
- el.shareUrlBtn.addEventListener("click", () => {
390
+ el.formatSchemaBtn.addEventListener("click", () => {
254
391
  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
- }
266
- });
267
-
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);
392
+ const parsed = JSON.parse(el.schemaInput.value);
393
+ el.schemaInput.value = pretty(parsed);
394
+ clearError(el.schemaErrors);
276
395
  } catch (e) {
277
- showError(el.prefillErrors, "JSON parse error: " + e.message);
396
+ showError(el.schemaErrors, `Format: JSON parse error: ${e.message}`);
278
397
  }
279
398
  });
280
399
 
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;
300
- }
301
- });
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
- }
400
+ // Read-only toggle handler
401
+ el.readOnlyToggle.addEventListener("change", () => {
402
+ applyCurrentSchema(); // Re-render form with new mode
309
403
  });
310
404
 
311
- // Form submission handlers
312
- function submitFormEnhanced() {
405
+ // Form interaction handlers
406
+ el.submitBtn.addEventListener("click", () => {
313
407
  clearError(el.formErrors);
408
+ clearError(el.dataErrors);
314
409
 
315
410
  const result = FormBuilder.getFormData();
316
411
 
317
412
  if (!result.valid) {
318
- showError(el.formErrors, "Validation errors: " + result.errors.join(", "));
413
+ showError(el.formErrors, `Validation errors: ${result.errors.join(", ")}`);
319
414
  return;
320
415
  }
321
416
 
322
- // Convert resource IDs to file info for demo
323
- const processedData = JSON.parse(JSON.stringify(result.data));
417
+ // For demo purposes, we can show either raw data or enhanced data
418
+ // Let's provide both options - you can choose which one to use
419
+
420
+ // Option 1: Show raw data with just resource IDs (what you might want)
421
+ const rawData = JSON.parse(JSON.stringify(result.data));
422
+
423
+ // Option 2: Show enhanced data with file metadata (for demo visualization)
424
+ const enhancedData = JSON.parse(JSON.stringify(result.data));
324
425
 
325
426
  function replaceResourceIds(obj) {
326
427
  for (const key in obj) {
327
428
  if (typeof obj[key] === "string" && obj[key].startsWith("res_")) {
328
- const meta = FormBuilder.state.resourceIndex.get(obj[key]);
329
- if (meta) {
429
+ const metadata = fileStorage.getMetadata(obj[key]);
430
+ if (metadata) {
330
431
  obj[key] = {
331
432
  resourceId: obj[key],
332
- name: meta.name,
333
- type: meta.type,
334
- size: meta.size,
433
+ name: metadata.name,
434
+ type: metadata.type,
435
+ size: metadata.size,
436
+ uploadedAt: metadata.uploadedAt,
335
437
  };
336
438
  }
337
439
  } else if (Array.isArray(obj[key])) {
338
- obj[key].forEach((item) => {
440
+ obj[key].forEach((item, index) => {
339
441
  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) {
442
+ const metadata = fileStorage.getMetadata(item);
443
+ if (metadata) {
343
444
  obj[key][index] = {
344
445
  resourceId: item,
345
- name: meta.name,
346
- type: meta.type,
347
- size: meta.size,
446
+ name: metadata.name,
447
+ type: metadata.type,
448
+ size: metadata.size,
449
+ uploadedAt: metadata.uploadedAt,
348
450
  };
349
451
  }
350
- } else if (typeof item === "object") {
452
+ } else if (typeof item === "object" && item !== null) {
351
453
  replaceResourceIds(item);
352
454
  }
353
455
  });
@@ -357,218 +459,109 @@ function submitFormEnhanced() {
357
459
  }
358
460
  }
359
461
 
360
- replaceResourceIds(processedData);
462
+ replaceResourceIds(enhancedData);
361
463
 
362
- el.outputJson.value = pretty(processedData);
363
- }
464
+ // Show raw data (just resource IDs) - change to enhancedData for metadata
465
+ el.dataTextarea.value = pretty(rawData);
466
+ console.log("Form submitted successfully");
467
+ console.log("Raw data:", rawData);
468
+ console.log("Enhanced data:", enhancedData);
469
+ });
364
470
 
365
- el.submitBtn.addEventListener("click", submitFormEnhanced);
366
- el.saveDraftBtn.addEventListener("click", () => {
367
- const result = FormBuilder.getFormData();
368
- el.outputJson.value = pretty(result.data);
471
+ el.clearFormBtn.addEventListener("click", () => {
472
+ FormBuilder.clearForm();
473
+ clearError(el.formErrors);
474
+ console.log("Form values cleared");
369
475
  });
370
476
 
371
- // Readonly demo handlers
372
- el.applyReadonlyBtn.addEventListener("click", () => {
373
- clearError(el.readonlyErrors);
477
+ // Data management handlers
478
+ el.prefillBtn.addEventListener("click", () => {
479
+ clearError(el.dataErrors);
480
+ clearError(el.formErrors);
481
+
374
482
  try {
375
- const schema = JSON.parse(el.readonlySchemaInput.value);
376
- const errors = FormBuilder.validateSchema(schema);
483
+ const prefillData = JSON.parse(el.dataTextarea.value || "{}");
484
+ const currentSchema = JSON.parse(el.schemaInput.value);
377
485
 
378
- if (errors.length > 0) {
379
- showError(
380
- el.readonlyErrors,
381
- "Schema validation errors: " + errors.join(", "),
382
- );
383
- return;
486
+ // Convert enhanced data back to raw format for prefilling
487
+ const processedData = JSON.parse(JSON.stringify(prefillData));
488
+
489
+ function extractResourceIds(obj) {
490
+ for (const key in obj) {
491
+ // Handle enhanced file objects - extract just the resourceId
492
+ if (
493
+ obj[key] &&
494
+ typeof obj[key] === "object" &&
495
+ obj[key].resourceId &&
496
+ typeof obj[key].resourceId === "string" &&
497
+ obj[key].resourceId.startsWith("res_")
498
+ ) {
499
+ obj[key] = obj[key].resourceId;
500
+ } else if (Array.isArray(obj[key])) {
501
+ obj[key].forEach((item, index) => {
502
+ if (
503
+ item &&
504
+ typeof item === "object" &&
505
+ item.resourceId &&
506
+ typeof item.resourceId === "string" &&
507
+ item.resourceId.startsWith("res_")
508
+ ) {
509
+ obj[key][index] = item.resourceId;
510
+ } else if (typeof item === "object" && item !== null) {
511
+ extractResourceIds(item);
512
+ }
513
+ });
514
+ } else if (typeof obj[key] === "object" && obj[key] !== null) {
515
+ extractResourceIds(obj[key]);
516
+ }
517
+ }
384
518
  }
385
519
 
386
- // Clear container
387
- el.readonlyDemoContainer.innerHTML = "";
520
+ extractResourceIds(processedData);
388
521
 
389
- // Store original form settings
390
- const originalFormRoot = FormBuilder.state.formRoot;
391
- const originalMode = FormBuilder.state.config.readonly;
522
+ // Set mode based on toggle
523
+ const isReadOnly = el.readOnlyToggle.checked;
524
+ FormBuilder.setMode(isReadOnly ? "readonly" : "edit");
392
525
 
393
- try {
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;
402
- }
526
+ FormBuilder.renderForm(currentSchema, processedData);
527
+ console.log("Form prefilled with data");
528
+ console.log("Processed prefill data:", processedData);
403
529
  } catch (e) {
404
- showError(el.readonlyErrors, "JSON parse error: " + e.message);
530
+ showError(el.dataErrors, `JSON parse error: ${e.message}`);
531
+ console.error("Prefill error:", e);
405
532
  }
406
533
  });
407
534
 
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);
412
- });
413
-
414
- // URL schema loading
415
- function loadSchemaFromURL() {
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);
427
- }
428
- }
429
- }
430
-
431
- // Read-only demo functions
432
- function downloadDemoFile(filePath, fileName) {
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);
461
- simpleLinkDownload(filePath, fileName);
462
- });
463
- } else {
464
- console.log("Not on local dev server, using simple link method");
465
- simpleLinkDownload(filePath, fileName);
535
+ el.copyDataBtn.addEventListener("click", async () => {
536
+ try {
537
+ await navigator.clipboard.writeText(el.dataTextarea.value || "");
538
+ el.copyDataBtn.textContent = "Copied!";
539
+ setTimeout(() => {
540
+ el.copyDataBtn.textContent = "Copy JSON";
541
+ }, 1000);
542
+ console.log("Data copied to clipboard");
543
+ } catch (e) {
544
+ console.warn("Copy failed:", e);
466
545
  }
467
- }
468
-
469
- function simpleLinkDownload(filePath, fileName) {
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");
479
- }
480
-
481
- // Configure download handler for readonly demo
482
- function setupReadonlyDemo() {
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
- });
518
- }
519
-
520
- // Default readonly schema for demo
521
- const DEFAULT_READONLY_SCHEMA = {
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
- ],
538
- };
539
-
540
- // Default readonly data
541
- const DEFAULT_READONLY_DATA = {
542
- result_image: "demo_infographic",
543
- result_video: "demo_video",
544
- };
546
+ });
545
547
 
546
- function renderReadonlyDemo() {
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
- }
552
- }
548
+ el.downloadDataBtn.addEventListener("click", () => {
549
+ downloadFile("form-data.json", el.dataTextarea.value || "{}");
550
+ console.log("Data downloaded");
551
+ });
553
552
 
554
553
  // Initialize demo application
555
554
  function initDemo() {
556
- // Set up the form builder for main form
557
- FormBuilder.setFormRoot(el.formContainer);
558
-
559
- // Set up handlers for readonly demo
560
- setupReadonlyDemo();
555
+ // Set up FormBuilder
556
+ setupFormBuilder();
561
557
 
562
558
  // Initialize with example schema
563
559
  el.schemaInput.value = pretty(EXAMPLE_SCHEMA);
564
- FormBuilder.setMode("edit"); // Ensure main form is in edit mode
565
- FormBuilder.renderForm(EXAMPLE_SCHEMA, {});
566
560
 
567
- // Load schema from URL if present
568
- loadSchemaFromURL();
561
+ // Apply initial schema
562
+ applyCurrentSchema();
569
563
 
570
- // Initialize read-only demo
571
- setTimeout(renderReadonlyDemo, 500);
564
+ console.log("Demo initialized successfully");
572
565
  }
573
566
 
574
567
  // Start the demo when the page loads