@innovastudio/contentbuilder 1.5.62 → 1.5.63

Sign up to get free protection for your applications and to get access to all the features.
@@ -4645,13 +4645,9 @@ class Util {
4645
4645
  // Hide other pops
4646
4646
  let elms = document.querySelectorAll('.is-pop.active');
4647
4647
  elms.forEach(otherPop => {
4648
+ // do not close parent/caller pop
4648
4649
  let close = true;
4649
- let callerPop;
4650
- if (overlay) {
4651
- // do not close parent/caller pop
4652
- callerPop = btn.closest('.is-pop');
4653
- if (callerPop === otherPop) close = false;
4654
- }
4650
+ if (otherPop.contains(btn)) close = false;
4655
4651
  if (otherPop !== pop && close) {
4656
4652
  otherPop.style.display = '';
4657
4653
  dom.removeClass(otherPop, 'active');
@@ -4677,9 +4673,9 @@ class Util {
4677
4673
  if (!pop.contains(e.target) && !btn.contains(e.target)) {
4678
4674
  // click outside
4679
4675
 
4680
- if (e.target.closest('.is-pop-overlay')) return;
4681
- let overlayExist = document.querySelector('.is-pop-overlay');
4682
- if (overlayExist) return;
4676
+ // if(e.target.closest('.is-pop-overlay')) return;
4677
+ // let overlayExist = document.querySelector('.is-pop-overlay');
4678
+ // if(overlayExist) return;
4683
4679
 
4684
4680
  // hide
4685
4681
  this.hidePop(pop);
@@ -27731,7 +27727,7 @@ function () {
27731
27727
  return EventEmitter;
27732
27728
  }();
27733
27729
 
27734
- var EventEmitter$1$1 = EventEmitter$2;
27730
+ var EventEmitter$1$2 = EventEmitter$2;
27735
27731
 
27736
27732
  /*
27737
27733
  Copyright (c) 2019 Daybrush
@@ -40242,7 +40238,7 @@ var MoveableManager$1 = /*#__PURE__*/function (_super) {
40242
40238
  "mouseEnter": null,
40243
40239
  "mouseLeave": null
40244
40240
  };
40245
- _this._emitter = new EventEmitter$1$1();
40241
+ _this._emitter = new EventEmitter$1$2();
40246
40242
  _this._prevTarget = null;
40247
40243
  _this._prevDragArea = false;
40248
40244
  _this._observer = null;
@@ -42171,7 +42167,7 @@ function __spreadArrays() {
42171
42167
  * Implement EventEmitter on object or component.
42172
42168
  */
42173
42169
 
42174
- var EventEmitter =
42170
+ var EventEmitter$1 =
42175
42171
  /*#__PURE__*/
42176
42172
  function () {
42177
42173
  function EventEmitter() {
@@ -42404,7 +42400,7 @@ function () {
42404
42400
  return EventEmitter;
42405
42401
  }();
42406
42402
 
42407
- var EventEmitter$1 = EventEmitter;
42403
+ var EventEmitter$1$1 = EventEmitter$1;
42408
42404
 
42409
42405
  /**
42410
42406
  * Moveable is Draggable! Resizable! Scalable! Rotatable!
@@ -42518,7 +42514,7 @@ function (_super) {
42518
42514
  });
42519
42515
  })], MoveableManager);
42520
42516
  return MoveableManager;
42521
- }(EventEmitter$1);
42517
+ }(EventEmitter$1$1);
42522
42518
 
42523
42519
  var Moveable$1 =
42524
42520
  /*#__PURE__*/
@@ -63807,6 +63803,418 @@ class RoundedSlider {
63807
63803
  }
63808
63804
  }
63809
63805
 
63806
+ class EventEmitter {
63807
+ constructor() {
63808
+ this.listeners = {};
63809
+ }
63810
+ on(event, listener) {
63811
+ if (!this.listeners[event]) {
63812
+ this.listeners[event] = [];
63813
+ }
63814
+ this.listeners[event].push(listener);
63815
+ }
63816
+ emit(event, ...args) {
63817
+ if (this.listeners[event]) {
63818
+ this.listeners[event].forEach(listener => {
63819
+ listener(...args);
63820
+ });
63821
+ }
63822
+ }
63823
+ }
63824
+
63825
+ /*
63826
+ // Usage example
63827
+
63828
+ const myGradientSlider = document.querySelector('.myslider');
63829
+ const slider = new GradientSlider(myGradientSlider);
63830
+ slider.draw([
63831
+ { color: 'rgba(4, 98, 183, 1)', position: 0 },
63832
+ { color: 'rgba(97, 110, 204, 1)', position: 25 },
63833
+ { color: 'rgba(246, 108, 168, 1)', position: 50 },
63834
+ { color: 'rgba(255, 211, 111, 1)', position: 75 },
63835
+ { color: 'rgba(255, 253, 193, 1)', position: 100 }
63836
+ ]
63837
+ );
63838
+
63839
+ slider.on('slideStart', (sliderPoint) => {
63840
+ console.log('Sliding started', sliderPoint);
63841
+ });
63842
+ slider.on('slideEnd', () => {
63843
+ console.log('Sliding ended');
63844
+ });
63845
+ slider.on('change', (pointsArray) => {
63846
+ console.log('Slider changed', pointsArray);
63847
+ });
63848
+ slider.on('slideChange', (pointsArray) => {
63849
+ console.log('Slider changed', pointsArray);
63850
+ });
63851
+ */
63852
+
63853
+ class GradientSlider extends EventEmitter {
63854
+ constructor(element, settings = {}) {
63855
+ super(); // Call the constructor of EventEmitter first
63856
+
63857
+ const defaults = {
63858
+ label: 'Gradient Slider'
63859
+ };
63860
+ this.opts = Object.assign(this, defaults, settings);
63861
+ this.element = element;
63862
+
63863
+ // Generate a unique ID for this slider instance
63864
+ this.uniqueId = GradientSlider.getUniqueId();
63865
+ this.drag = this.drag.bind(this);
63866
+ this.dragStop = this.dragStop.bind(this);
63867
+ this.addColorOnClick = this.addColorOnClick.bind(this);
63868
+ }
63869
+
63870
+ // Static method to generate a unique ID
63871
+ static getUniqueId() {
63872
+ if (!this.uniqueCounter) {
63873
+ this.uniqueCounter = 0; // Initialize if it doesn't exist
63874
+ }
63875
+
63876
+ this.uniqueCounter++; // Increment the counter for each new instance
63877
+ return `gradientSliderLabel${this.uniqueCounter}`; // Return a unique ID
63878
+ }
63879
+
63880
+ constructGradientString(pointsArray) {
63881
+ const gradientParts = pointsArray.map(color => `${color.color} ${color.position}%`);
63882
+ return `linear-gradient(90deg, ${gradientParts.join(', ')})`;
63883
+ }
63884
+
63885
+ /*
63886
+ pointsArray = [
63887
+ { color: 'rgba(4, 98, 183, 1)', position: 0 },
63888
+ { color: 'rgba(246, 108, 168, 1)', position: 50 },
63889
+ { color: 'rgba(255, 253, 193, 1)', position: 100 }
63890
+ ]
63891
+ */
63892
+ draw(pointsArray) {
63893
+ this.sliderPoints = [];
63894
+ let label = this.element.querySelector('.visually-hidden');
63895
+ if (!label) {
63896
+ label = document.createElement('div');
63897
+ label.className = 'visually-hidden';
63898
+ label.id = this.uniqueId;
63899
+ label.textContent = this.opts.label;
63900
+ this.element.prepend(label); // Add label to the slider element
63901
+ }
63902
+
63903
+ if (!this.sliderContainer) {
63904
+ this.sliderContainer = document.createElement('div');
63905
+ this.sliderContainer.className = 'gradient-slider-container';
63906
+ this.element.insertAdjacentElement('afterend', this.sliderContainer);
63907
+ this.shadowContainer = document.createElement('div');
63908
+ this.shadowContainer.classList.add('gradient-slider-container-shadow');
63909
+ this.sliderContainer.insertAdjacentElement('afterbegin', this.shadowContainer);
63910
+ this.setupAddColorOnClick();
63911
+ } else {
63912
+ this.clearPoints();
63913
+ }
63914
+ pointsArray.forEach(point => {
63915
+ const knob = document.createElement('div');
63916
+ knob.className = 'slider-point';
63917
+ knob.style.left = `${point.position}%`;
63918
+ knob.style.backgroundColor = point.color;
63919
+ this.sliderContainer.insertAdjacentElement('beforeend', knob);
63920
+ this.sliderPoints.push(knob); // this.sliderPoints contains list of knobs
63921
+
63922
+ // Accessibility Enhancements
63923
+ knob.setAttribute('tabindex', '0'); // Make it focusable
63924
+ knob.setAttribute('role', 'slider');
63925
+ knob.setAttribute('aria-valuemin', '0');
63926
+ knob.setAttribute('aria-valuemax', '100');
63927
+ knob.setAttribute('aria-valuenow', point.position);
63928
+ knob.setAttribute('aria-valuetext', `Color: ${point.color}, Position: ${point.position}%`);
63929
+ knob.setAttribute('aria-labelledby', this.uniqueId);
63930
+
63931
+ // Initialize drag functionality
63932
+ knob.addEventListener('mousedown', this.dragStart.bind(this, knob), {
63933
+ passive: false
63934
+ });
63935
+ knob.addEventListener('touchstart', this.dragStart.bind(this, knob), {
63936
+ passive: false
63937
+ });
63938
+
63939
+ // Keyboard interaction
63940
+ knob.addEventListener('keydown', e => this.handleKeydown(e, knob));
63941
+ });
63942
+
63943
+ // Update the slider's gradient background
63944
+ const cssGradient = this.constructGradientString(pointsArray);
63945
+ this.setContainerGradient(cssGradient);
63946
+
63947
+ // show hide container
63948
+ this.showHideContainer();
63949
+ }
63950
+ showHideContainer() {
63951
+ let hide = false;
63952
+ if (this.sliderPoints.length === 2) {
63953
+ let color1 = this.sliderPoints[0].style.backgroundColor;
63954
+ let color2 = this.sliderPoints[1].style.backgroundColor;
63955
+ if (color1 === 'rgba(255, 255, 255, 1)' && color2 === 'rgba(255, 255, 255, 1)') {
63956
+ hide = true;
63957
+ }
63958
+ if (color1 === 'rgb(255, 255, 255)' && color2 === 'rgb(255, 255, 255)') {
63959
+ hide = true;
63960
+ }
63961
+ }
63962
+ if (hide) {
63963
+ this.sliderContainer.style.display = 'none';
63964
+ } else {
63965
+ this.sliderContainer.style.display = '';
63966
+ }
63967
+ }
63968
+ updateColor(color, index) {
63969
+ // update array (this also applies to the knob, because this.sliderPoints contains list of knobs)
63970
+ this.sliderPoints[index].style.backgroundColor = color;
63971
+
63972
+ // show hide container
63973
+ this.showHideContainer();
63974
+ this.update();
63975
+ }
63976
+ removeColor(index) {
63977
+ // remove knob
63978
+ let sliderKnobs = this.sliderContainer.querySelectorAll('.slider-point');
63979
+ let knob = sliderKnobs[index];
63980
+ knob.removeEventListener('mousedown', this.dragStart);
63981
+ knob.removeEventListener('touchstart', this.dragStart);
63982
+ knob.remove();
63983
+
63984
+ // update array
63985
+ this.sliderPoints.splice(index, 1);
63986
+ this.update();
63987
+ }
63988
+ setupAddColorOnClick() {
63989
+ this.sliderContainer.addEventListener('click', this.addColorOnClick);
63990
+ // this.sliderContainer.addEventListener('touchstart', this.addColorOnClick, {passive: true});
63991
+
63992
+ // Indicate that the user can add a knob by changing the cursor
63993
+ this.sliderContainer.style.cursor = 'copy'; // Or use a custom cursor icon that indicates addition
63994
+ }
63995
+
63996
+ interpolateColor(color1) {
63997
+ //, color2, fraction
63998
+ // Iimplement color interpolation logic.
63999
+ // fraction is a value between 0 and 1 indicating the relative position of the new stop.
64000
+ return color1; // for simplicity, just return color1
64001
+ }
64002
+
64003
+ addColorOnClick(e) {
64004
+ /*
64005
+ // Prevent the event if it's a touch event to stop it from triggering click events afterwards
64006
+ e.preventDefault();
64007
+ // Determine whether this is a touch event and extract the appropriate X position
64008
+ let clientX;
64009
+ if (e.touches) {
64010
+ clientX = e.touches[0].clientX; // Use the first touch point
64011
+ } else {
64012
+ clientX = e.clientX; // Use the mouse click position
64013
+ }
64014
+ */
64015
+ let clientX = e.clientX;
64016
+
64017
+ // Ensure the action is triggered only when the container itself is touched, not its children
64018
+ if (!e.target.classList.contains('gradient-slider-container-shadow')) return;
64019
+ const rect = this.sliderContainer.getBoundingClientRect();
64020
+ const clickX = clientX - rect.left; // Click position within the sliderContainer, in pixels
64021
+ const width = rect.width;
64022
+ const positionPercentage = clickX / width * 100;
64023
+
64024
+ // Find the two closest stops surrounding the click position
64025
+ const sortedPoints = this.sliderPoints.map(knob => {
64026
+ return {
64027
+ position: parseFloat(knob.style.left),
64028
+ color: knob.style.backgroundColor
64029
+ };
64030
+ }).sort((a, b) => a.position - b.position);
64031
+
64032
+ // Find the surrounding stops
64033
+ let lowerStop = sortedPoints[0]; // Initialize with the first stop
64034
+ let upperStop = sortedPoints[sortedPoints.length - 1]; // Initialize with the last stop
64035
+ for (let i = 0; i < sortedPoints.length - 1; i++) {
64036
+ if (positionPercentage >= sortedPoints[i].position && positionPercentage <= sortedPoints[i + 1].position) {
64037
+ lowerStop = sortedPoints[i];
64038
+ upperStop = sortedPoints[i + 1];
64039
+ break;
64040
+ }
64041
+ }
64042
+
64043
+ // Calculate the fraction of the position between the lower and upper stops
64044
+ const fraction = (positionPercentage - lowerStop.position) / (upperStop.position - lowerStop.position);
64045
+
64046
+ // Interpolate the color
64047
+ const interpolatedColor = this.interpolateColor(lowerStop.color, upperStop.color, fraction);
64048
+
64049
+ // Now, add the color knob at the calculated position with the interpolated color
64050
+ this.addColor(interpolatedColor, positionPercentage);
64051
+ }
64052
+ addColor(color, position) {
64053
+ position = position - 4; // adjustment
64054
+
64055
+ // First, find the correct position to insert the new knob
64056
+ let insertAt = this.sliderPoints.findIndex(knob => {
64057
+ const knobPosition = parseFloat(knob.style.left);
64058
+ return position < knobPosition;
64059
+ });
64060
+
64061
+ // If no suitable position is found (i.e., we're inserting at the end or the array is empty), adjust insertAt accordingly
64062
+ if (insertAt === -1) insertAt = this.sliderPoints.length;
64063
+
64064
+ // Create and configure the new knob
64065
+ const knob = document.createElement('div');
64066
+ knob.className = 'slider-point';
64067
+ knob.style.left = `${position}%`;
64068
+ knob.style.backgroundColor = color;
64069
+
64070
+ // Accessibility Enhancements
64071
+ knob.setAttribute('tabindex', '0');
64072
+ knob.setAttribute('role', 'slider');
64073
+ knob.setAttribute('aria-valuemin', '0');
64074
+ knob.setAttribute('aria-valuemax', '100');
64075
+ knob.setAttribute('aria-valuenow', position);
64076
+ knob.setAttribute('aria-valuetext', `Color: ${color}, Position: ${position}%`);
64077
+ knob.setAttribute('aria-labelledby', this.uniqueId);
64078
+
64079
+ // Initialize drag functionality
64080
+ knob.addEventListener('mousedown', this.dragStart.bind(this, knob), {
64081
+ passive: false
64082
+ });
64083
+ knob.addEventListener('touchstart', this.dragStart.bind(this, knob), {
64084
+ passive: false
64085
+ });
64086
+ if (insertAt === this.sliderPoints.length) {
64087
+ // If inserting at the end, simply add the knob to the container
64088
+ this.sliderContainer.appendChild(knob);
64089
+ } else {
64090
+ // Otherwise, insert the knob before the knob currently at the insertAt position
64091
+ this.sliderContainer.insertBefore(knob, this.sliderPoints[insertAt]);
64092
+ }
64093
+
64094
+ // Update the internal representation and the displayed gradient
64095
+ this.sliderPoints.splice(insertAt, 0, knob); // Insert the knob into the internal array at the correct position
64096
+
64097
+ this.update(true); // Update the gradient and emit the change event
64098
+ }
64099
+
64100
+ update(triggerAddpoint) {
64101
+ // Generate pointsArray based on the points color & position
64102
+ const pointsArray = this.sliderPoints.map(point => {
64103
+ const position = parseFloat(point.style.left); // Assuming style.left is always in '%'
64104
+ return {
64105
+ color: point.style.backgroundColor,
64106
+ position: position
64107
+ };
64108
+ });
64109
+
64110
+ // Update the slider's gradient background
64111
+ const cssGradient = this.constructGradientString(pointsArray);
64112
+ this.setContainerGradient(cssGradient);
64113
+
64114
+ // Emit a "change" event with the pointsArray as the detail
64115
+ this.emit('change', pointsArray, cssGradient);
64116
+ if (triggerAddpoint) {
64117
+ this.emit('addpoint', pointsArray, cssGradient);
64118
+ }
64119
+ }
64120
+ dragStart(knob, e) {
64121
+ this.knob = knob;
64122
+ e.preventDefault();
64123
+ this.isDragging = true;
64124
+
64125
+ // Get the initial offset by subtracting the slider point's left position from the cursor's X position
64126
+ const startX = e.touches ? e.touches[0].clientX : e.clientX;
64127
+ const pointRect = knob.getBoundingClientRect();
64128
+ this.initialOffsetX = startX - pointRect.left;
64129
+ document.addEventListener('mousemove', this.drag);
64130
+ document.addEventListener('touchmove', this.drag);
64131
+ document.addEventListener('mouseup', this.dragStop);
64132
+ document.addEventListener('touchend', this.dragStop);
64133
+ this.emit('slideStart', knob);
64134
+ }
64135
+ drag(e) {
64136
+ if (!this.isDragging) return;
64137
+ const knob = this.knob;
64138
+
64139
+ // Update knob (left) position
64140
+ let touch = e.touches ? e.touches[0] : e;
64141
+ let rect = this.sliderContainer.getBoundingClientRect();
64142
+ let x = touch.clientX - rect.left - this.initialOffsetX; // Adjust position based on initial offset
64143
+ let percentage = Math.max(0, Math.min(100, x / rect.width * 100));
64144
+ knob.style.left = `${percentage}%`;
64145
+
64146
+ // Updating knob (left) position will also update the this.sliderPoints
64147
+ // console.log(this.sliderPoints[1].style.left); // to test
64148
+
64149
+ // this.update() //The code below is similar to update(), only that it triggers sliderChange
64150
+
64151
+ // Generate pointsArray based on the points color & position
64152
+ const pointsArray = this.sliderPoints.map(point => {
64153
+ const position = parseFloat(point.style.left); // Assuming style.left is always in '%'
64154
+ return {
64155
+ color: point.style.backgroundColor,
64156
+ position: position
64157
+ };
64158
+ });
64159
+
64160
+ // Update the slider's gradient background
64161
+ const cssGradient = this.constructGradientString(pointsArray);
64162
+ this.setContainerGradient(cssGradient);
64163
+
64164
+ // Emit a "slideChange" event with the pointsArray as the detail
64165
+ this.emit('slideChange', pointsArray, cssGradient);
64166
+ }
64167
+ dragStop() {
64168
+ this.isDragging = false;
64169
+ document.removeEventListener('mousemove', this.drag);
64170
+ document.removeEventListener('touchmove', this.drag);
64171
+ document.removeEventListener('mouseup', this.dragStop);
64172
+ document.removeEventListener('touchend', this.dragStop);
64173
+ this.emit('slideEnd');
64174
+ }
64175
+ clearPoints() {
64176
+ // Remove all child slider points from the container
64177
+ const sliderKnobs = this.sliderContainer.querySelectorAll('.slider-point');
64178
+ sliderKnobs.forEach(knob => {
64179
+ knob.removeEventListener('mousedown', this.dragStart);
64180
+ knob.removeEventListener('touchstart', this.dragStart);
64181
+ knob.remove();
64182
+ });
64183
+
64184
+ // update array
64185
+ this.sliderPoints = []; // Clear
64186
+ }
64187
+
64188
+ handleKeydown(e, sliderPoint) {
64189
+ const key = e.key;
64190
+ let newPosition = parseFloat(sliderPoint.style.left);
64191
+ if (key === 'ArrowRight' || key === 'ArrowUp') {
64192
+ newPosition = Math.min(100, newPosition + 1); // Increase position
64193
+ } else if (key === 'ArrowLeft' || key === 'ArrowDown') {
64194
+ newPosition = Math.max(0, newPosition - 1); // Decrease position
64195
+ } else {
64196
+ return; // Ignore other keys
64197
+ }
64198
+
64199
+ sliderPoint.style.left = `${newPosition}%`;
64200
+ sliderPoint.setAttribute('aria-valuenow', newPosition);
64201
+ sliderPoint.setAttribute('aria-valuetext', `Color: ${sliderPoint.style.backgroundColor}, Position: ${newPosition}%`);
64202
+
64203
+ // Update gradient and invoke onChange callback with the new positions
64204
+ this.dragStop(); // Simulate dragStop to update positions and gradient
64205
+ }
64206
+
64207
+ setContainerGradient(cssGradient) {
64208
+ this.shadowContainer.style.backgroundImage = cssGradient;
64209
+ }
64210
+ destroy() {
64211
+ if (this.element.nextElementSibling && this.element.nextElementSibling.classList.contains('gradient-slider-container')) {
64212
+ this.clearPoints();
64213
+ this.element.nextElementSibling.remove();
64214
+ }
64215
+ }
64216
+ }
64217
+
63810
64218
  class GradientPicker {
63811
64219
  constructor(builder) {
63812
64220
  this.builder = builder;
@@ -63829,9 +64237,10 @@ class GradientPicker {
63829
64237
  <div class="label-saved">${this.out('Saved')}:</div>
63830
64238
  <div class="div-saved"></div>
63831
64239
 
63832
- <div class="gradient-preview"></div>
63833
-
63834
64240
  <div class="label-saved">${this.out('Gradient Colors')}:</div>
64241
+
64242
+ <div class="gradient-slider" role="application"></div>
64243
+
63835
64244
  <div class="div-sort"></div>
63836
64245
 
63837
64246
  <div class="div-add">
@@ -63864,20 +64273,30 @@ class GradientPicker {
63864
64273
  }
63865
64274
  init() {
63866
64275
  this.renderSavedGradients();
63867
- const btnAddStop = this.pickGradient.querySelector('.btn-addstop');
63868
- if (btnAddStop) btnAddStop.addEventListener('click', () => {
63869
- this.builder.uo.saveForUndo();
63870
- let lastColor = this.colorsArray[this.colorsArray.length - 1];
63871
- this.colorsArray.push(lastColor);
63872
- this.renderColorStops();
64276
+ const divGradSlider = this.pickGradient.querySelector('.gradient-slider');
64277
+ const gradSlider = new GradientSlider(divGradSlider, {
64278
+ label: this.out('Gradient Slider')
64279
+ });
64280
+ this.gradSlider = gradSlider;
64281
+ gradSlider.on('slideStart', () => {
64282
+ this.builder.uo.saveForUndo(true);
64283
+ });
64284
+ gradSlider.on('slideEnd', () => {
64285
+ if (this.onChange) this.onChange();
64286
+ this.builder.onChange();
64287
+ });
64288
+ gradSlider.on('slideChange', pointsArray => {
64289
+ this.colorsPosArray = pointsArray;
64290
+ this.applyGradient();
64291
+ });
64292
+ gradSlider.on('change', pointsArray => {
64293
+ this.colorsPosArray = pointsArray;
63873
64294
  this.applyGradient();
63874
64295
  if (this.onChange) this.onChange();
63875
64296
  this.builder.onChange();
63876
64297
  });
63877
- const btnSave = this.pickGradient.querySelector('.btn-save');
63878
- if (btnSave) btnSave.addEventListener('click', () => {
63879
- this.saveGradient();
63880
- this.renderSavedGradients();
64298
+ gradSlider.on('addpoint', () => {
64299
+ this.renderColorStops();
63881
64300
  });
63882
64301
  const inpAngle = this.pickGradient.querySelector('.inp-angle');
63883
64302
  this.slider = new RoundedSlider(inpAngle, {
@@ -63892,14 +64311,44 @@ class GradientPicker {
63892
64311
  if (this.onChange) this.onChange();
63893
64312
  this.builder.onChange();
63894
64313
  };
64314
+ const btnAddStop = this.pickGradient.querySelector('.btn-addstop');
64315
+ if (btnAddStop) btnAddStop.addEventListener('click', () => {
64316
+ this.builder.uo.saveForUndo();
64317
+ const lastItem = this.colorsPosArray[this.colorsPosArray.length - 1];
64318
+ lastItem.position = lastItem.position - 15;
64319
+ this.colorsPosArray.push({
64320
+ color: lastItem.color,
64321
+ position: 100
64322
+ });
64323
+ this.renderColorPositions();
64324
+ this.renderColorStops();
64325
+ this.applyGradient();
64326
+ if (this.onChange) this.onChange();
64327
+ this.builder.onChange();
64328
+ });
64329
+ const btnSave = this.pickGradient.querySelector('.btn-save');
64330
+ if (btnSave) btnSave.addEventListener('click', () => {
64331
+ this.saveGradient();
64332
+ this.renderSavedGradients();
64333
+ });
63895
64334
  const btnClear = this.pickGradient.querySelector('.btn-clear');
63896
64335
  if (btnClear) btnClear.addEventListener('click', () => {
63897
64336
  this.builder.uo.saveForUndo();
63898
- this.colorsArray = [];
63899
- this.renderColorStops();
63900
64337
  this.angle = 0;
63901
64338
  this.renderAngle();
63902
- this.applyGradient();
64339
+ this.colorsPosArray = [{
64340
+ color: 'rgba(255, 255, 255, 1)',
64341
+ position: 0
64342
+ }, {
64343
+ color: 'rgba(255, 255, 255, 1)',
64344
+ position: 100
64345
+ }];
64346
+ this.renderColorPositions();
64347
+ this.renderColorStops();
64348
+
64349
+ // this.applyGradient();
64350
+ this.element.style.backgroundImage = '';
64351
+ this.btn.style.backgroundImage = '';
63903
64352
  if (this.onChange) this.onChange();
63904
64353
  this.builder.onChange();
63905
64354
  });
@@ -63911,14 +64360,14 @@ class GradientPicker {
63911
64360
  btn.addEventListener('click', () => {
63912
64361
  this.builder.uo.saveForUndo();
63913
64362
  let cssGradient = btn.getAttribute('data-value');
63914
- this.element.style.backgroundImage = cssGradient;
63915
- this.btn.style.backgroundImage = cssGradient;
63916
- const colorsArray = this.extractColors(cssGradient);
63917
- this.colorsArray = colorsArray;
63918
64363
  const angle = this.extractAngle(cssGradient);
63919
64364
  this.angle = angle;
63920
64365
  this.renderAngle();
64366
+ const colorsPosArray = this.extractColorsPos(cssGradient);
64367
+ this.colorsPosArray = colorsPosArray;
64368
+ this.renderColorPositions();
63921
64369
  this.renderColorStops();
64370
+ this.applyGradient();
63922
64371
  if (this.onChange) this.onChange();
63923
64372
  this.builder.onChange();
63924
64373
  });
@@ -63927,7 +64376,7 @@ class GradientPicker {
63927
64376
  saveGradient() {
63928
64377
  let gradient = {
63929
64378
  angle: this.angle,
63930
- colors: this.colorsArray
64379
+ colors: this.colorsPosArray
63931
64380
  };
63932
64381
  this.savedGrads.push(gradient);
63933
64382
  localStorage.setItem('_savedgrads', JSON.stringify(this.savedGrads));
@@ -63945,11 +64394,11 @@ class GradientPicker {
63945
64394
  if (localStorage.getItem('_customgradcolors') !== null) {
63946
64395
  customgradcolors = JSON.parse(localStorage.getItem('_customgradcolors'));
63947
64396
  customgradcolors.forEach(cssGradient => {
63948
- const colorsArray = this.extractColors(cssGradient);
64397
+ const colorsPosArray = this.extractColorsPos(cssGradient);
63949
64398
  const angle = this.extractAngle(cssGradient);
63950
64399
  let gradient = {
63951
64400
  angle: angle,
63952
- colors: colorsArray
64401
+ colors: colorsPosArray
63953
64402
  };
63954
64403
  oldGrads.push(gradient);
63955
64404
  });
@@ -63963,16 +64412,16 @@ class GradientPicker {
63963
64412
  const labelSaved = this.pickGradient.querySelector('.label-saved');
63964
64413
  let html = '';
63965
64414
  savedGrads.forEach((gradient, index) => {
63966
- let deg = gradient.angle;
63967
- let colorsArray = gradient.colors;
63968
- let s = this.convertColorsToGradient(colorsArray, deg);
64415
+ let angle = gradient.angle;
64416
+ let colorsPosArray = gradient.colors;
64417
+ let cssGradient = this.constructGradientString(colorsPosArray, angle); // set the angle
63969
64418
  html += `
63970
64419
  <div>
63971
64420
  <button
63972
64421
  data-index="${index}"
63973
64422
  title="${this.out('Select')}"
63974
- class="btn-graditem" data-value="${s}"
63975
- style="background-image:${s}"></button>
64423
+ class="btn-graditem" data-value="${cssGradient}"
64424
+ style="background-image:${cssGradient}"></button>
63976
64425
 
63977
64426
  <button title="${this.out('Remove')}" class="btn-remove">
63978
64427
  <svg class="is-icon-flex"><use xlink:href="#icon-minus"></use></svg>
@@ -63993,14 +64442,14 @@ class GradientPicker {
63993
64442
  btn.addEventListener('click', () => {
63994
64443
  this.builder.uo.saveForUndo();
63995
64444
  let cssGradient = btn.getAttribute('data-value');
63996
- this.element.style.backgroundImage = cssGradient;
63997
- this.btn.style.backgroundImage = cssGradient;
63998
- const colorsArray = this.extractColors(cssGradient);
63999
- this.colorsArray = colorsArray;
64000
64445
  const angle = this.extractAngle(cssGradient);
64001
64446
  this.angle = angle;
64002
64447
  this.renderAngle();
64448
+ const colorsPosArray = this.extractColorsPos(cssGradient);
64449
+ this.colorsPosArray = colorsPosArray;
64450
+ this.renderColorPositions();
64003
64451
  this.renderColorStops();
64452
+ this.applyGradient();
64004
64453
  if (this.onChange) this.onChange();
64005
64454
  this.builder.onChange();
64006
64455
  });
@@ -64024,104 +64473,42 @@ class GradientPicker {
64024
64473
  });
64025
64474
  });
64026
64475
  }
64027
- removeItemByIndex(arr, index) {
64028
- if (index > -1 && index < arr.length) {
64029
- arr.splice(index, 1);
64030
- }
64031
- return arr;
64032
- }
64033
- extractColors(gradientString) {
64034
- // Regular expression to match rgb(a) colors and hex colors (with optional alpha)
64035
- const colorRegex = /rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*\d*\.?\d+\s*)?\)|#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?/g;
64036
- // Extract all color occurrences
64037
- const colors = gradientString.match(colorRegex);
64038
- return colors || [];
64039
- }
64040
- extractAngle(gradientString) {
64041
- const anglePattern = /linear-gradient\((\d+)deg,/;
64042
- const match = anglePattern.exec(gradientString);
64043
- if (match && match[1]) {
64044
- return parseInt(match[1], 10);
64045
- } else {
64046
- return 0;
64047
- }
64048
- }
64049
- updateColorsArray() {
64050
- let arr = [];
64051
- const divSort = this.pickGradient.querySelector('.div-sort');
64052
- divSort.querySelectorAll('button[data-color]').forEach(btn => {
64053
- let s = btn.getAttribute('data-color');
64054
- arr.push(s);
64055
- });
64056
- this.colorsArray = arr;
64057
- }
64058
- allowRemoveStop() {
64059
- const divSort = this.pickGradient.querySelector('.div-sort');
64060
- const btns = divSort.querySelectorAll('button[data-color]');
64061
- if (btns.length === 2) {
64062
- return false;
64063
- } else {
64064
- return true;
64065
- }
64066
- }
64067
- convertColorsToGradient(colorsArray, deg = 0) {
64068
- let gradientString = `linear-gradient(${deg}deg, `;
64069
- colorsArray.forEach((color, index) => {
64070
- gradientString += color;
64071
- if (index < colorsArray.length - 1) {
64072
- gradientString += ', ';
64073
- }
64074
- });
64075
- gradientString += ')';
64076
- return gradientString;
64077
- }
64078
64476
  applyGradient() {
64079
64477
  let cssGradient;
64080
- if (this.colorsArray.length === 0) {
64478
+ if (this.colorsPosArray === 0) {
64081
64479
  cssGradient = '';
64082
64480
  } else {
64083
- cssGradient = this.convertColorsToGradient(this.colorsArray, this.angle);
64481
+ cssGradient = this.constructGradientString(this.colorsPosArray);
64084
64482
  }
64483
+
64484
+ // apply to element
64085
64485
  this.element.style.backgroundImage = cssGradient;
64086
64486
  this.btn.style.backgroundImage = cssGradient;
64087
- const divPreview = this.pickGradient.querySelector('.gradient-preview');
64088
- divPreview.style.backgroundImage = cssGradient;
64089
64487
  }
64090
- areArraysIdentical(arr1, arr2) {
64091
- if (arr1.length !== arr2.length) {
64092
- return false; // Arrays of different lengths cannot be identical
64093
- }
64094
-
64095
- for (let i = 0; i < arr1.length; i++) {
64096
- if (arr1[i] !== arr2[i]) {
64097
- return false; // Found elements that are not the same
64098
- }
64099
- }
64100
-
64101
- return true; // All elements are the same and in the same order
64102
- }
64103
-
64104
64488
  renderColorStops() {
64105
- let colorsArray = this.colorsArray;
64106
- if (colorsArray.length === 0) {
64107
- colorsArray = ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'];
64489
+ if (this.colorsPosArray.length === 0) {
64490
+ this.colorsPosArray = [{
64491
+ color: 'rgba(255, 255, 255, 1)',
64492
+ position: 0
64493
+ }, {
64494
+ color: 'rgba(255, 255, 255, 1)',
64495
+ position: 100
64496
+ }];
64108
64497
  }
64109
64498
  let htmlStop = '';
64110
- let n = 0;
64111
- colorsArray.forEach(color => {
64499
+ this.colorsPosArray.forEach((item, index) => {
64112
64500
  htmlStop += `<div>
64113
- <button data-color="${color}"
64114
- data-index="${n}"
64501
+ <button data-color="${item.color}"
64502
+ data-index="${index}"
64115
64503
  title="${this.out('Select Color')}"
64116
64504
  class="btn-colorstop is-btn-color"
64117
- data-value="${color}"
64118
- style="background-color:${color}"></button>
64505
+ data-value="${item.color}"
64506
+ style="background-color:${item.color}"></button>
64119
64507
 
64120
64508
  <button title="${this.out('Remove')}" class="btn-remove">
64121
64509
  <svg class="is-icon-flex"><use xlink:href="#icon-minus"></use></svg>
64122
64510
  </button>
64123
64511
  </div>`;
64124
- n++;
64125
64512
  });
64126
64513
  const divSort = this.pickGradient.querySelector('.div-sort');
64127
64514
  divSort.innerHTML = htmlStop;
@@ -64130,25 +64517,35 @@ class GradientPicker {
64130
64517
  dragClass: 'hide-drag-class',
64131
64518
  onStart: () => {},
64132
64519
  onSort: () => {
64133
- this.updateColorsArray();
64520
+ // TODO
64521
+
64522
+ const btnPicks = this.pickGradient.querySelectorAll('.btn-colorstop');
64523
+ let colors = [];
64524
+ btnPicks.forEach(btn => {
64525
+ let color = btn.style.backgroundColor;
64526
+ colors.push(color);
64527
+ });
64528
+ this.colorsPosArray.map((item, index) => {
64529
+ item.color = colors[index];
64530
+ });
64531
+ this.gradSlider.draw(this.colorsPosArray);
64532
+ this.renderColorStops();
64134
64533
  this.applyGradient();
64534
+ if (this.onChange) this.onChange();
64535
+ this.builder.onChange();
64135
64536
  }
64136
64537
  });
64137
64538
  const colorPicker = this.builder.colorPicker;
64138
64539
  const btnPicks = this.pickGradient.querySelectorAll('.btn-colorstop');
64139
- btnPicks.forEach(btn => {
64540
+ btnPicks.forEach((btn, index) => {
64140
64541
  btn.addEventListener('click', () => {
64141
64542
  this.builder.uo.saveForUndo(true);
64142
64543
  colorPicker.open(color => {
64143
64544
  if (color === '') {
64144
64545
  const allow = this.allowRemoveStop();
64145
64546
  if (allow) {
64146
- // clear
64147
- btn.parentNode.remove();
64148
- this.updateColorsArray();
64149
- this.applyGradient();
64150
- if (this.onChange) this.onChange();
64151
- this.builder.onChange();
64547
+ // remove
64548
+ btn.parentNode.querySelector('.btn-remove').click();
64152
64549
  this.builder.util.hidePopOverlay(); // if pop is opened using overlay, to programmatically close, use this
64153
64550
  return;
64154
64551
  }
@@ -64157,14 +64554,7 @@ class GradientPicker {
64157
64554
 
64158
64555
  btn.setAttribute('data-color', color);
64159
64556
  btn.style.backgroundColor = color;
64160
- this.updateColorsArray();
64161
- if (this.areArraysIdentical(this.colorsArray, ['rgba(255, 255, 255, 1)', 'rgba(255, 255, 255, 1)'])) {
64162
- console.log('Initial, no gradient');
64163
- return;
64164
- }
64165
- this.applyGradient();
64166
- if (this.onChange) this.onChange();
64167
- this.builder.onChange();
64557
+ this.gradSlider.updateColor(color, index); // triggers gradSlider.on('change')
64168
64558
  }, btn.style.backgroundColor, () => {
64169
64559
  btn.removeAttribute('data-focus');
64170
64560
  btn.focus();
@@ -64174,28 +64564,25 @@ class GradientPicker {
64174
64564
  });
64175
64565
  });
64176
64566
  const btnRemove = divSort.querySelectorAll('.btn-remove');
64177
- btnRemove.forEach(btn => {
64567
+ btnRemove.forEach((btn, index) => {
64178
64568
  btn.addEventListener('click', e => {
64179
64569
  this.builder.uo.saveForUndo();
64180
64570
  const allow = this.allowRemoveStop();
64181
64571
  if (allow) {
64182
64572
  btn.parentNode.remove();
64183
- } else {
64184
- let color = 'rgba(255, 255, 255, 1)'; // gradient stop requires value
64573
+ this.colorsPosArray.splice(index, 1);
64574
+ this.renderColorStops(); // a must
64185
64575
 
64186
- const btnColor = btn.parentNode.querySelector('.btn-colorstop');
64187
- btnColor.setAttribute('data-color', color);
64188
- btnColor.style.backgroundColor = color;
64576
+ this.gradSlider.removeColor(index);
64189
64577
  }
64190
- this.updateColorsArray();
64191
- this.applyGradient();
64192
- if (this.onChange) this.onChange();
64193
- this.builder.onChange();
64194
64578
  e.preventDefault();
64195
64579
  e.stopImmediatePropagation();
64196
64580
  });
64197
64581
  });
64198
64582
  }
64583
+ renderColorPositions() {
64584
+ this.gradSlider.draw(this.colorsPosArray);
64585
+ }
64199
64586
  renderAngle() {
64200
64587
  const inpAngle = this.pickGradient.querySelector('.inp-angle');
64201
64588
  inpAngle.value = this.angle;
@@ -64302,16 +64689,117 @@ class GradientPicker {
64302
64689
  // this.onFinish = onFinish; // used in showPop below
64303
64690
 
64304
64691
  let cssGradient = elm.style.backgroundImage;
64305
- const colorsArray = this.extractColors(cssGradient);
64306
- this.colorsArray = colorsArray;
64307
- const angle = this.extractAngle(cssGradient);
64308
- this.angle = angle;
64309
- this.renderAngle();
64310
- this.renderColorStops();
64692
+ if (!cssGradient) {
64693
+ this.angle = 0;
64694
+ this.renderAngle();
64695
+ this.colorsPosArray = [{
64696
+ color: 'rgba(255, 255, 255, 1)',
64697
+ position: 0
64698
+ }, {
64699
+ color: 'rgba(255, 255, 255, 1)',
64700
+ position: 100
64701
+ }];
64702
+ this.renderColorPositions();
64703
+ this.renderColorStops();
64704
+ } else {
64705
+ const angle = this.extractAngle(cssGradient);
64706
+ this.angle = angle;
64707
+ this.renderAngle();
64708
+ const colorsPosArray = this.extractColorsPos(cssGradient);
64709
+ this.colorsPosArray = colorsPosArray;
64710
+ this.renderColorPositions();
64711
+ this.renderColorStops();
64712
+ }
64311
64713
  setTimeout(() => {
64312
64714
  this.slider.create();
64313
64715
  }, 0);
64314
64716
  }
64717
+ extractColorsPos(gradientString) {
64718
+ // Regular expression to match rgb(a) colors and hex colors (with optional alpha) and their positions
64719
+ const colorPositionRegex = /((rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*(,\s*\d*\.?\d+\s*)?\)|#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?))(?:\s+(\d+)%\s*)?/g;
64720
+ const matches = [...gradientString.matchAll(colorPositionRegex)];
64721
+ let colorsWithPositions = matches.map(match => ({
64722
+ color: match[1],
64723
+ // The color value
64724
+ position: match[5] ? parseInt(match[5], 10) : undefined // The position, if present
64725
+ }));
64726
+
64727
+ // Additional operation to correctly parse positions
64728
+ const positionMatches = [...gradientString.matchAll(/\d+\.?\d*%/g)].map(match => parseFloat(match[0]));
64729
+ positionMatches.forEach((position, index) => {
64730
+ if (colorsWithPositions[index]) {
64731
+ colorsWithPositions[index].position = position;
64732
+ }
64733
+ });
64734
+
64735
+ // For gradients without explicit positions, we'll distribute positions evenly
64736
+ if (colorsWithPositions.some(c => c.position === undefined)) {
64737
+ const step = 100 / (colorsWithPositions.length - 1);
64738
+ colorsWithPositions.forEach((c, i) => c.position = i * step);
64739
+ }
64740
+ return colorsWithPositions;
64741
+ }
64742
+ extractAngle(gradientString) {
64743
+ const anglePattern = /linear-gradient\((\d+)deg,/;
64744
+ const match = anglePattern.exec(gradientString);
64745
+ if (match && match[1]) {
64746
+ return parseInt(match[1], 10);
64747
+ } else {
64748
+ return 0;
64749
+ }
64750
+ }
64751
+
64752
+ // Utils
64753
+
64754
+ removeItemByIndex(arr, index) {
64755
+ if (index > -1 && index < arr.length) {
64756
+ arr.splice(index, 1);
64757
+ }
64758
+ return arr;
64759
+ }
64760
+ allowRemoveStop() {
64761
+ const divSort = this.pickGradient.querySelector('.div-sort');
64762
+ const btns = divSort.querySelectorAll('button[data-color]');
64763
+ if (btns.length === 2) {
64764
+ return false;
64765
+ } else {
64766
+ return true;
64767
+ }
64768
+ }
64769
+ convertColorsToGradient(colorsArray, deg = 0) {
64770
+ let gradientString = `linear-gradient(${deg}deg, `;
64771
+ colorsArray.forEach((color, index) => {
64772
+ gradientString += color;
64773
+ if (index < colorsArray.length - 1) {
64774
+ gradientString += ', ';
64775
+ }
64776
+ });
64777
+ gradientString += ')';
64778
+ return gradientString;
64779
+ }
64780
+ constructGradientString(points, angle) {
64781
+ const gradientParts = points.map(color => `${color.color} ${color.position}%`);
64782
+ if (angle) {
64783
+ return `linear-gradient(${angle}deg, ${gradientParts.join(', ')})`;
64784
+ } else {
64785
+ return `linear-gradient(${this.angle}deg, ${gradientParts.join(', ')})`;
64786
+ }
64787
+ }
64788
+ areArraysIdentical(arr1, arr2) {
64789
+ if (arr1.length !== arr2.length) {
64790
+ return false; // Arrays of different lengths cannot be identical
64791
+ }
64792
+
64793
+ for (let i = 0; i < arr1.length; i++) {
64794
+ if (arr1[i] !== arr2[i]) {
64795
+ return false; // Found elements that are not the same
64796
+ }
64797
+ }
64798
+
64799
+ return true; // All elements are the same and in the same order
64800
+ }
64801
+
64802
+ // Pop stuff
64315
64803
  getElementPosition(element) {
64316
64804
  const top = element.getBoundingClientRect().top;
64317
64805
  const left = element.getBoundingClientRect().left;
@@ -90971,7 +91459,7 @@ class ContentBuilder {
90971
91459
  /* If not empty, applying font size will apply class: size-12, size-14, and so on. All responsive, defined in content.css */
90972
91460
  gradientColors: [
90973
91461
  // 'linear-gradient(234deg, rgba(255, 253, 185, 1), rgb(255, 208, 100), rgb(239, 98, 159), rgb(73, 88, 195), rgba(2, 20, 145, 1))',
90974
- 'linear-gradient(234deg, rgba(255, 253, 193, 1), rgba(255, 211, 111, 1), rgba(246, 108, 168, 1), rgba(97, 110, 204, 1), rgba(4, 98, 183, 1))', 'linear-gradient(15deg, rgba(222, 90, 69, 1), rgba(255, 118, 96, 1), rgba(249, 214, 137, 1), rgba(255, 242, 172, 1))', 'linear-gradient(357deg, rgba(68, 85, 204, 1), rgba(4, 166, 240, 1), rgba(51, 241, 255, 1))', 'linear-gradient(26deg, rgba(41, 145, 255, 1), rgba(49, 103, 240, 1), rgba(67, 107, 208, 1), rgba(149, 115, 255, 1), rgba(159, 159, 255, 1))', 'linear-gradient(0deg, rgba(198, 233, 155, 1), rgba(97, 221, 180, 1), rgba(244, 255, 190, 1))', 'linear-gradient(342deg, rgb(189, 156, 219), rgb(163, 172, 240), rgba(154, 203, 253, 1))', 'linear-gradient(45deg, rgb(255, 225, 172), rgb(253, 194, 141), rgba(246, 150, 183, 1), rgba(221, 140, 207, 1))', 'linear-gradient(0deg, rgba(157, 172, 246, 1), rgba(129, 205, 255, 1), rgba(202, 255, 207, 1))'],
91462
+ 'linear-gradient(32deg, rgb(4, 98, 183) 0%, rgb(97, 110, 204) 23.5075%, rgb(246, 108, 168) 50%, rgb(255, 211, 111) 75%, rgb(255, 253, 193) 100%)', 'linear-gradient(15deg, rgba(222, 90, 69, 1), rgba(255, 118, 96, 1), rgba(249, 214, 137, 1), rgba(255, 242, 172, 1))', 'linear-gradient(357deg, rgba(68, 85, 204, 1), rgba(4, 166, 240, 1), rgba(51, 241, 255, 1))', 'linear-gradient(26deg, rgba(41, 145, 255, 1), rgba(49, 103, 240, 1), rgba(67, 107, 208, 1), rgba(149, 115, 255, 1), rgba(159, 159, 255, 1))', 'linear-gradient(0deg, rgba(198, 233, 155, 1), rgba(97, 221, 180, 1), rgba(244, 255, 190, 1))', 'linear-gradient(342deg, rgb(189, 156, 219), rgb(163, 172, 240), rgba(154, 203, 253, 1))', 'linear-gradient(45deg, rgb(255, 225, 172), rgb(253, 194, 141), rgba(246, 150, 183, 1), rgba(221, 140, 207, 1))', 'linear-gradient(0deg, rgba(157, 172, 246, 1), rgba(129, 205, 255, 1), rgba(202, 255, 207, 1))'],
90975
91463
  elementEditor: true,
90976
91464
  customval: '',
90977
91465
  moduleConfig: [],
@@ -92867,7 +93355,7 @@ Add an image for each feature.`, 'Create a new content showcasing a photo galler
92867
93355
  dropdown.style.display = 'none';
92868
93356
  });
92869
93357
  }
92870
- const clrPicker = document.querySelector('.pop-picker.active');
93358
+ const clrPicker = document.querySelector('.pop-picker.active') || document.querySelector('.pickgradientcolor.active');
92871
93359
  // if(clrPicker) return;
92872
93360
 
92873
93361
  // Image Resizer