Haroon999 commited on
Commit
5ded031
·
verified ·
1 Parent(s): a450e62

create this as a canvas fully functional drag and drop resize moving modern advance featur rich remove form instead of form make canvas fully flexible custoizbla using real canvas resources

Browse files
Files changed (1) hide show
  1. templates.html +353 -113
templates.html CHANGED
@@ -96,113 +96,105 @@
96
  </div>
97
 
98
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
99
- <!-- Template Form -->
100
  <div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
101
  <h2 class="text-xl font-semibold mb-4 flex items-center">
102
- <i data-feather="edit-3" class="w-5 h-5 mr-2 text-primary-500"></i>
103
- Template Settings
104
  </h2>
105
- <form>
106
- <div class="space-y-4">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  <div>
108
- <label class="block text-sm font-medium text-gray-300 mb-1">Template Name</label>
109
- <input type="text" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent" placeholder="My Awesome Template">
110
- </div>
111
-
112
- <div class="grid grid-cols-2 gap-4">
113
- <div>
114
- <label class="block text-sm font-medium text-gray-300 mb-1">DPI</label>
115
- <input type="number" value="300" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
116
- </div>
117
- <div>
118
- <label class="block text-sm font-medium text-gray-300 mb-1">Canvas Width (mm)</label>
119
- <input type="number" value="65" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
120
- </div>
121
- </div>
122
-
123
- <div class="grid grid-cols-2 gap-4">
124
- <div>
125
- <label class="block text-sm font-medium text-gray-300 mb-1">Canvas Height (mm)</label>
126
- <input type="number" value="140" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
127
- </div>
128
- <div>
129
- <label class="block text-sm font-medium text-gray-300 mb-1">QR Box Width (mm)</label>
130
- <input type="number" value="50" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
131
- </div>
132
- </div>
133
-
134
- <div class="grid grid-cols-2 gap-4">
135
- <div>
136
- <label class="block text-sm font-medium text-gray-300 mb-1">QR Box Height (mm)</label>
137
- <input type="number" value="50" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
138
- </div>
139
- <div>
140
- <label class="block text-sm font-medium text-gray-300 mb-1">QR Position X (mm)</label>
141
- <input type="number" value="0" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
142
- </div>
143
  </div>
144
-
145
  <div>
146
- <label class="block text-sm font-medium text-gray-300 mb-1">QR Position Y (mm)</label>
147
- <input type="number" value="0" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
148
- </div>
149
-
150
- <div class="pt-4">
151
- <button type="submit" class="w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors">
152
- Save Template
153
- </button>
154
  </div>
155
  </div>
156
- </form>
 
 
 
 
 
157
  </div>
158
 
159
- <!-- Preview Section -->
160
  <div class="space-y-6">
161
- <!-- QR Preview -->
162
  <div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
163
  <h2 class="text-xl font-semibold mb-4 flex items-center">
164
- <i data-feather="eye" class="w-5 h-5 mr-2 text-primary-500"></i>
165
- Live Preview
166
  </h2>
167
- <div class="relative border-2 border-dashed border-gray-700 p-4 rounded-lg">
168
- <div class="bg-white relative" style="width: 300px; height: 646px;">
169
- <div style="position: absolute; left: 0px; top: 0px; width: 231px; height: 231px; border: 1px dashed gray;">
170
- <div class="bg-gray-200 w-full h-full cursor-move flex items-center justify-center text-gray-600 text-sm">
171
- QR Code Position
172
- <div class="resize-handle"></div>
173
- </div>
174
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
  </div>
176
  </div>
177
  </div>
178
-
179
- <!-- Upload Section -->
180
- <div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
181
  <h2 class="text-xl font-semibold mb-4 flex items-center">
182
- <i data-feather="upload" class="w-5 h-5 mr-2 text-primary-500"></i>
183
- Upload QR Codes
184
  </h2>
185
- <div class="dropzone rounded-lg p-8 text-center cursor-pointer mb-4">
186
- <input type="file" class="hidden" multiple accept="image/*">
187
- <i data-feather="upload-cloud" class="w-12 h-12 mx-auto text-primary-500 mb-3"></i>
188
- <p class="text-gray-400 mb-1">Drag & drop your QR code images here</p>
189
- <p class="text-sm text-gray-500">or click to select files</p>
190
- </div>
191
-
192
- <div class="border border-gray-700 rounded-lg p-4 mb-4">
193
- <h3 class="text-sm font-medium text-gray-300 mb-2">Selected Files</h3>
194
- <div class="text-sm text-gray-400 italic">No files selected</div>
195
  </div>
