senangwebs-photobooth 1.0.1 → 1.0.2

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