senangwebs-photobooth 1.0.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/src/js/swp.js ADDED
@@ -0,0 +1,726 @@
1
+ /**
2
+ * SenangWebs Photobooth (SWP)
3
+ * A lightweight client-side photo editing library
4
+ * @version 1.0.0
5
+ */
6
+
7
+ import '../css/swp.css';
8
+
9
+ class SWP {
10
+ constructor(container, options = {}) {
11
+ this.container = container;
12
+ this.options = {
13
+ imageUrl: options.imageUrl || null,
14
+ width: options.width || 800,
15
+ height: options.height || 600,
16
+ showIcons: options.showIcons !== undefined ? options.showIcons : true,
17
+ showLabels: options.showLabels !== undefined ? options.showLabels : true,
18
+ labels: {
19
+ upload: options.labels?.upload !== undefined ? options.labels.upload : 'Upload',
20
+ rotateLeft: options.labels?.rotateLeft !== undefined ? options.labels.rotateLeft : null,
21
+ rotateRight: options.labels?.rotateRight !== undefined ? options.labels.rotateRight : null,
22
+ flipH: options.labels?.flipH !== undefined ? options.labels.flipH : null,
23
+ flipV: options.labels?.flipV !== undefined ? options.labels.flipV : null,
24
+ resize: options.labels?.resize !== undefined ? options.labels.resize : 'Resize',
25
+ adjust: options.labels?.adjust !== undefined ? options.labels.adjust : 'Adjust',
26
+ filters: options.labels?.filters !== undefined ? options.labels.filters : 'Filters',
27
+ reset: options.labels?.reset !== undefined ? options.labels.reset : 'Reset',
28
+ save: options.labels?.save !== undefined ? options.labels.save : 'Save'
29
+ }
30
+ };
31
+
32
+ this.canvas = null;
33
+ this.ctx = null;
34
+ this.originalImage = null;
35
+ this.currentImage = null;
36
+ this.history = [];
37
+ this.currentState = {
38
+ brightness: 100,
39
+ contrast: 100,
40
+ saturation: 100,
41
+ rotation: 0,
42
+ flipH: false,
43
+ flipV: false,
44
+ filter: 'none'
45
+ };
46
+ this.eventListeners = {};
47
+
48
+ this.init();
49
+ }
50
+
51
+ init() {
52
+ this.createUI();
53
+ if (this.options.imageUrl) {
54
+ this.loadImage(this.options.imageUrl);
55
+ }
56
+ }
57
+
58
+ createUI() {
59
+ // Clear container
60
+ this.container.innerHTML = '';
61
+ this.container.classList.add('swp-container');
62
+
63
+ // Create main structure
64
+ const wrapper = document.createElement('div');
65
+ wrapper.className = 'swp-wrapper';
66
+
67
+ // Create toolbar
68
+ const toolbar = document.createElement('div');
69
+ toolbar.className = 'swp-toolbar';
70
+ toolbar.innerHTML = this.createToolbarHTML();
71
+
72
+ // Create layout container
73
+ const layoutContainer = document.createElement('div');
74
+ layoutContainer.className = 'swp-layout-container';
75
+
76
+ // Create control container
77
+ const controlContainer = document.createElement('div');
78
+ controlContainer.className = 'swp-control-container';
79
+
80
+ // Create canvas container
81
+ const canvasContainer = document.createElement('div');
82
+ canvasContainer.className = 'swp-canvas-container';
83
+
84
+ // Create canvas
85
+ this.canvas = document.createElement('canvas');
86
+ this.canvas.width = this.options.width;
87
+ this.canvas.height = this.options.height;
88
+ this.canvas.className = 'swp-canvas';
89
+ this.ctx = this.canvas.getContext('2d');
90
+
91
+ canvasContainer.appendChild(this.canvas);
92
+
93
+ // Create adjustments panel
94
+ const adjustmentsPanel = document.createElement('div');
95
+ adjustmentsPanel.className = 'swp-adjustments-panel';
96
+ adjustmentsPanel.innerHTML = this.createAdjustmentsPanelHTML();
97
+
98
+ // Create filters panel
99
+ const filtersPanel = document.createElement('div');
100
+ filtersPanel.className = 'swp-filters-panel';
101
+ filtersPanel.innerHTML = this.createFiltersPanelHTML();
102
+
103
+ // Create resize panel
104
+ const resizePanel = document.createElement('div');
105
+ resizePanel.className = 'swp-resize-panel';
106
+ resizePanel.innerHTML = this.createResizePanelHTML();
107
+
108
+ // Append elements
109
+ wrapper.appendChild(toolbar);
110
+ wrapper.appendChild(layoutContainer);
111
+
112
+ layoutContainer.appendChild(canvasContainer);
113
+ layoutContainer.appendChild(controlContainer);
114
+
115
+ controlContainer.appendChild(adjustmentsPanel);
116
+ controlContainer.appendChild(filtersPanel);
117
+ controlContainer.appendChild(resizePanel);
118
+ this.container.appendChild(wrapper);
119
+
120
+ // Bind events
121
+ this.bindEvents();
122
+ }
123
+
124
+ createToolbarHTML() {
125
+ const { showIcons, showLabels, labels } = this.options;
126
+
127
+ const createButton = (action, icon, label, title) => {
128
+ const showIcon = showIcons ? `<span class="swp-icon"><i class="${icon}"></i></span>` : '';
129
+ const showLabel = showLabels && label !== null ? `<span>${label}</span>` : '';
130
+ return `<button class="swp-btn${!showLabel ? ' swp-btn-icon-only' : ''}" data-action="${action}" title="${title}">${showIcon}${showLabel}</button>`;
131
+ };
132
+
133
+ return `
134
+ <div class="swp-toolbar-group">
135
+ ${createButton('upload', 'fas fa-folder', labels.upload, 'Upload Image')}
136
+ <input type="file" id="swp-file-input" accept="image/*" style="display: none;">
137
+ </div>
138
+
139
+ <div class="swp-toolbar-group" style="margin: 0px auto;">
140
+ ${createButton('rotate-left', 'fas fa-undo', labels.rotateLeft, 'Rotate Left')}
141
+ ${createButton('rotate-right', 'fas fa-redo', labels.rotateRight, 'Rotate Right')}
142
+ ${createButton('flip-h', 'fas fa-arrows-alt-h', labels.flipH, 'Flip Horizontal')}
143
+ ${createButton('flip-v', 'fas fa-arrows-alt-v', labels.flipV, 'Flip Vertical')}
144
+ </div>
145
+
146
+ <div class="swp-toolbar-group">
147
+ ${createButton('reset', 'fas fa-history', labels.reset, 'Reset')}
148
+ </div>
149
+
150
+ <div class="swp-toolbar-group">
151
+ ${createButton('toggle-resize', 'fas fa-expand-arrows-alt', labels.resize, 'Resize')}
152
+ ${createButton('toggle-adjustments', 'fas fa-sliders-h', labels.adjust, 'Adjustments')}
153
+ ${createButton('toggle-filters', 'fas fa-magic', labels.filters, 'Filters')}
154
+ </div>
155
+ <div class="swp-toolbar-group">
156
+ <button class="swp-btn swp-btn-primary${!showLabels || labels.save === null ? ' swp-btn-icon-only' : ''}" data-action="download" title="Download">
157
+ ${showIcons ? '<span class="swp-icon"><i class="fas fa-save"></i></span>' : ''}
158
+ ${showLabels && labels.save !== null ? `<span>${labels.save}</span>` : ''}
159
+ </button>
160
+ </div>
161
+ `;
162
+ }
163
+
164
+ createAdjustmentsPanelHTML() {
165
+ return `
166
+ <h3>Adjustments</h3>
167
+ <div class="swp-adjustment">
168
+ <label>Brightness</label>
169
+ <input type="range" id="swp-brightness" min="0" max="200" value="100">
170
+ <span class="swp-value">100%</span>
171
+ </div>
172
+ <div class="swp-adjustment">
173
+ <label>Contrast</label>
174
+ <input type="range" id="swp-contrast" min="0" max="200" value="100">
175
+ <span class="swp-value">100%</span>
176
+ </div>
177
+ <div class="swp-adjustment">
178
+ <label>Saturation</label>
179
+ <input type="range" id="swp-saturation" min="0" max="200" value="100">
180
+ <span class="swp-value">100%</span>
181
+ </div>
182
+ `;
183
+ }
184
+
185
+ createResizePanelHTML() {
186
+ return `
187
+ <h3>Resize Image</h3>
188
+ <div class="swp-resize-controls">
189
+ <div class="swp-adjustment">
190
+ <label>Width (px)</label>
191
+ <input type="number" id="swp-resize-width" min="1" max="5000" value="800">
192
+ </div>
193
+ <div class="swp-adjustment">
194
+ <label>Height (px)</label>
195
+ <input type="number" id="swp-resize-height" min="1" max="5000" value="600">
196
+ </div>
197
+ <div class="swp-adjustment">
198
+ <label>
199
+ <input type="checkbox" id="swp-maintain-ratio" checked>
200
+ Maintain aspect ratio
201
+ </label>
202
+ </div>
203
+ <button class="swp-btn swp-btn-primary" data-action="apply-resize">Apply Resize</button>
204
+ </div>
205
+ `;
206
+ }
207
+
208
+ createFiltersPanelHTML() {
209
+ const filters = [
210
+ { name: 'none', label: 'None' },
211
+ { name: 'grayscale', label: 'Grayscale' },
212
+ { name: 'sepia', label: 'Sepia' },
213
+ { name: 'invert', label: 'Invert' },
214
+ { name: 'blur', label: 'Blur' }
215
+ ];
216
+
217
+ return `
218
+ <h3>Filters</h3>
219
+ <div class="swp-filters-grid">
220
+ ${filters.map(filter => `
221
+ <button class="swp-filter-btn ${filter.name === 'none' ? 'active' : ''}"
222
+ data-filter="${filter.name}">
223
+ <span class="swp-filter-preview" data-filter="${filter.name}"></span>
224
+ <span>${filter.label}</span>
225
+ </button>
226
+ `).join('')}
227
+ </div>
228
+ `;
229
+ }
230
+
231
+ bindEvents() {
232
+ // Toolbar buttons
233
+ this.container.querySelectorAll('[data-action]').forEach(btn => {
234
+ btn.addEventListener('click', (e) => {
235
+ const action = e.currentTarget.getAttribute('data-action');
236
+ this.handleAction(action);
237
+ });
238
+ });
239
+
240
+ // File input
241
+ const fileInput = this.container.querySelector('#swp-file-input');
242
+ if (fileInput) {
243
+ fileInput.addEventListener('change', (e) => {
244
+ const file = e.target.files[0];
245
+ if (file) {
246
+ const reader = new FileReader();
247
+ reader.onload = (event) => {
248
+ this.loadImage(event.target.result);
249
+ };
250
+ reader.readAsDataURL(file);
251
+ }
252
+ });
253
+ }
254
+
255
+ // Adjustment sliders
256
+ ['brightness', 'contrast', 'saturation'].forEach(adj => {
257
+ const slider = this.container.querySelector(`#swp-${adj}`);
258
+ if (slider) {
259
+ slider.addEventListener('input', (e) => {
260
+ const value = parseInt(e.target.value);
261
+ e.target.nextElementSibling.textContent = value + '%';
262
+ this.setAdjustment(adj, value);
263
+ });
264
+ }
265
+ });
266
+
267
+ // Filter buttons
268
+ this.container.querySelectorAll('[data-filter]').forEach(btn => {
269
+ if (btn.classList.contains('swp-filter-btn')) {
270
+ btn.addEventListener('click', (e) => {
271
+ const filter = e.currentTarget.getAttribute('data-filter');
272
+ this.applyFilter(filter);
273
+
274
+ // Update active state
275
+ this.container.querySelectorAll('.swp-filter-btn').forEach(b => {
276
+ b.classList.remove('active');
277
+ });
278
+ e.currentTarget.classList.add('active');
279
+ });
280
+ }
281
+ });
282
+
283
+ }
284
+
285
+ bindResizeInputs() {
286
+ const widthInput = this.container.querySelector('#swp-resize-width');
287
+ const heightInput = this.container.querySelector('#swp-resize-height');
288
+ const maintainRatio = this.container.querySelector('#swp-maintain-ratio');
289
+
290
+ if (!widthInput || !heightInput || !this.currentImage) return;
291
+
292
+ // Remove old event listeners by cloning and replacing
293
+ const newWidthInput = widthInput.cloneNode(true);
294
+ const newHeightInput = heightInput.cloneNode(true);
295
+ widthInput.parentNode.replaceChild(newWidthInput, widthInput);
296
+ heightInput.parentNode.replaceChild(newHeightInput, heightInput);
297
+
298
+ const aspectRatio = this.currentImage.width / this.currentImage.height;
299
+
300
+ // Real-time width adjustment with aspect ratio
301
+ newWidthInput.addEventListener('input', (e) => {
302
+ const newWidth = parseInt(e.target.value);
303
+
304
+ if (maintainRatio.checked) {
305
+ const newHeight = Math.round(newWidth / aspectRatio);
306
+ newHeightInput.value = newHeight;
307
+ }
308
+
309
+ // Real-time preview
310
+ this.updateCanvasSize(parseInt(newWidthInput.value), parseInt(newHeightInput.value));
311
+ });
312
+
313
+ // Real-time height adjustment with aspect ratio
314
+ newHeightInput.addEventListener('input', (e) => {
315
+ const newHeight = parseInt(e.target.value);
316
+
317
+ if (maintainRatio.checked) {
318
+ const newWidth = Math.round(newHeight * aspectRatio);
319
+ newWidthInput.value = newWidth;
320
+ }
321
+
322
+ // Real-time preview
323
+ this.updateCanvasSize(parseInt(newWidthInput.value), parseInt(newHeightInput.value));
324
+ });
325
+ }
326
+
327
+ updateCanvasSize(width, height) {
328
+ if (!this.currentImage || !width || !height || width < 1 || height < 1) return;
329
+
330
+ // Update canvas dimensions
331
+ this.canvas.width = width;
332
+ this.canvas.height = height;
333
+
334
+ // Redraw image at new size
335
+ this.drawImage();
336
+ }
337
+
338
+ handleAction(action) {
339
+ switch(action) {
340
+ case 'upload':
341
+ this.container.querySelector('#swp-file-input').click();
342
+ break;
343
+ case 'rotate-left':
344
+ this.rotate(-90);
345
+ break;
346
+ case 'rotate-right':
347
+ this.rotate(90);
348
+ break;
349
+ case 'flip-h':
350
+ this.flip('horizontal');
351
+ break;
352
+ case 'flip-v':
353
+ this.flip('vertical');
354
+ break;
355
+ case 'toggle-adjustments':
356
+ this.togglePanel('.swp-adjustments-panel');
357
+ break;
358
+ case 'toggle-filters':
359
+ this.togglePanel('.swp-filters-panel');
360
+ break;
361
+ case 'toggle-resize':
362
+ this.togglePanel('.swp-resize-panel');
363
+ break;
364
+ case 'apply-resize':
365
+ this.applyResize();
366
+ break;
367
+ case 'reset':
368
+ this.reset();
369
+ break;
370
+ case 'download':
371
+ this.download();
372
+ break;
373
+ }
374
+ }
375
+
376
+ togglePanel(selector) {
377
+ const panel = this.container.querySelector(selector);
378
+ if (panel) {
379
+ panel.classList.toggle('active');
380
+
381
+ // Close other panels
382
+ const panels = ['.swp-adjustments-panel', '.swp-filters-panel', '.swp-resize-panel'];
383
+ panels.forEach(p => {
384
+ if (p !== selector) {
385
+ this.container.querySelector(p)?.classList.remove('active');
386
+ }
387
+ });
388
+ }
389
+ }
390
+
391
+ loadImage(imageUrl) {
392
+ const img = new Image();
393
+ img.crossOrigin = 'anonymous';
394
+
395
+ img.onload = () => {
396
+ this.originalImage = img;
397
+ this.currentImage = img;
398
+
399
+ // Set canvas to exact image dimensions
400
+ this.canvas.width = img.width;
401
+ this.canvas.height = img.height;
402
+
403
+ // Update resize inputs
404
+ const widthInput = this.container.querySelector('#swp-resize-width');
405
+ const heightInput = this.container.querySelector('#swp-resize-height');
406
+ if (widthInput) widthInput.value = img.width;
407
+ if (heightInput) heightInput.value = img.height;
408
+
409
+ // Bind resize inputs with aspect ratio
410
+ this.bindResizeInputs();
411
+
412
+ this.drawImage();
413
+ this.emit('load');
414
+ };
415
+
416
+ img.onerror = () => {
417
+ console.error('Failed to load image');
418
+ };
419
+
420
+ img.src = imageUrl;
421
+ }
422
+
423
+ drawImage() {
424
+ if (!this.currentImage) return;
425
+
426
+ const canvas = this.canvas;
427
+ const ctx = this.ctx;
428
+
429
+ // Clear canvas
430
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
431
+
432
+ // Apply transformations
433
+ ctx.save();
434
+
435
+ // Move to center for transformations
436
+ ctx.translate(canvas.width / 2, canvas.height / 2);
437
+
438
+ // Apply rotation
439
+ ctx.rotate(this.currentState.rotation * Math.PI / 180);
440
+
441
+ // Apply flips
442
+ ctx.scale(
443
+ this.currentState.flipH ? -1 : 1,
444
+ this.currentState.flipV ? -1 : 1
445
+ );
446
+
447
+ // Apply CSS filters
448
+ ctx.filter = this.getFilterString();
449
+
450
+ // Draw image at actual size (canvas matches image dimensions)
451
+ ctx.drawImage(
452
+ this.currentImage,
453
+ -canvas.width / 2,
454
+ -canvas.height / 2,
455
+ canvas.width,
456
+ canvas.height
457
+ );
458
+
459
+ ctx.restore();
460
+
461
+ this.emit('change');
462
+ }
463
+
464
+ getFilterString() {
465
+ let filters = [];
466
+
467
+ if (this.currentState.brightness !== 100) {
468
+ filters.push(`brightness(${this.currentState.brightness}%)`);
469
+ }
470
+ if (this.currentState.contrast !== 100) {
471
+ filters.push(`contrast(${this.currentState.contrast}%)`);
472
+ }
473
+ if (this.currentState.saturation !== 100) {
474
+ filters.push(`saturate(${this.currentState.saturation}%)`);
475
+ }
476
+
477
+ switch(this.currentState.filter) {
478
+ case 'grayscale':
479
+ filters.push('grayscale(100%)');
480
+ break;
481
+ case 'sepia':
482
+ filters.push('sepia(100%)');
483
+ break;
484
+ case 'invert':
485
+ filters.push('invert(100%)');
486
+ break;
487
+ case 'blur':
488
+ filters.push('blur(5px)');
489
+ break;
490
+ }
491
+
492
+ return filters.length > 0 ? filters.join(' ') : 'none';
493
+ }
494
+
495
+ rotate(degrees) {
496
+ if (!this.currentImage) return;
497
+
498
+ this.currentState.rotation = (this.currentState.rotation + degrees) % 360;
499
+ this.drawImage();
500
+ }
501
+
502
+ flip(direction) {
503
+ if (!this.currentImage) return;
504
+
505
+ if (direction === 'horizontal') {
506
+ this.currentState.flipH = !this.currentState.flipH;
507
+ } else if (direction === 'vertical') {
508
+ this.currentState.flipV = !this.currentState.flipV;
509
+ }
510
+
511
+ this.drawImage();
512
+ }
513
+
514
+ setAdjustment(adjustment, value) {
515
+ if (!this.currentImage) return;
516
+
517
+ this.currentState[adjustment] = value;
518
+ this.drawImage();
519
+ }
520
+
521
+ applyFilter(filterName) {
522
+ if (!this.currentImage) return;
523
+
524
+ this.currentState.filter = filterName;
525
+ this.drawImage();
526
+ }
527
+
528
+ applyResize() {
529
+ if (!this.currentImage) return;
530
+
531
+ const widthInput = this.container.querySelector('#swp-resize-width');
532
+ const heightInput = this.container.querySelector('#swp-resize-height');
533
+
534
+ const newWidth = parseInt(widthInput.value);
535
+ const newHeight = parseInt(heightInput.value);
536
+
537
+ if (!newWidth || !newHeight || newWidth < 1 || newHeight < 1) {
538
+ alert('Please enter valid dimensions');
539
+ return;
540
+ }
541
+
542
+ // Canvas size is already updated by real-time preview
543
+ // Now we need to make the resize permanent by updating the currentImage
544
+
545
+ // Create temporary canvas with current canvas content
546
+ const tempCanvas = document.createElement('canvas');
547
+ tempCanvas.width = this.canvas.width;
548
+ tempCanvas.height = this.canvas.height;
549
+ const tempCtx = tempCanvas.getContext('2d');
550
+
551
+ // Copy current canvas content
552
+ tempCtx.drawImage(this.canvas, 0, 0);
553
+
554
+ // Load as new current image
555
+ const resizedImage = new Image();
556
+ resizedImage.onload = () => {
557
+ this.currentImage = resizedImage;
558
+ this.originalImage = resizedImage;
559
+
560
+ // Rebind resize inputs with new aspect ratio
561
+ this.bindResizeInputs();
562
+
563
+ this.drawImage();
564
+ };
565
+ resizedImage.src = tempCanvas.toDataURL();
566
+ }
567
+
568
+ crop(x, y, width, height) {
569
+ if (!this.currentImage) return;
570
+
571
+ // Create temporary canvas for cropping
572
+ const tempCanvas = document.createElement('canvas');
573
+ tempCanvas.width = width;
574
+ tempCanvas.height = height;
575
+ const tempCtx = tempCanvas.getContext('2d');
576
+
577
+ // Draw cropped area
578
+ tempCtx.drawImage(this.canvas, x, y, width, height, 0, 0, width, height);
579
+
580
+ // Load cropped image
581
+ const croppedImage = new Image();
582
+ croppedImage.onload = () => {
583
+ this.currentImage = croppedImage;
584
+ this.drawImage();
585
+ };
586
+ croppedImage.src = tempCanvas.toDataURL();
587
+ }
588
+
589
+ reset() {
590
+ // Reset all states
591
+ this.currentState = {
592
+ brightness: 100,
593
+ contrast: 100,
594
+ saturation: 100,
595
+ rotation: 0,
596
+ flipH: false,
597
+ flipV: false,
598
+ filter: 'none'
599
+ };
600
+
601
+ // Reset sliders
602
+ ['brightness', 'contrast', 'saturation'].forEach(adj => {
603
+ const slider = this.container.querySelector(`#swp-${adj}`);
604
+ if (slider) {
605
+ slider.value = 100;
606
+ slider.nextElementSibling.textContent = '100%';
607
+ }
608
+ });
609
+
610
+ // Reset filter selection
611
+ this.container.querySelectorAll('.swp-filter-btn').forEach(btn => {
612
+ btn.classList.remove('active');
613
+ if (btn.getAttribute('data-filter') === 'none') {
614
+ btn.classList.add('active');
615
+ }
616
+ });
617
+
618
+ // Reset image
619
+ if (this.originalImage) {
620
+ this.currentImage = this.originalImage;
621
+ this.drawImage();
622
+ }
623
+ }
624
+
625
+ getImageData(format = 'jpeg', quality = 0.9) {
626
+ if (!this.canvas) return null;
627
+
628
+ const mimeType = format === 'png' ? 'image/png' : 'image/jpeg';
629
+ return this.canvas.toDataURL(mimeType, quality);
630
+ }
631
+
632
+ download() {
633
+ const dataUrl = this.getImageData('png');
634
+ if (!dataUrl) return;
635
+
636
+ const link = document.createElement('a');
637
+ link.download = `swp-edited-${Date.now()}.png`;
638
+ link.href = dataUrl;
639
+ link.click();
640
+
641
+ this.emit('save');
642
+ }
643
+
644
+ // Event system
645
+ on(event, callback) {
646
+ if (!this.eventListeners[event]) {
647
+ this.eventListeners[event] = [];
648
+ }
649
+ this.eventListeners[event].push(callback);
650
+ }
651
+
652
+ emit(event, data) {
653
+ if (this.eventListeners[event]) {
654
+ this.eventListeners[event].forEach(callback => {
655
+ callback(data);
656
+ });
657
+ }
658
+ }
659
+ }
660
+
661
+ // Auto-initialize for declarative approach
662
+ if (typeof document !== 'undefined') {
663
+ document.addEventListener('DOMContentLoaded', () => {
664
+ const declarativeContainers = document.querySelectorAll('[data-swp]');
665
+ declarativeContainers.forEach(container => {
666
+ // Parse data attributes for options
667
+ const options = {};
668
+
669
+ // Parse basic options
670
+ if (container.dataset.swpImageUrl) {
671
+ options.imageUrl = container.dataset.swpImageUrl;
672
+ }
673
+ if (container.dataset.swpWidth) {
674
+ options.width = parseInt(container.dataset.swpWidth);
675
+ }
676
+ if (container.dataset.swpHeight) {
677
+ options.height = parseInt(container.dataset.swpHeight);
678
+ }
679
+
680
+ // Parse boolean options
681
+ if (container.dataset.swpShowIcons !== undefined) {
682
+ options.showIcons = container.dataset.swpShowIcons !== 'false';
683
+ }
684
+ if (container.dataset.swpShowLabels !== undefined) {
685
+ options.showLabels = container.dataset.swpShowLabels !== 'false';
686
+ }
687
+
688
+ // Parse labels object
689
+ if (container.dataset.swpLabels) {
690
+ try {
691
+ // Support both JSON and simple key:value format
692
+ let labelsStr = container.dataset.swpLabels.trim();
693
+
694
+ // If it looks like JSON, parse as JSON
695
+ if (labelsStr.startsWith('{')) {
696
+ options.labels = JSON.parse(labelsStr);
697
+ } else {
698
+ // Parse simple format: "upload: 'text'; resize: 'text'"
699
+ options.labels = {};
700
+ const pairs = labelsStr.split(';');
701
+ pairs.forEach(pair => {
702
+ const [key, value] = pair.split(':').map(s => s.trim());
703
+ if (key && value) {
704
+ // Remove quotes and parse null
705
+ const cleanValue = value.replace(/^['"]|['"]$/g, '');
706
+ options.labels[key] = cleanValue === 'null' ? null : cleanValue;
707
+ }
708
+ });
709
+ }
710
+ } catch (e) {
711
+ console.error('Failed to parse data-swp-labels:', e);
712
+ }
713
+ }
714
+
715
+ new SWP(container, options);
716
+ });
717
+ });
718
+ }
719
+
720
+ // Export
721
+ export default SWP;
722
+
723
+ // Also attach to window for non-module usage
724
+ if (typeof window !== 'undefined') {
725
+ window.SWP = SWP;
726
+ }