PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 51.1.45 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / extensions / Site_Preloader / assets / admin.js

admin.js in King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder 51.1.78, at includes/extensions/Site_Preloader/assets/admin.js

1,091 lines 43.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Site Preloader Admin Script.
3 *
4 * Handles admin UI interactions, live preview, color pickers, media uploaders, and AJAX operations.
5 * Premium style inspired smooth interactions.
6 *
7 * @package King_Addons
8 * @since 1.0.0
9 */
10
11 (function ($) {
12 'use strict';
13
14 /**
15 * Admin Controller
16 */
17 const KngPreloaderAdmin = {
18
19 /**
20 * Cached elements
21 */
22 elements: {
23 form: null,
24 preview: null,
25 previewFrame: null,
26 previewContent: null,
27 colorPickers: null,
28 mediaButtons: null,
29 tabLinks: null,
30 rulesList: null
31 },
32
33 /**
34 * Current settings state
35 */
36 settings: {},
37
38 /**
39 * Initialize admin
40 */
41 init: function () {
42 this.cacheElements();
43 this.initColorPickers();
44 this.initMediaUploaders();
45 this.initTabs();
46 this.initPresetSelection();
47 this.initLivePreview();
48 this.initRulesManager();
49 this.initImportExport();
50 this.initDarkModeToggle();
51 this.initFormSubmit();
52 this.initPreviewModal();
53 },
54
55 /**
56 * Cache DOM elements
57 */
58 cacheElements: function () {
59 this.elements.form = $('#ka-preloader-settings-form');
60 if (!this.elements.form.length) {
61 this.elements.form = $('#kng-preloader-settings-form');
62 }
63 this.elements.preview = $('.ka-preloader-live-preview');
64 this.elements.previewFrame = $('.ka-preloader-preview-frame');
65 this.elements.previewContent = $('.ka-preloader-preview-content');
66 this.elements.colorPickers = $('.ka-preloader-color-input, .ka-color-picker, .ka-preloader-wp-color-picker');
67 this.elements.mediaButtons = $('.ka-preloader-media-button');
68 this.elements.tabLinks = $('.ka-preloader-tab-link');
69 this.elements.rulesList = $('.ka-preloader-rules-list');
70 },
71
72 /**
73 * Initialize color pickers
74 */
75 initColorPickers: function () {
76 // Legacy custom preview inputs (if any exist)
77 $('.ka-preloader-color-input').each(function () {
78 const $input = $(this);
79 const $preview = $input.siblings('.ka-preloader-color-preview');
80
81 $input.off('input.kngPreloader change.kngPreloader');
82 $input.on('input.kngPreloader change.kngPreloader', function () {
83 const color = $(this).val();
84 $preview.css('background-color', color);
85 KngPreloaderAdmin.updatePreview();
86 });
87
88 $preview.css('background-color', $input.val());
89 });
90
91 // Alpha-enabled WordPress color picker
92 if ($.fn.wpColorPicker) {
93 const $pickers = $('.ka-color-picker, .ka-preloader-wp-color-picker');
94 $pickers.each(function () {
95 const $input = $(this);
96 if ($input.hasClass('wp-color-picker')) {
97 return;
98 }
99
100 $input.wpColorPicker({
101 defaultColor: $input.data('default-color'),
102 change: function () {
103 KngPreloaderAdmin.updatePreview();
104 },
105 clear: function () {
106 KngPreloaderAdmin.updatePreview();
107 }
108 });
109 });
110 }
111 },
112
113 /**
114 * Initialize media uploaders
115 */
116 initMediaUploaders: function () {
117 this.elements.mediaButtons.on('click', function (e) {
118 e.preventDefault();
119
120 const $button = $(this);
121 const $container = $button.closest('.ka-preloader-media-field');
122 const $input = $container.find('input[type="hidden"]');
123 const $preview = $container.find('.ka-preloader-media-preview');
124 const $removeBtn = $container.find('.ka-preloader-media-remove');
125
126 // Create media frame
127 const frame = wp.media({
128 title: $button.data('title') || 'Select Image',
129 button: {
130 text: $button.data('button-text') || 'Use Image'
131 },
132 multiple: false
133 });
134
135 // Handle selection
136 frame.on('select', function () {
137 const attachment = frame.state().get('selection').first().toJSON();
138
139 $input.val(attachment.id);
140
141 let imgUrl = attachment.url;
142 if (attachment.sizes && attachment.sizes.thumbnail) {
143 imgUrl = attachment.sizes.thumbnail.url;
144 }
145
146 $preview.html('<img src="' + imgUrl + '" alt="" />').show();
147 $removeBtn.show();
148 $button.text('Change Image');
149
150 KngPreloaderAdmin.updatePreview();
151 });
152
153 frame.open();
154 });
155
156 // Handle remove button
157 $('.ka-preloader-media-remove').on('click', function (e) {
158 e.preventDefault();
159
160 const $button = $(this);
161 const $container = $button.closest('.ka-preloader-media-field');
162 const $input = $container.find('input[type="hidden"]');
163 const $preview = $container.find('.ka-preloader-media-preview');
164 const $uploadBtn = $container.find('.ka-preloader-media-button');
165
166 $input.val('');
167 $preview.empty().hide();
168 $button.hide();
169 $uploadBtn.text('Upload Image');
170
171 KngPreloaderAdmin.updatePreview();
172 });
173 },
174
175 /**
176 * Initialize tabs
177 */
178 initTabs: function () {
179 this.elements.tabLinks.on('click', function (e) {
180 e.preventDefault();
181
182 const $link = $(this);
183 const tab = $link.data('tab');
184
185 // Update URL without reload
186 const url = new URL(window.location);
187 url.searchParams.set('tab', tab);
188 window.history.replaceState({}, '', url);
189
190 // Update active states
191 KngPreloaderAdmin.elements.tabLinks.removeClass('active');
192 $link.addClass('active');
193
194 // Show target panel
195 $('.ka-preloader-tab-panel').removeClass('active');
196 $('#ka-preloader-tab-' + tab).addClass('active');
197 });
198 },
199
200 /**
201 * Initialize preset selection
202 */
203 initPresetSelection: function () {
204 $(document).on('click', '.ka-preloader-preset-card', function () {
205 const $card = $(this);
206 const presetId = $card.data('preset');
207
208 // Update active state
209 $('.ka-preloader-preset-card').removeClass('active');
210 $card.addClass('active');
211
212 // Update hidden input
213 $('input[name="template"], input[name="preset"]').val(presetId);
214
215 // Update preview
216 KngPreloaderAdmin.updatePreview();
217 });
218 },
219
220 /**
221 * Initialize live preview
222 */
223 initLivePreview: function () {
224 // Collect all form inputs
225 const $formInputs = this.elements.form.length ? this.elements.form.find('input, select, textarea') : $('input, select, textarea');
226
227 // Update preview on input change
228 $formInputs.on('input change', function () {
229 KngPreloaderAdmin.debounce(KngPreloaderAdmin.updatePreview, 150)();
230 });
231
232 // Initial preview render
233 this.updatePreview();
234 },
235
236 /**
237 * Update live preview
238 */
239 updatePreview: function () {
240 if (!this.elements.previewContent.length) {
241 return;
242 }
243
244 // Collect current settings
245 const settings = this.collectFormSettings();
246
247 const bgColor = settings.bg_color || settings.background_color || 'rgba(0,0,0,0)';
248 const presetId = settings.template || settings.preset || 'spinner-circle';
249 const sizePx = (settings.spinner_size || settings.animation_size || 48) + 'px';
250
251 // Update preview CSS variables
252 const $frame = this.elements.previewFrame;
253 $frame.css({
254 '--kng-preloader-accent': settings.accent_color || '#0071e3',
255 '--kng-preloader-bg': bgColor,
256 '--kng-preloader-text': settings.text_color || '#1d1d1f',
257 '--kng-preloader-size': sizePx
258 });
259
260 // Update background overlay
261 const $overlay = this.elements.previewContent.find('.ka-preloader-preview-overlay');
262 if ($overlay.length) {
263 $overlay.css('background-color', bgColor);
264 }
265
266 // Update preset animation
267 this.updatePresetAnimation(presetId);
268
269 // Update text
270 const $text = this.elements.previewContent.find('.ka-preloader-preview-text');
271 if (settings.text_content) {
272 $text.text(settings.text_content).show();
273 } else {
274 $text.hide();
275 }
276 },
277
278 /**
279 * Update preset animation in preview
280 */
281 updatePresetAnimation: function (presetId) {
282 const $animation = this.elements.previewContent.find('.ka-preloader-preview-animation');
283 if (!$animation.length) {
284 return;
285 }
286
287 // Generate preset HTML
288 const presetHtml = this.getPresetHtml(presetId);
289 $animation.html(presetHtml);
290 },
291
292 /**
293 * Get preset HTML
294 */
295 getPresetHtml: function (presetId) {
296 const presets = {
297 'spinner-circle': '<div class="kng-preloader-spinner-circle"></div>',
298 'dual-ring': '<div class="kng-preloader-dual-ring"></div>',
299 'dots-bounce': '<div class="kng-preloader-dots-bounce"><span></span><span></span><span></span></div>',
300 'bar-loader': '<div class="kng-preloader-bar-loader"><div class="kng-preloader-bar-loader__bar"></div></div>',
301 'pulse-logo': '<div class="kng-preloader-pulse-logo"><div class="kng-preloader-pulse-logo__circle"></div></div>',
302 'minimal-line': '<div class="kng-preloader-minimal-line"><div class="kng-preloader-minimal-line__track"><div class="kng-preloader-minimal-line__bar"></div></div></div>',
303 'gradient-spinner': '<div class="kng-preloader-gradient-spinner"></div>',
304 'fade-text': '<div class="kng-preloader-fade-text"><span style="animation-delay: 0s">L</span><span style="animation-delay: 0.1s">o</span><span style="animation-delay: 0.2s">a</span><span style="animation-delay: 0.3s">d</span><span style="animation-delay: 0.4s">i</span><span style="animation-delay: 0.5s">n</span><span style="animation-delay: 0.6s">g</span></div>',
305 'cube-grid': '<div class="kng-preloader-cube-grid">' + '<div class="kng-preloader-cube-grid__cube"></div>'.repeat(9) + '</div>',
306 'wave-bars': '<div class="kng-preloader-wave-bars">' + '<div class="kng-preloader-wave-bars__bar"></div>'.repeat(5) + '</div>',
307 'rotating-squares': '<div class="kng-preloader-rotating-squares"><div class="kng-preloader-rotating-squares__square"></div><div class="kng-preloader-rotating-squares__square"></div></div>',
308 'morphing-circle': '<div class="kng-preloader-morphing-circle"></div>'
309 };
310
311 return presets[presetId] || presets['spinner-circle'];
312 },
313
314 /**
315 * Collect form settings
316 */
317 collectFormSettings: function () {
318 const settings = {};
319
320 this.elements.form.find('input, select, textarea').each(function () {
321 const $field = $(this);
322 const name = $field.attr('name');
323
324 if (!name) return;
325
326 if ($field.is(':checkbox')) {
327 settings[name] = $field.is(':checked') ? '1' : '0';
328 } else if ($field.is(':radio')) {
329 if ($field.is(':checked')) {
330 settings[name] = $field.val();
331 }
332 } else {
333 settings[name] = $field.val();
334 }
335 });
336
337 return settings;
338 },
339
340 /**
341 * Initialize rules manager
342 */
343 initRulesManager: function () {
344 // Add rule button - support both class and ID
345 $(document).on('click', '#ka-add-rule-btn, .ka-preloader-add-rule', function (e) {
346 e.preventDefault();
347 KngPreloaderAdmin.addNewRule();
348 });
349
350 // Remove rule button (both classes) - use deleteRule with AJAX
351 $(document).on('click', '.ka-preloader-rule-remove, .ka-rule-delete-btn', function (e) {
352 e.preventDefault();
353 e.stopPropagation();
354
355 if (!confirm(kngPreloaderAdmin.strings.confirmDelete || 'Are you sure you want to delete this rule?')) {
356 return;
357 }
358
359 const $rule = $(this).closest('.ka-preloader-rule-item');
360 KngPreloaderAdmin.deleteRule($rule);
361 });
362
363 // Edit rule button - toggle details
364 $(document).on('click', '.ka-rule-edit-btn', function (e) {
365 e.preventDefault();
366 e.stopPropagation();
367 const $rule = $(this).closest('.ka-preloader-rule-item');
368 const $details = $rule.find('.ka-preloader-rule-item__details');
369 $details.slideToggle(200);
370 });
371
372 // Save rule button
373 $(document).on('click', '.ka-rule-save-btn', function (e) {
374 e.preventDefault();
375 const $rule = $(this).closest('.ka-preloader-rule-item');
376 KngPreloaderAdmin.saveRule($rule);
377 });
378
379 // Cancel rule button
380 $(document).on('click', '.ka-rule-cancel-btn', function (e) {
381 e.preventDefault();
382 const $rule = $(this).closest('.ka-preloader-rule-item');
383 KngPreloaderAdmin.cancelRuleEdit($rule);
384 });
385
386 // Action buttons (Show/Hide/Override) - both old and new class names
387 $(document).on('click', '.ka-rule-action-btn, .ka-rule-action-btns .ka-btn', function (e) {
388 e.preventDefault();
389 const $btn = $(this);
390 const $rule = $btn.closest('.ka-preloader-rule-item');
391 const action = $btn.data('action');
392
393 // Update active state
394 $btn.siblings().removeClass('active');
395 $btn.addClass('active');
396
397 // Store in hidden input if exists
398 $rule.find('input.ka-rule-action').val(action);
399
400 // Show/hide template row (only for override)
401 const $templateRow = $rule.find('.ka-rule-template-row');
402 const $colorsToggleRow = $rule.find('.ka-rule-colors-toggle-row');
403 const $colorsRow = $rule.find('.ka-rule-colors-row');
404
405 if (action === 'override') {
406 $templateRow.slideDown(200);
407 $colorsToggleRow.slideDown(200);
408 if ($rule.find('.ka-rule-override-colors').is(':checked')) {
409 $colorsRow.slideDown(200);
410 }
411 } else {
412 $templateRow.slideUp(200);
413 $colorsToggleRow.slideUp(200);
414 $colorsRow.slideUp(200);
415 }
416
417 // Update header badge
418 const actionLabels = { 'show': 'Show', 'hide': 'Hide', 'override': 'Override' };
419 const actionIcons = { 'show': 'visibility', 'hide': 'hidden', 'override': 'admin-customizer' };
420 $rule.find('.ka-preloader-rule-item__summary .ka-rule-action')
421 .removeClass('ka-rule-action--show ka-rule-action--hide ka-rule-action--override')
422 .addClass('ka-rule-action--' + action)
423 .html('<span class="dashicons dashicons-' + actionIcons[action] + '"></span> ' + actionLabels[action]);
424 });
425
426 // Toggle rule expansion (header click)
427 $(document).on('click', '.ka-preloader-rule-item__header', function (e) {
428 // Don't toggle if clicking buttons
429 if ($(e.target).closest('button, .ka-toggle, input').length) {
430 return;
431 }
432
433 const $rule = $(this).closest('.ka-preloader-rule-item');
434 const $details = $rule.find('.ka-preloader-rule-item__details');
435 $details.slideToggle(200);
436 });
437
438 // Toggle rule expansion (old class for JS-generated rules)
439 $(document).on('click', '.ka-preloader-rule-header', function (e) {
440 if ($(e.target).closest('button').length) {
441 return;
442 }
443
444 const $rule = $(this).closest('.ka-preloader-rule-item');
445 $rule.toggleClass('expanded');
446 });
447
448 // Rule condition change - show/hide appropriate fields
449 $(document).on('change', '.ka-rule-condition', function () {
450 const $rule = $(this).closest('.ka-preloader-rule-item');
451 const condition = $(this).val();
452
453 KngPreloaderAdmin.updateConditionFields($rule, condition);
454 });
455
456 // Also support old class names
457 $(document).on('change', '.ka-preloader-rule-condition, .ka-rule-condition-type', function () {
458 const $rule = $(this).closest('.ka-preloader-rule-item');
459 const condition = $(this).val();
460
461 KngPreloaderAdmin.updateRuleConditions($rule, condition);
462 });
463
464 // Override colors toggle
465 $(document).on('change', '.ka-rule-override-colors', function () {
466 const $rule = $(this).closest('.ka-preloader-rule-item');
467 const $colorRows = $rule.find('.ka-rule-colors-row, .ka-rule-colors-grid');
468
469 if ($(this).is(':checked')) {
470 $colorRows.slideDown(200);
471 } else {
472 $colorRows.slideUp(200);
473 }
474 });
475
476 // Rule type change
477 $(document).on('change', '.ka-preloader-rule-type', function () {
478 const $rule = $(this).closest('.ka-preloader-rule-item');
479 const type = $(this).val();
480
481 KngPreloaderAdmin.updateRuleConditions($rule, type);
482 });
483
484 // Initialize condition fields on page load
485 $('.ka-preloader-rule-item').each(function() {
486 const $rule = $(this);
487 const condition = $rule.find('.ka-rule-condition').val();
488 if (condition) {
489 KngPreloaderAdmin.updateConditionFields($rule, condition);
490 }
491 });
492 },
493
494 /**
495 * Update condition fields visibility based on selected condition
496 */
497 updateConditionFields: function ($rule, condition) {
498 const $pagesRow = $rule.find('.ka-rule-pages-row');
499 const $posttypeRow = $rule.find('.ka-rule-posttype-row');
500 const $urlRow = $rule.find('.ka-rule-url-row');
501
502 // Hide all conditional rows first
503 $pagesRow.hide();
504 $posttypeRow.hide();
505 $urlRow.hide();
506
507 // Show appropriate row based on condition
508 switch (condition) {
509 case 'specific_pages':
510 $pagesRow.slideDown(200);
511 break;
512 case 'post_type':
513 $posttypeRow.slideDown(200);
514 break;
515 case 'url_contains':
516 case 'url_equals':
517 $urlRow.slideDown(200);
518 break;
519 // Other conditions don't need additional fields
520 }
521 },
522
523 /**
524 * Save rule via AJAX
525 */
526 saveRule: function ($rule) {
527 const $saveBtn = $rule.find('.ka-rule-save-btn');
528 const originalText = $saveBtn.text();
529
530 // Get action from the active button or hidden input
531 let action = 'show';
532 const $activeAction = $rule.find('.ka-rule-action-btn.active, .ka-rule-action-btns .ka-btn.active');
533 if ($activeAction.length) {
534 action = $activeAction.data('action');
535 } else {
536 const $actionInput = $rule.find('input.ka-rule-action');
537 if ($actionInput.length && $actionInput.val()) {
538 action = $actionInput.val();
539 }
540 }
541
542 // Get condition
543 const condition = $rule.find('.ka-rule-condition').val() || 'specific_pages';
544
545 // Get pages as array
546 const pages = [];
547 $rule.find('.ka-rule-pages option:selected').each(function() {
548 pages.push($(this).val());
549 });
550
551 // Collect rule data with new structure
552 const ruleData = {
553 action: 'king_addons_preloader_save_rule',
554 nonce: kngPreloaderAdmin.nonce,
555 rule_id: $rule.attr('data-rule-id') || '',
556 enabled: $rule.find('.ka-rule-enabled').is(':checked') ? '1' : '0',
557 priority: $rule.find('.ka-rule-priority').val() || 10,
558 rule_action: action, // show / hide / override
559 condition: condition,
560 condition_value: $rule.find('.ka-rule-condition-value, .ka-rule-url-value, .ka-rule-posttype').val() || '',
561 pages: pages,
562 template: $rule.find('.ka-rule-template').val() || '',
563 override_colors: $rule.find('.ka-rule-override-colors').is(':checked') ? '1' : '0',
564 bg_color: $rule.find('.ka-rule-bg-color').val() || '#ffffff',
565 accent_color: $rule.find('.ka-rule-accent-color').val() || '#0071e3'
566 };
567
568 // Show saving state
569 $saveBtn.text('Saving...').prop('disabled', true);
570
571 // console.log('Saving rule:', ruleData);
572
573 // Send AJAX request
574 $.ajax({
575 url: kngPreloaderAdmin.ajaxUrl,
576 type: 'POST',
577 data: ruleData,
578 success: function (response) {
579 // console.log('Save response:', response);
580 if (response.success) {
581 // Update rule ID if it was a new rule
582 if (response.data.rule && response.data.rule.id) {
583 $rule.attr('data-rule-id', response.data.rule.id);
584 }
585
586 // Update header display
587 const actionLabels = { 'show': 'Show', 'hide': 'Hide', 'override': 'Override' };
588 const conditionLabels = {
589 'specific_pages': 'Specific Pages',
590 'all_pages': 'All Pages',
591 'front_page': 'Front Page',
592 'blog_page': 'Blog Page',
593 'all_posts': 'All Posts',
594 'post_type': 'Post Type',
595 'archive': 'Archive',
596 'search': 'Search',
597 '404': '404 Page',
598 'url_contains': 'URL Contains',
599 'url_equals': 'URL Equals'
600 };
601
602 $rule.find('.ka-preloader-rule-item__action').text(actionLabels[action] || action);
603 $rule.find('.ka-preloader-rule-item__type').text(conditionLabels[condition] || condition);
604
605 // Show pages count for specific_pages
606 if (condition === 'specific_pages' && pages.length > 0) {
607 $rule.find('.ka-preloader-rule-item__value').text(pages.length + ' page(s)');
608 } else {
609 $rule.find('.ka-preloader-rule-item__value').text(ruleData.condition_value || '');
610 }
611
612 // Hide details
613 $rule.find('.ka-preloader-rule-item__details').slideUp(200);
614
615 // Show success
616 $saveBtn.text('Saved!');
617 setTimeout(() => {
618 $saveBtn.text(originalText).prop('disabled', false);
619 }, 1500);
620 } else {
621 alert(response.data?.message || kngPreloaderAdmin.strings.error);
622 $saveBtn.text(originalText).prop('disabled', false);
623 }
624 },
625 error: function () {
626 alert(kngPreloaderAdmin.strings.error);
627 $saveBtn.text(originalText).prop('disabled', false);
628 }
629 });
630 },
631
632 /**
633 * Delete rule via AJAX
634 */
635 deleteRule: function ($rule) {
636 const ruleId = $rule.attr('data-rule-id');
637
638 // If it's a new unsaved rule, just remove from DOM
639 if (!ruleId || ruleId.startsWith('new_')) {
640 $rule.slideUp(200, function () {
641 $(this).remove();
642 KngPreloaderAdmin.checkEmptyRules();
643 });
644 return;
645 }
646
647 // Send AJAX request
648 $.ajax({
649 url: kngPreloaderAdmin.ajaxUrl,
650 type: 'POST',
651 data: {
652 action: 'king_addons_preloader_delete_rule',
653 nonce: kngPreloaderAdmin.nonce,
654 rule_id: ruleId
655 },
656 success: function (response) {
657 if (response.success) {
658 $rule.slideUp(200, function () {
659 $(this).remove();
660 KngPreloaderAdmin.checkEmptyRules();
661 KngPreloaderAdmin.updateRuleIndices();
662 });
663 } else {
664 alert(response.data?.message || kngPreloaderAdmin.strings.error);
665 }
666 },
667 error: function () {
668 alert(kngPreloaderAdmin.strings.error);
669 }
670 });
671 },
672
673 /**
674 * Cancel rule edit
675 */
676 cancelRuleEdit: function ($rule) {
677 const ruleId = $rule.attr('data-rule-id');
678
679 // If it's a new unsaved rule, remove it
680 if (ruleId && ruleId.startsWith('new_')) {
681 $rule.slideUp(200, function () {
682 $(this).remove();
683 KngPreloaderAdmin.checkEmptyRules();
684 });
685 } else {
686 // Just hide details for existing rules
687 $rule.find('.ka-preloader-rule-item__details').slideUp(200);
688 }
689 },
690
691 /**
692 * Check if rules list is empty and show empty state
693 */
694 checkEmptyRules: function () {
695 const $rulesList = $('#ka-rules-list');
696 const rulesCount = $rulesList.find('.ka-preloader-rule-item').length;
697
698 if (rulesCount === 0) {
699 $('#ka-rules-empty').show();
700 }
701 },
702
703 /**
704 * Add new rule
705 */
706 addNewRule: function () {
707 // Get rules list, try ID first then class
708 let $rulesList = $('#ka-rules-list');
709 if (!$rulesList.length) {
710 $rulesList = this.elements.rulesList;
711 }
712
713 if (!$rulesList.length) {
714 // console.error('Rules list container not found');
715 return;
716 }
717
718 // Hide empty state
719 $('#ka-rules-empty').hide();
720
721 // Try to use template from PHP first
722 const $template = $('#ka-rule-template');
723 let $newRule;
724
725 if ($template.length) {
726 // Clone template content
727 $newRule = $($template.html()).clone();
728 $newRule.attr('data-rule-id', 'new_' + Date.now());
729 $newRule.find('.ka-preloader-rule-item__details').show();
730 } else {
731 // Fallback to JS-generated HTML
732 const ruleIndex = $rulesList.find('.ka-preloader-rule-item').length;
733 $newRule = $(`
734 <div class="ka-preloader-rule-item expanded" data-index="${ruleIndex}">
735 <div class="ka-preloader-rule-header">
736 <span class="ka-preloader-rule-title">New Rule</span>
737 <div class="ka-preloader-rule-actions">
738 <button type="button" class="ka-preloader-rule-remove">
739 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
740 <path d="M18 6L6 18M6 6l12 12"/>
741 </svg>
742 </button>
743 </div>
744 </div>
745 <div class="ka-preloader-rule-content">
746 <div class="ka-preloader-rule-field">
747 <label>Rule Name</label>
748 <input type="text" name="rules[${ruleIndex}][name]" value="New Rule" class="ka-preloader-rule-name">
749 </div>
750 <div class="ka-preloader-rule-field">
751 <label>Rule Type</label>
752 <select name="rules[${ruleIndex}][type]" class="ka-preloader-rule-type">
753 <option value="include">Show On (Include)</option>
754 <option value="exclude">Hide On (Exclude)</option>
755 </select>
756 </div>
757 <div class="ka-preloader-rule-field">
758 <label>Condition</label>
759 <select name="rules[${ruleIndex}][condition]" class="ka-preloader-rule-condition">
760 <option value="entire_site">Entire Site</option>
761 <option value="front_page">Front Page</option>
762 <option value="blog_page">Blog Page</option>
763 <option value="posts">All Posts</option>
764 <option value="pages">All Pages</option>
765 <option value="archives">Archives</option>
766 <option value="search">Search Results</option>
767 <option value="404">404 Page</option>
768 <option value="specific_page">Specific Page</option>
769 <option value="specific_post">Specific Post</option>
770 </select>
771 </div>
772 <div class="ka-preloader-rule-field ka-preloader-rule-value-field" style="display: none;">
773 <label>Value</label>
774 <input type="text" name="rules[${ruleIndex}][value]" class="ka-preloader-rule-value" placeholder="Enter ID or slug">
775 </div>
776 <div class="ka-preloader-rule-field">
777 <label>
778 <input type="checkbox" name="rules[${ruleIndex}][enabled]" value="1" checked>
779 Enable this rule
780 </label>
781 </div>
782 </div>
783 </div>
784 `);
785 }
786
787 $rulesList.append($newRule);
788
789 // Initialize WP color picker on newly added rule (alpha-enabled)
790 if ($newRule && $newRule.length) {
791 const $newPickers = $newRule.find('.ka-color-picker, .ka-preloader-wp-color-picker');
792 if ($.fn.wpColorPicker && $newPickers.length) {
793 $newPickers.each(function () {
794 const $input = $(this);
795 if ($input.hasClass('wp-color-picker')) {
796 return;
797 }
798
799 $input.wpColorPicker({
800 defaultColor: $input.data('default-color'),
801 change: function () {
802 KngPreloaderAdmin.updatePreview();
803 },
804 clear: function () {
805 KngPreloaderAdmin.updatePreview();
806 }
807 });
808 });
809 }
810 }
811
812 // Scroll to new rule
813 if ($newRule && $newRule[0]) {
814 $newRule[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
815 }
816 },
817
818 /**
819 * Update rule conditions based on type
820 */
821 updateRuleConditions: function ($rule, condition) {
822 // Find value field (support both class naming conventions)
823 const $valueField = $rule.find('.ka-preloader-rule-value-field');
824 const $conditionValueRow = $rule.find('.ka-row').filter(function() {
825 return $(this).find('.ka-rule-condition-value').length > 0;
826 });
827
828 // Conditions that need a value input
829 const needsValue = ['url_contains', 'url_equals', 'url_regex', 'query_param', 'specific_page', 'specific_post', 'specific_cpt', 'post_type', 'page_template'];
830
831 if (needsValue.includes(condition)) {
832 $valueField.slideDown(200);
833 $conditionValueRow.slideDown(200);
834 } else {
835 $valueField.slideUp(200);
836 $conditionValueRow.slideUp(200);
837 }
838 },
839
840 /**
841 * Update rule indices after deletion
842 */
843 updateRuleIndices: function () {
844 this.elements.rulesList.find('.ka-preloader-rule-item').each(function (index) {
845 const $rule = $(this);
846 $rule.attr('data-index', index);
847
848 // Update all input names
849 $rule.find('input, select').each(function () {
850 const $field = $(this);
851 const name = $field.attr('name');
852 if (name) {
853 $field.attr('name', name.replace(/\[\d+\]/, '[' + index + ']'));
854 }
855 });
856 });
857 },
858
859 /**
860 * Initialize import/export
861 */
862 initImportExport: function () {
863 // Export button
864 $(document).on('click', '.ka-preloader-export-btn', function (e) {
865 e.preventDefault();
866 KngPreloaderAdmin.exportSettings();
867 });
868
869 // Import button
870 $(document).on('click', '.ka-preloader-import-btn', function (e) {
871 e.preventDefault();
872 KngPreloaderAdmin.importSettings();
873 });
874
875 // Copy to clipboard
876 $(document).on('click', '.ka-preloader-copy-btn', function (e) {
877 e.preventDefault();
878 const $textarea = $(this).siblings('textarea');
879 $textarea.select();
880 document.execCommand('copy');
881
882 const $btn = $(this);
883 const originalText = $btn.text();
884 $btn.text('Copied!');
885 setTimeout(() => $btn.text(originalText), 2000);
886 });
887 },
888
889 /**
890 * Export settings
891 */
892 exportSettings: function () {
893 const settings = this.collectFormSettings();
894 const exportData = JSON.stringify(settings, null, 2);
895
896 const $exportArea = $('.ka-preloader-export-area');
897 $exportArea.val(exportData).show();
898 },
899
900 /**
901 * Import settings
902 */
903 importSettings: function () {
904 const $importArea = $('.ka-preloader-import-area');
905 const importData = $importArea.val().trim();
906
907 if (!importData) {
908 alert('Please paste settings JSON to import.');
909 return;
910 }
911
912 try {
913 const settings = JSON.parse(importData);
914 this.applyImportedSettings(settings);
915 alert('Settings imported successfully! Please save to apply.');
916 } catch (e) {
917 alert('Invalid JSON format. Please check your import data.');
918 }
919 },
920
921 /**
922 * Apply imported settings to form
923 */
924 applyImportedSettings: function (settings) {
925 for (const [key, value] of Object.entries(settings)) {
926 const $field = this.elements.form.find('[name="' + key + '"]');
927
928 if (!$field.length) continue;
929
930 if ($field.is(':checkbox')) {
931 $field.prop('checked', value === '1');
932 } else if ($field.is(':radio')) {
933 $field.filter('[value="' + value + '"]').prop('checked', true);
934 } else if ($field.is('select')) {
935 $field.val(value);
936 } else {
937 $field.val(value);
938 }
939 }
940
941 // Update preview
942 this.updatePreview();
943
944 // Update preset card selection
945 const preset = settings.template || settings.preset;
946 if (preset) {
947 $('.ka-preloader-preset-card').removeClass('active');
948 $('.ka-preloader-preset-card[data-preset="' + preset + '"]').addClass('active');
949 }
950 },
951
952 /**
953 * Initialize dark mode toggle
954 */
955 initDarkModeToggle: function () {
956 $(document).on('change', '.ka-preloader-dark-toggle input', function () {
957 const isDark = $(this).is(':checked');
958
959 if (isDark) {
960 $('.ka-preloader-admin-page').addClass('dark-mode');
961 localStorage.setItem('kng_preloader_dark_mode', '1');
962 } else {
963 $('.ka-preloader-admin-page').removeClass('dark-mode');
964 localStorage.setItem('kng_preloader_dark_mode', '0');
965 }
966 });
967
968 // Restore preference
969 const savedDarkMode = localStorage.getItem('kng_preloader_dark_mode');
970 if (savedDarkMode === '1') {
971 $('.ka-preloader-dark-toggle input').prop('checked', true).trigger('change');
972 }
973 },
974
975 /**
976 * Initialize form submit
977 */
978 initFormSubmit: function () {
979 this.elements.form.on('submit', function (e) {
980 // Let the form submit naturally, just show a loading state
981 const $submitBtn = $(this).find('button[type="submit"]');
982 $submitBtn.prop('disabled', true).text('Saving...');
983 });
984 },
985
986 /**
987 * Initialize preview modal
988 */
989 initPreviewModal: function () {
990 $(document).on('click', '.ka-preloader-full-preview-btn', function (e) {
991 e.preventDefault();
992 KngPreloaderAdmin.openPreviewModal();
993 });
994
995 // Close on Escape key
996 $(document).on('keydown', function (e) {
997 if (e.key === 'Escape') {
998 KngPreloaderAdmin.closePreviewModal();
999 }
1000 });
1001 },
1002
1003 /**
1004 * Open preview modal
1005 */
1006 openPreviewModal: function () {
1007 // Prefer template-provided modal implementation when available.
1008 if (typeof window.kaPreloaderOpenPreview === 'function') {
1009 window.kaPreloaderOpenPreview();
1010 return;
1011 }
1012
1013 const settings = this.collectFormSettings();
1014 const $modal = $('.ka-preloader-preview-modal');
1015
1016 if (!$modal.length) {
1017 return;
1018 }
1019
1020 // Build preview HTML
1021 const bgColor = settings.bg_color || settings.background_color || 'rgba(0,0,0,0)';
1022 const presetId = settings.template || settings.preset || 'spinner-circle';
1023 const sizePx = (settings.spinner_size || settings.animation_size || 48) + 'px';
1024
1025 const presetHtml = this.getPresetHtml(presetId);
1026 const previewHtml = `
1027 <div class="kng-site-preloader" style="
1028 --kng-preloader-accent: ${settings.accent_color || '#0071e3'};
1029 --kng-preloader-bg: ${bgColor};
1030 --kng-preloader-text: ${settings.text_color || '#1d1d1f'};
1031 --kng-preloader-size: ${sizePx};
1032 ">
1033 <div class="kng-site-preloader__overlay" style="background: ${bgColor}"></div>
1034 <div class="kng-site-preloader__content">
1035 <div class="kng-site-preloader__animation">${presetHtml}</div>
1036 ${settings.text_content ? '<div class="kng-site-preloader__text">' + settings.text_content + '</div>' : ''}
1037 </div>
1038 </div>
1039 `;
1040
1041 const $container = $('#ka-preloader-preview-container');
1042 if ($container.length) {
1043 $container.html(previewHtml);
1044 $modal.css('display', 'flex');
1045 $('body').css('overflow', 'hidden');
1046 }
1047 },
1048
1049 /**
1050 * Close preview modal
1051 */
1052 closePreviewModal: function () {
1053 // Prefer template-provided modal implementation when available.
1054 if (typeof window.kaPreloaderClosePreview === 'function') {
1055 window.kaPreloaderClosePreview();
1056 return;
1057 }
1058
1059 const $modal = $('.ka-preloader-preview-modal');
1060 if ($modal.length) {
1061 $modal.css('display', 'none').removeClass('active');
1062 }
1063 $('body').css('overflow', '').removeClass('ka-preloader-modal-open');
1064 },
1065
1066 /**
1067 * Debounce helper
1068 */
1069 debounce: function (func, wait) {
1070 let timeout;
1071 return function executedFunction(...args) {
1072 const later = () => {
1073 clearTimeout(timeout);
1074 func.apply(KngPreloaderAdmin, args);
1075 };
1076 clearTimeout(timeout);
1077 timeout = setTimeout(later, wait);
1078 };
1079 }
1080 };
1081
1082 // Initialize on DOM ready
1083 $(document).ready(function () {
1084 KngPreloaderAdmin.init();
1085 });
1086
1087 // Expose globally for debugging
1088 window.KngPreloaderAdmin = KngPreloaderAdmin;
1089
1090 })(jQuery);
1091