196
-
197
- <button class="w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors flex items-center justify-center">
198
- <i data-feather="zap" class="w-4 h-4 mr-2"></i>
199
- Process & Download
200
- </button>
201
  </div>
202
  </div>
203
  </div>
204
  </main>
205
 
 
 
 
206
  <!-- Footer -->
207
  <footer class="border-t border-gray-800 py-8 mt-12">
208
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
@@ -221,48 +213,296 @@
221
  </footer>
222
  </div>
223
 
 
224
  <script>
225
  feather.replace();
226
 
227
- // Simple dropzone interaction
228
- document.querySelectorAll('.dropzone').forEach(dropzone => {
229
- const input = dropzone.querySelector('input');
230
-
231
- dropzone.addEventListener('click', () => {
232
- input.click();
233
  });
234
-
235
- ['dragenter', 'dragover'].forEach(eventName => {
236
- dropzone.addEventListener(eventName, (e) => {
237
- e.preventDefault();
238
- dropzone.classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
  });
240
  });
241
-
242
- ['dragleave', 'drop'].forEach(eventName => {
243
- dropzone.addEventListener(eventName, (e) => {
244
- e.preventDefault();
245
- dropzone.classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  });
247
  });
248
-
249
- input.addEventListener('change', () => {
250
- const filesList = input.files;
251
- if (filesList.length > 0) {
252
- const filesContainer = dropzone.nextElementSibling.querySelector('div');
253
- filesContainer.innerHTML = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
- Array.from(filesList).forEach(file => {
256
- const fileElement = document.createElement('div');
257
- fileElement.className = 'flex justify-between items-center py-1';
258
- fileElement.innerHTML = `
259
- <span class="truncate">${file.name}</span>
260
- <span class="text-gray-500 text-xs">${(file.size / 1024).toFixed(1)} KB</span>
261
- `;
262
- filesContainer.appendChild(fileElement);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  });
264
  }
265
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  });
267
  </script>
268
  </body>
 
96
  </div>
97
 
98
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
99
+ <!-- Export Section -->
100
  <div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
101
  <h2 class="text-xl font-semibold mb-4 flex items-center">
102
+ <i data-feather="download" class="w-5 h-5 mr-2 text-primary-500"></i>
103
+ Export Options
104
  </h2>
105
+ <div class="space-y-4">
106
+ <div>
107
+ <label class="block text-sm font-medium text-gray-300 mb-1">Export Format</label>
108
+ <select class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2">
109
+ <option>PNG</option>
110
+ <option>JPG</option>
111
+ <option>PDF</option>
112
+ <option>SVG</option>
113
+ </select>
114
+ </div>
115
+
116
+ <div>
117
+ <label class="block text-sm font-medium text-gray-300 mb-1">DPI</label>
118
+ <select class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2">
119
+ <option>72 (Web)</option>
120
+ <option selected>300 (Print)</option>
121
+ <option>600 (High Quality Print)</option>
122
+ </select>
123
+ </div>
124
+
125
+ <div class="grid grid-cols-2 gap-4">
126
  <div>
127
+ <label class="block text-sm font-medium text-gray-300 mb-1">Width (mm)</label>
128
+ <input type="number" value="65" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  </div>
 
130
  <div>
131
+ <label class="block text-sm font-medium text-gray-300 mb-1">Height (mm)</label>
132
+ <input type="number" value="140" class="w-full bg-gray-800 border border-gray-700 rounded-lg px-4 py-2">
 
 
 
 
 
 
133
  </div>
134
  </div>
135
+
136
+ <button id="exportCanvas" class="w-full bg-primary-600 hover:bg-primary-700 text-white font-medium py-2 px-4 rounded-lg transition-colors flex items-center justify-center">
137
+ <i data-feather="download" class="w-4 h-4 mr-2"></i>
138
+ Export Design
139
+ </button>
140
+ </div>
141
  </div>
142
 
143
+ <!-- Canvas Designer -->
144
  <div class="space-y-6">
 
145
  <div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6">
146
  <h2 class="text-xl font-semibold mb-4 flex items-center">
147
+ <i data-feather="edit-2" class="w-5 h-5 mr-2 text-primary-500"></i>
148
+ Canvas Designer
149
  </h2>
150
+ <div class="flex flex-col space-y-4">
151
+ <div class="flex space-x-4">
152
+ <button id="addQR" class="px-4 py-2 bg-primary-600 hover:bg-primary-700 rounded-lg flex items-center">
153
+ <i data-feather="plus" class="w-4 h-4 mr-2"></i> Add QR
154
+ </button>
155
+ <button id="addText" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg flex items-center">
156
+ <i data-feather="type" class="w-4 h-4 mr-2"></i> Add Text
157
+ </button>
158
+ <button id="addImage" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg flex items-center">
159
+ <i data-feather="image" class="w-4 h-4 mr-2"></i> Add Image
160
+ </button>
161
+ </div>
162
+ <div class="flex space-x-4">
163
+ <button id="deleteSelected" class="px-4 py-2 bg-red-600 hover:bg-red-700 rounded-lg flex items-center">
164
+ <i data-feather="trash-2" class="w-4 h-4 mr-2"></i> Delete
165
+ </button>
166
+ <button id="clearCanvas" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg flex items-center">
167
+ <i data-feather="x" class="w-4 h-4 mr-2"></i> Clear All
168
+ </button>
169
+ </div>
170
+ </div>
171
+
172
+ <div class="mt-6 relative">
173
+ <div class="border-2 border-dashed border-gray-700 rounded-lg overflow-hidden">
174
+ <canvas id="designCanvas" class="bg-white w-full" style="height: 600px;"></canvas>
175
  </div>
176
  </div>
177
  </div>
178
+
179
+ <!-- Properties Panel -->
180
+ <div class="bg-gray-900/50 border border-gray-800 rounded-xl p-6" id="propertiesPanel">
181
  <h2 class="text-xl font-semibold mb-4 flex items-center">
182
+ <i data-feather="sliders" class="w-5 h-5 mr-2 text-primary-500"></i>
183
+ Properties
184
  </h2>
185
+ <div class="space-y-4" id="propertyControls">
186
+ <div class="text-gray-400 italic text-center py-8">
187
+ Select an element to edit its properties
188
+ </div>
 
 
 
 
 
 
189
  </div>
 
 
 
 
 
190
  </div>
191
  </div>
192
  </div>
193
  </main>
194
 
195
+ <!-- Hidden elements for export -->
196
+ <a id="downloadLink" style="display: none;"></a>
197
+
198
  <!-- Footer -->
199
  <footer class="border-t border-gray-800 py-8 mt-12">
200
  <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
 
213
  </footer>
214
  </div>
215
 
216
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
217
  <script>
218
  feather.replace();
219
 
220
+ document.addEventListener('DOMContentLoaded', function() {
221
+ const canvas = new fabric.Canvas('designCanvas', {
222
+ backgroundColor: '#ffffff',
223
+ selection: true,
224
+ preserveObjectStacking: true
 
225
  });
226
+
227
+ // Add QR code element
228
+ document.getElementById('addQR').addEventListener('click', function() {
229
+ fabric.Image.fromURL('https://api.qrserver.com/v1/create-qr-code/?size=150x150&data=Example', function(img) {
230
+ img.set({
231
+ left: 50,
232
+ top: 50,
233
+ scaleX: 1,
234
+ scaleY: 1,
235
+ cornerStyle: 'circle',
236
+ cornerColor: '#3b82f6',
237
+ cornerSize: 10,
238
+ transparentCorners: false,
239
+ borderColor: '#3b82f6',
240
+ borderScaleFactor: 1.5,
241
+ hasRotatingPoint: true
242
+ });
243
+ canvas.add(img);
244
+ canvas.setActiveObject(img);
245
+ updatePropertiesPanel(img);
246
  });
247
  });
248
+
249
+ // Add text element
250
+ document.getElementById('addText').addEventListener('click', function() {
251
+ const text = new fabric.Text('Double click to edit', {
252
+ left: 100,
253
+ top: 100,
254
+ fontFamily: 'Inter',
255
+ fontSize: 20,
256
+ fill: '#000000',
257
+ hasControls: true,
258
+ cornerStyle: 'circle',
259
+ cornerColor: '#3b82f6',
260
+ cornerSize: 10,
261
+ transparentCorners: false
262
+ });
263
+ canvas.add(text);
264
+ canvas.setActiveObject(text);
265
+ updatePropertiesPanel(text);
266
+
267
+ text.on('editing:entered', function() {
268
+ text.set('fill', '#3b82f6');
269
+ canvas.renderAll();
270
+ });
271
+
272
+ text.on('editing:exited', function() {
273
+ text.set('fill', '#000000');
274
+ canvas.renderAll();
275
  });
276
  });
277
+
278
+ // Add image element
279
+ document.getElementById('addImage').addEventListener('click', function() {
280
+ const input = document.createElement('input');
281
+ input.type = 'file';
282
+ input.accept = 'image/*';
283
+ input.onchange = function(e) {
284
+ const file = e.target.files[0];
285
+ if (file) {
286
+ const reader = new FileReader();
287
+ reader.onload = function(event) {
288
+ fabric.Image.fromURL(event.target.result, function(img) {
289
+ img.set({
290
+ left: 150,
291
+ top: 150,
292
+ scaleX: 0.5,
293
+ scaleY: 0.5,
294
+ hasControls: true,
295
+ cornerStyle: 'circle',
296
+ cornerColor: '#3b82f6',
297
+ cornerSize: 10,
298
+ transparentCorners: false
299
+ });
300
+ canvas.add(img);
301
+ canvas.setActiveObject(img);
302
+ updatePropertiesPanel(img);
303
+ });
304
+ };
305
+ reader.readAsDataURL(file);
306
+ }
307
+ };
308
+ input.click();
309
+ });
310
+
311
+ // Delete selected element
312
+ document.getElementById('deleteSelected').addEventListener('click', function() {
313
+ const activeObject = canvas.getActiveObject();
314
+ if (activeObject) {
315
+ canvas.remove(activeObject);
316
+ updatePropertiesPanel(null);
317
+ }
318
+ });
319
+
320
+ // Clear canvas
321
+ document.getElementById('clearCanvas').addEventListener('click', function() {
322
+ if (confirm('Are you sure you want to clear the canvas?')) {
323
+ canvas.clear();
324
+ updatePropertiesPanel(null);
325
+ }
326
+ });
327
+
328
+ // Canvas selection event
329
+ canvas.on('selection:created', function(options) {
330
+ updatePropertiesPanel(options.selected[0]);
331
+ });
332
+
333
+ canvas.on('selection:updated', function(options) {
334
+ updatePropertiesPanel(options.selected[0]);
335
+ });
336
+
337
+ canvas.on('selection:cleared', function() {
338
+ updatePropertiesPanel(null);
339
+ });
340
+
341
+ // Update properties panel based on selected object
342
+ function updatePropertiesPanel(obj) {
343
+ const panel = document.getElementById('propertyControls');
344
+
345
+ if (!obj) {
346
+ panel.innerHTML = `
347
+ <div class="text-gray-400 italic text-center py-8">
348
+ Select an element to edit its properties
349
+ </div>
350
+ `;
351
+ return;
352
+ }
353
+
354
+ let controls = '';
355
+
356
+ // Common properties
357
+ controls += `
358
+ <div>
359
+ <label class="block text-sm font-medium text-gray-300 mb-1">Position</label>
360
+ <div class="grid grid-cols-2 gap-2">
361
+ <div>
362
+ <input type="number" class="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1"
363
+ id="posX" value="${Math.round(obj.left)}" placeholder="X">
364
+ </div>
365
+ <div>
366
+ <input type="number" class="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1"
367
+ id="posY" value="${Math.round(obj.top)}" placeholder="Y">
368
+ </div>
369
+ </div>
370
+ </div>
371
 
372
+ <div>
373
+ <label class="block text-sm font-medium text-gray-300 mb-1">Size</label>
374
+ <div class="grid grid-cols-2 gap-2">
375
+ <div>
376
+ <input type="number" class="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1"
377
+ id="width" value="${Math.round(obj.width * obj.scaleX)}" placeholder="Width">
378
+ </div>
379
+ <div>
380
+ <input type="number" class="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1"
381
+ id="height" value="${Math.round(obj.height * obj.scaleY)}" placeholder="Height">
382
+ </div>
383
+ </div>
384
+ </div>
385
+
386
+ <div>
387
+ <label class="block text-sm font-medium text-gray-300 mb-1">Rotation</label>
388
+ <input type="range" class="w-full bg-gray-800" id="rotation"
389
+ min="0" max="360" value="${Math.round(obj.angle || 0)}">
390
+ </div>
391
+ `;
392
+
393
+ // Text specific properties
394
+ if (obj.type === 'text') {
395
+ controls += `
396
+ <div>
397
+ <label class="block text-sm font-medium text-gray-300 mb-1">Text Content</label>
398
+ <textarea class="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1"
399
+ id="textContent" rows="2">${obj.text}</textarea>
400
+ </div>
401
+
402
+ <div>
403
+ <label class="block text-sm font-medium text-gray-300 mb-1">Font Size</label>
404
+ <input type="number" class="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1"
405
+ id="fontSize" value="${obj.fontSize}">
406
+ </div>
407
+
408
+ <div>
409
+ <label class="block text-sm font-medium text-gray-300 mb-1">Text Color</label>
410
+ <input type="color" class="w-full bg-gray-800 border border-gray-700 rounded px-2 py-1"
411
+ id="textColor" value="${rgbToHex(obj.fill)}">
412
+ </div>
413
+ `;
414
+ }
415
+
416
+ // Image specific properties
417
+ if (obj.type === 'image') {
418
+ controls += `
419
+ <div>
420
+ <label class="block text-sm font-medium text-gray-300 mb-1">Opacity</label>
421
+ <input type="range" class="w-full bg-gray-800" id="opacity"
422
+ min="0" max="1" step="0.1" value="${obj.opacity}">
423
+ </div>
424
+ `;
425
+ }
426
+
427
+ panel.innerHTML = controls;
428
+
429
+ // Add event listeners for property changes
430
+ document.getElementById('posX')?.addEventListener('change', function(e) {
431
+ obj.set('left', parseInt(e.target.value));
432
+ canvas.renderAll();
433
+ });
434
+
435
+ document.getElementById('posY')?.addEventListener('change', function(e) {
436
+ obj.set('top', parseInt(e.target.value));
437
+ canvas.renderAll();
438
+ });
439
+
440
+ document.getElementById('width')?.addEventListener('change', function(e) {
441
+ const scale = parseInt(e.target.value) / obj.width;
442
+ obj.set('scaleX', scale);
443
+ canvas.renderAll();
444
+ });
445
+
446
+ document.getElementById('height')?.addEventListener('change', function(e) {
447
+ const scale = parseInt(e.target.value) / obj.height;
448
+ obj.set('scaleY', scale);
449
+ canvas.renderAll();
450
+ });
451
+
452
+ document.getElementById('rotation')?.addEventListener('input', function(e) {
453
+ obj.set('angle', parseInt(e.target.value));
454
+ canvas.renderAll();
455
+ });
456
+
457
+ if (obj.type === 'text') {
458
+ document.getElementById('textContent')?.addEventListener('change', function(e) {
459
+ obj.set('text', e.target.value);
460
+ canvas.renderAll();
461
+ });
462
+
463
+ document.getElementById('fontSize')?.addEventListener('change', function(e) {
464
+ obj.set('fontSize', parseInt(e.target.value));
465
+ canvas.renderAll();
466
+ });
467
+
468
+ document.getElementById('textColor')?.addEventListener('change', function(e) {
469
+ obj.set('fill', e.target.value);
470
+ canvas.renderAll();
471
  });
472
  }
473
+
474
+ if (obj.type === 'image') {
475
+ document.getElementById('opacity')?.addEventListener('input', function(e) {
476
+ obj.set('opacity', parseFloat(e.target.value));
477
+ canvas.renderAll();
478
+ });
479
+ }
480
+ }
481
+
482
+ // Helper function to convert RGB to HEX
483
+ function rgbToHex(rgb) {
484
+ if (!rgb) return '#000000';
485
+ if (typeof rgb === 'string' && rgb.startsWith('#')) return rgb;
486
+
487
+ return '#' +
488
+ (rgb[0] | 1 << 8).toString(16).slice(1) +
489
+ (rgb[1] | 1 << 8).toString(16).slice(1) +
490
+ (rgb[2] | 1 << 8).toString(16).slice(1);
491
+ }
492
+
493
+ // Set canvas size to fit container
494
+ function resizeCanvas() {
495
+ const container = document.querySelector('.border-2.border-dashed');
496
+ if (container) {
497
+ const width = container.clientWidth;
498
+ canvas.setWidth(width);
499
+ canvas.setHeight(600); // Fixed height for demo
500
+ canvas.renderAll();
501
+ }
502
+ }
503
+
504
+ window.addEventListener('resize', resizeCanvas);
505
+ resizeCanvas();
506
  });
507
  </script>
508
  </body>