PluginProbe
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets / 4.1.15
Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets v4.1.15
4.5.4 4.2.1 4.2.2 4.2.3 4.5.0 4.5.2 4.5.3 4.2.0 4.1.18 4.1.17 4.1.16 4.1.15 4.1.14 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.0.9 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 All 146 releases
ultimate-post-kit / includes / setup-wizard / assets / js / setup-wizard.js

setup-wizard.js in Ultimate Post Kit – Elementor Post Grid, Post Carousel, Post Slider & Blog Layout Widgets 4.1.15, at includes/setup-wizard/assets/js/setup-wizard.js

817 lines 35.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (function ($) {
2 if (!$('.bdt-setup-wizard').length) {
3 return;
4 }
5
6 $(document).ready(function () {
7 const wizard = {
8 currentStep: 0,
9 steps: document.querySelectorAll('.bdt-wizard-step'),
10 nextButtons: document.querySelectorAll('.bdt-wizard-next'),
11 prevButtons: document.querySelectorAll('.bdt-wizard-prev'),
12 progressItems: document.querySelectorAll('.bdt-wizard-progress-item'),
13 progressBar: document.getElementById('plugin-install-progress'),
14 categorySelect: document.getElementById('category-select'),
15 searchInput: document.querySelector('.widget-search'),
16 activateAllButton: document.querySelector('.bulk-action.activate'),
17 deactivateAllButton: document.querySelector('.bulk-action.deactivate'),
18 saveButton: document.getElementById('save-and-continue'),
19 widgetList: document.querySelector('.widget-list'),
20 installButton: document.getElementById('upk-install-plugins-btn'),
21 filterButtons: document.querySelectorAll('.filter-button'),
22
23 init: function () {
24 this.setupStepAttributes();
25 this.showStep(this.currentStep);
26 this.updateProgress(this.currentStep);
27 this.bindEvents();
28 this.initAnimations();
29 },
30
31 setupStepAttributes: function() {
32 const stepNames = ['welcome', 'features', 'integration', 'finish'];
33 this.steps.forEach((step, index) => {
34 if (!step.hasAttribute('data-step') && index < stepNames.length) {
35 step.setAttribute('data-step', stepNames[index]);
36 }
37 });
38 },
39
40 initAnimations: function() {
41 const currentStep = this.steps[this.currentStep];
42 if (currentStep) {
43 setTimeout(() => {
44 currentStep.classList.add('active');
45 currentStep.style.display = 'block'; // Ensure display is set to block
46 }, 100);
47 }
48 },
49
50 bindEvents: function () {
51 const self = this;
52
53 this.progressItems.forEach((item, index) => {
54 item.addEventListener('click', () => {
55 const stepName = item.getAttribute('data-step');
56 if (index <= this.getCompletedStepIndex()) {
57 this.goToStep(index);
58 }
59 });
60 });
61
62 $(document).on('click', '.bdt-wizard-next', function(e) {
63 e.preventDefault();
64 const targetStep = $(this).data('step');
65
66 // No loader or timeout - immediate transition
67 if (targetStep) {
68 for (let i = 0; i < self.steps.length; i++) {
69 if ($(self.steps[i]).data('step') === targetStep) {
70 self.goToStep(i);
71 return;
72 }
73 }
74
75 const stepIndex = self.getStepIndexByName(targetStep);
76 if (stepIndex !== -1) {
77 self.goToStep(stepIndex);
78 return;
79 }
80 }
81
82 self.goToStep(self.currentStep + 1);
83 });
84
85 this.prevButtons.forEach(button => {
86 button.addEventListener('click', () => {
87 // No loader or timeout - immediate transition
88 const targetStep = button.getAttribute('data-step');
89 if (targetStep) {
90 const stepIndex = this.getStepIndexByName(targetStep);
91 if (stepIndex !== -1) {
92 this.goToStep(stepIndex);
93 }
94 } else if (this.currentStep > 0) {
95 this.goToStep(this.currentStep - 1);
96 }
97 });
98 });
99
100 if (this.filterButtons) {
101 this.filterButtons.forEach(button => {
102 button.addEventListener('click', () => {
103 this.filterButtons.forEach(btn => btn.classList.remove('active'));
104 button.classList.add('active');
105 this.filterItemsByType(button.dataset.filter);
106 });
107 });
108 }
109
110 if (this.categorySelect) {
111 this.categorySelect.addEventListener('change', this.filterWidgets.bind(this));
112 }
113
114 if (this.searchInput) {
115 this.searchInput.addEventListener('input', this.searchWidgets.bind(this));
116 }
117
118 if (this.activateAllButton) {
119 this.activateAllButton.addEventListener('click', this.activateAllWidgets.bind(this));
120 }
121 if (this.deactivateAllButton) {
122 this.deactivateAllButton.addEventListener('click', this.deactivateAllWidgets.bind(this));
123 }
124
125 this.saveSettingsSubmit();
126 this.installPlugins();
127 this.onChangedPluginSliderCheckbox();
128
129 const featureItems = document.querySelectorAll('.bdt-feature-item');
130 featureItems.forEach(item => {
131 item.addEventListener('mouseenter', () => {
132 item.style.transform = 'translateY(-8px)';
133 });
134 item.addEventListener('mouseleave', () => {
135 item.style.transform = 'translateY(-3px)';
136 });
137 });
138 },
139
140 getStepIndexByName: function(stepName) {
141 for (let i = 0; i < this.steps.length; i++) {
142 if (this.steps[i].getAttribute('data-step') === stepName) {
143 return i;
144 }
145 }
146
147 const stepMap = {
148 'welcome': 0,
149 'features': 1,
150 'integration': 2,
151 'finish': 3
152 };
153
154 return stepName in stepMap ? stepMap[stepName] : -1;
155 },
156
157 getCompletedStepIndex: function() {
158 return this.currentStep;
159 },
160
161 goToStep: function(stepIndex) {
162 if (stepIndex >= 0 && stepIndex < this.steps.length) {
163 this.currentStep = stepIndex;
164 this.showStep(this.currentStep);
165 this.updateProgress(this.currentStep);
166 }
167 },
168
169 pluginSliderCheckbox: function(selector){
170 const pluginSlugs = [];
171 const data = $(selector).serialize();
172 data.split('&').forEach(item => {
173 const [key, value] = item.split('=');
174 if (key.startsWith('plugins') && value === 'on') {
175 const slug = decodeURIComponent(key.split('%5B%5D')[1]);
176 if (slug) {
177 pluginSlugs.push(slug);
178 }
179 }
180 });
181
182 if(pluginSlugs.length){
183 $("#upk-install-plugins-btn").removeClass('d-none').addClass('pulse-animation');
184 }else{
185 $("#upk-install-plugins-btn").addClass('d-none').removeClass('pulse-animation');
186 }
187 },
188
189 onChangedPluginSliderCheckbox: function(){
190 const vm = this;
191 $('#upk-install-plugins').on('change', '.plugin-slider-checkbox', function (e) {
192 vm.pluginSliderCheckbox('#upk-install-plugins .plugin-slider-checkbox');
193
194 const pluginItem = $(this).closest('.plugin-item');
195 pluginItem.addClass('item-highlight');
196 setTimeout(() => {
197 pluginItem.removeClass('item-highlight');
198 }, 600);
199 });
200 },
201
202 showStep: function (step) {
203 // Hide all steps first
204 this.steps.forEach((stepElement) => {
205 stepElement.classList.remove('active');
206 stepElement.style.display = 'none'; // Ensure inactive steps are completely hidden
207 });
208
209 // Immediately show the current step without delay
210 const currentStep = this.steps[step];
211 if (currentStep) {
212 currentStep.classList.add('active');
213 currentStep.style.display = 'block'; // Make sure active step is visible
214 }
215
216 this.pluginSliderCheckbox('#upk-install-plugins .plugin-slider-checkbox');
217 },
218
219 filterItemsByType: function(type) {
220 const items = document.querySelectorAll('.feature-item, .plugin-item');
221
222 items.forEach(item => {
223 if (type === 'all' || item.dataset.type === type) {
224 item.style.display = 'flex';
225 setTimeout(() => {
226 item.style.opacity = '1';
227 item.style.transform = 'translateY(0)';
228 }, 50);
229 } else {
230 item.style.opacity = '0';
231 item.style.transform = 'translateY(10px)';
232 setTimeout(() => {
233 item.style.display = 'none';
234 }, 300);
235 }
236 });
237 },
238
239 filterWidgets: function () {
240 const selectedCategory = this.categorySelect.value;
241 const widgets = this.widgetList.querySelectorAll('li');
242
243 widgets.forEach(widget => {
244 const types = widget.dataset.type.split(/\s+/);
245 if (selectedCategory === 'all' || types.includes(selectedCategory)) {
246 widget.style.display = 'block';
247 setTimeout(() => {
248 widget.style.opacity = '1';
249 widget.style.transform = 'translateY(0)';
250 }, 50);
251 } else {
252 widget.style.opacity = '0';
253 widget.style.transform = 'translateY(10px)';
254 setTimeout(() => {
255 widget.style.display = 'none';
256 }, 300);
257 }
258 });
259 },
260
261 updateProgress: function (step) {
262 this.progressItems.forEach((item, index) => {
263 if (index < step) {
264 item.classList.remove('active');
265 item.classList.add('completed');
266 } else if (index === step) {
267 item.classList.add('active');
268 item.classList.remove('completed');
269 } else {
270 item.classList.remove('active', 'completed');
271 }
272 });
273 },
274
275 searchWidgets: function () {
276 const searchTerm = this.searchInput.value.toLowerCase();
277 const widgets = this.widgetList.querySelectorAll('li');
278
279 widgets.forEach(widget => {
280 if (widget.dataset.label.toLowerCase().includes(searchTerm)) {
281 widget.style.display = 'block';
282 setTimeout(() => {
283 widget.style.opacity = '1';
284 }, 50);
285 } else {
286 widget.style.opacity = '0';
287 setTimeout(() => {
288 widget.style.display = 'none';
289 }, 300);
290 }
291 });
292 },
293
294 activateAllWidgets: function (event) {
295 event.preventDefault();
296 const checkboxes = this.widgetList.querySelectorAll('input[type="checkbox"]');
297
298 this.activateAllButton.classList.add('button-pulse');
299 setTimeout(() => {
300 this.activateAllButton.classList.remove('button-pulse');
301 }, 500);
302
303 checkboxes.forEach(checkbox => {
304 checkbox.checked = true;
305 const widgetItem = checkbox.closest('li');
306 widgetItem.classList.add('item-highlight');
307 setTimeout(() => {
308 widgetItem.classList.remove('item-highlight');
309 }, 600);
310 });
311 },
312
313 deactivateAllWidgets: function (event) {
314 event.preventDefault();
315 const checkboxes = this.widgetList.querySelectorAll('input[type="checkbox"]');
316
317 this.deactivateAllButton.classList.add('button-pulse');
318 setTimeout(() => {
319 this.deactivateAllButton.classList.remove('button-pulse');
320 }, 500);
321
322 checkboxes.forEach(checkbox => {
323 checkbox.checked = false;
324 const widgetItem = checkbox.closest('li');
325 widgetItem.classList.add('item-highlight');
326 setTimeout(() => {
327 widgetItem.classList.remove('item-highlight');
328 }, 600);
329 });
330 },
331
332 saveSettingsSubmit: function () {
333 const vm = this;
334 $('#upk_setup_wizard_modules').submit(function (e) {
335 e.preventDefault();
336 var data = $(this).serialize();
337
338 // Get the button and store original text
339 const saveBtn = $(this).find('#save-and-continue');
340 const originalText = saveBtn.html().trim();
341
342 // Show loading state
343 saveBtn.prop('disabled', true)
344 .html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Saving...');
345
346 $.ajax({
347 url: BDT_SetupWizard.ajax_url,
348 type: 'POST',
349 data: data,
350 success: function (response) {
351 if (response.success) {
352 saveBtn.html('<i class="dashicons dashicons-yes-alt"></i> Saved!');
353 setTimeout(() => {
354 vm.goToStep(vm.currentStep + 1);
355 // Reset button state
356 saveBtn.prop('disabled', false).html(originalText);
357 }, 1000);
358 } else {
359 saveBtn.html('<i class="dashicons dashicons-no"></i> Failed');
360 setTimeout(() => {
361 saveBtn.prop('disabled', false).html(originalText);
362 }, 1000);
363 alert('Failed to save settings');
364 }
365 },
366 error: function (error) {
367 saveBtn.html('<i class="dashicons dashicons-no"></i> Error');
368 setTimeout(() => {
369 saveBtn.prop('disabled', false).html(originalText);
370 }, 1000);
371 console.error('Error:', error);
372 }
373 });
374 });
375 },
376
377 installPlugins: function () {
378 const vm = this;
379 $('#upk-install-plugins').submit(function (e) {
380 e.preventDefault();
381
382 vm.installButton.disabled = true;
383 vm.installButton.innerHTML = '<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Installing...';
384
385 const pluginSlugs = [];
386 const data = $(this).serialize();
387 data.split('&').forEach(item => {
388 const [key, value] = item.split('=');
389 if (key.startsWith('plugins') && value === 'on') {
390 const slug = decodeURIComponent(key.split('%5B%5D')[1]);
391 if (slug) {
392 pluginSlugs.push(slug);
393 }
394 }
395 });
396
397 let installedPlugins = 0;
398 const totalPluginsSlug = pluginSlugs.length;
399
400 let progressContainer = document.querySelector('.install-progress-container');
401 if (!progressContainer) {
402 progressContainer = document.createElement('div');
403 progressContainer.className = 'install-progress-container';
404 progressContainer.innerHTML = `
405 <div class="progress-bar-wrapper">
406 <div id="plugin-install-progress" class="progress-bar" style="width: 0%">0%</div>
407 </div>
408 <div class="install-status">Preparing to install plugins...</div>
409 `;
410 vm.installButton.parentNode.appendChild(progressContainer);
411 }
412
413 const progressBar = document.getElementById('plugin-install-progress');
414 const statusText = document.querySelector('.install-status');
415
416 const updateProgressBar = () => {
417 const progress = (installedPlugins / totalPluginsSlug) * 100;
418 progressBar.style.width = `${progress}%`;
419 progressBar.textContent = `${Math.round(progress)}%`;
420 statusText.textContent = `Installing plugin ${installedPlugins} of ${totalPluginsSlug}...`;
421 };
422
423 const installNextPlugin = () => {
424 if (installedPlugins < totalPluginsSlug) {
425 const slug = pluginSlugs[installedPlugins];
426 const pluginName = slug.split('/')[0].split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
427
428 statusText.textContent = `Installing ${pluginName}...`;
429
430 jQuery.ajax({
431 url: BDT_SetupWizard.ajax_url,
432 method: 'POST',
433 data: {
434 action: 'setup_wizard_install_plugins',
435 nonce: BDT_SetupWizard.nonce,
436 plugins: [slug]
437 },
438 success: (response) => {
439 if (response.success) {
440 installedPlugins++;
441 updateProgressBar();
442
443 statusText.innerHTML += ` <span class="success-indicator"><i class="dashicons dashicons-yes-alt"></i></span>`;
444
445 const pluginItem = document.querySelector(`[data-slug="${slug}"]`);
446 if (pluginItem) {
447 pluginItem.classList.add('plugin-installed');
448 }
449
450 setTimeout(() => {
451 installNextPlugin();
452 }, 500);
453 } else {
454 statusText.innerHTML += ` <span class="error-indicator"><i class="dashicons dashicons-no"></i> Failed</span>`;
455 installedPlugins++;
456 updateProgressBar();
457 setTimeout(() => {
458 installNextPlugin();
459 }, 500);
460 }
461 },
462 error: (error) => {
463 statusText.innerHTML += ` <span class="error-indicator"><i class="dashicons dashicons-no"></i> Error</span>`;
464 installedPlugins++;
465 updateProgressBar();
466 setTimeout(() => {
467 installNextPlugin();
468 }, 500);
469 }
470 });
471 } else {
472 statusText.textContent = 'All plugins installed successfully!';
473 statusText.innerHTML += ' <span class="success-indicator"><i class="dashicons dashicons-yes-alt"></i></span>';
474
475 setTimeout(() => {
476 vm.installButton.disabled = false;
477 vm.installButton.innerHTML = 'Installation Complete';
478 vm.goToStep(vm.currentStep + 1);
479 }, 1500);
480 }
481 };
482
483 installNextPlugin();
484 });
485 }
486 };
487
488 wizard.init();
489 });
490
491 // Handle template import button clicks
492 $('body').on('click', '.upk-setup-wizard .template-import', function (e) {
493 e.preventDefault();
494 e.stopPropagation();
495
496 const $button = $(this);
497 const $templateCard = $button.closest('.choose-template');
498 const importUrl = $templateCard.data('import-url');
499 const templateName = $templateCard.find('.template-title').text();
500
501 if (!importUrl) {
502 alert('Import URL not found');
503 return;
504 }
505
506 // Prevent multiple clicks
507 if ($button.prop('disabled')) {
508 return;
509 }
510
511 // Update button state
512 const originalButtonHtml = $button.html();
513 $button.prop('disabled', true)
514 .html('<i class="dashicons dashicons-update"></i> Importing...');
515
516 $templateCard.addClass('template-importing');
517 $templateCard.find('.template-title').html(`<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Importing ${templateName}...`);
518
519 // Determine import type based on template card classes
520 const isZipTemplate = $templateCard.hasClass('bdt-upk-import-temp-zip');
521 const isJsonTemplate = $templateCard.hasClass('bdt-upk-import-temp-json');
522
523 if (isJsonTemplate) {
524 // Handle JSON template import
525 $.ajax({
526 url: BDT_SetupWizard.ajax_url,
527 type: 'POST',
528 data: {
529 action: 'import_elementor_template',
530 nonce: BDT_SetupWizard.nonce,
531 import_url: importUrl
532 },
533 success: function(response) {
534 if (response.success) {
535 $button.removeClass('template-importing')
536 .addClass('template-imported')
537 .html('<i class="dashicons dashicons-yes-alt"></i> Imported');
538 $templateCard.removeClass('template-importing').addClass('template-imported');
539 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-yes-alt"></i> ${templateName} Imported`);
540 } else {
541 $button.removeClass('template-importing')
542 .addClass('template-import-failed')
543 .html('<i class="dashicons dashicons-no"></i> Failed');
544 $templateCard.removeClass('template-importing').addClass('template-import-failed');
545 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-no"></i> Import Failed`);
546
547 // Reset button after 3 seconds
548 setTimeout(() => {
549 $button.prop('disabled', false)
550 .removeClass('template-import-failed')
551 .html(originalButtonHtml);
552 }, 3000);
553 }
554 },
555 error: function() {
556 $button.removeClass('template-importing')
557 .addClass('template-import-failed')
558 .html('<i class="dashicons dashicons-no"></i> Failed');
559 $templateCard.removeClass('template-importing').addClass('template-import-failed');
560 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-no"></i> Import Failed`);
561
562 // Reset button after 3 seconds
563 setTimeout(() => {
564 $button.prop('disabled', false)
565 .removeClass('template-import-failed')
566 .html(originalButtonHtml);
567 }, 3000);
568 }
569 });
570 } else if (isZipTemplate) {
571 // Handle ZIP template import
572 $.ajax({
573 url: BDT_SetupWizard.ajax_url,
574 type: 'POST',
575 data: {
576 action: 'import_upk_elementor_bundle_template',
577 nonce: BDT_SetupWizard.nonce,
578 import_url: importUrl
579 },
580 success: async function(response) {
581 if (response.success) {
582 const sessionId = response.data.session;
583 const runners = response?.data?.runners;
584 let error = '';
585
586 for (const runner of runners) {
587 const success = await importRunner(sessionId, runner);
588 if (!success) {
589 error = `❌ Failed to import: ${runner}`;
590 break;
591 }
592 }
593
594 if (error) {
595 $button.removeClass('template-importing')
596 .addClass('template-import-failed')
597 .html('<i class="dashicons dashicons-no"></i> Failed');
598 $templateCard.removeClass('template-importing').addClass('template-import-failed');
599 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-no"></i> ${error}`);
600 alert(error);
601
602 // Reset button after 3 seconds
603 setTimeout(() => {
604 $button.prop('disabled', false)
605 .removeClass('template-import-failed')
606 .html(originalButtonHtml);
607 }, 3000);
608 return;
609 }
610
611 $button.removeClass('template-importing')
612 .addClass('template-imported')
613 .html('<i class="dashicons dashicons-yes-alt"></i> Imported');
614 $templateCard.removeClass('template-importing').addClass('template-imported');
615 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-yes-alt"></i> ${templateName} Imported`);
616 } else {
617 $button.removeClass('template-importing')
618 .addClass('template-import-failed')
619 .html('<i class="dashicons dashicons-no"></i> Failed');
620 $templateCard.removeClass('template-importing').addClass('template-import-failed');
621 const missingPlugins = response?.data?.plugins;
622 if (missingPlugins) {
623 let pluginArr = [];
624 for (const plugin of missingPlugins) {
625 pluginArr.push(plugin.name);
626 }
627 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-no"></i> ${"plugins are required to import: " + pluginArr.join(', ')}`);
628 } else {
629 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-no"></i> ${response.data.message}`);
630 }
631
632 // Reset button after 3 seconds
633 setTimeout(() => {
634 $button.prop('disabled', false)
635 .removeClass('template-import-failed')
636 .html(originalButtonHtml);
637 }, 3000);
638 }
639 },
640 error: function() {
641 $button.removeClass('template-importing')
642 .addClass('template-import-failed')
643 .html('<i class="dashicons dashicons-no"></i> Failed');
644 $templateCard.removeClass('template-importing').addClass('template-import-failed');
645 $templateCard.find('.template-title').html(`<i class="dashicons dashicons-no"></i> Import Failed`);
646
647 // Reset button after 3 seconds
648 setTimeout(() => {
649 $button.prop('disabled', false)
650 .removeClass('template-import-failed')
651 .html(originalButtonHtml);
652 }, 3000);
653 }
654 });
655 } else {
656 // Unsupported template type
657 alert('Unsupported template format');
658 $button.prop('disabled', false).html(originalButtonHtml);
659 $templateCard.removeClass('template-importing');
660 }
661 });
662
663 async function importRunner(sessionId, runner) {
664 try {
665 const response = await new Promise((resolve, reject) => {
666 $.ajax({
667 url: BDT_SetupWizard.ajax_url, type: 'POST', data: {
668 action: 'import_upk_elementor_bundle_runner_template',
669 nonce: BDT_SetupWizard.nonce,
670 sessionId: sessionId,
671 runner: runner,
672 }, success: resolve, error: reject
673 })
674 });
675
676 return response.success;
677 } catch (error) {
678 console.error('AJAX Error:', error);
679 return false;
680 }
681 }
682
683 })(jQuery);
684
685 document.addEventListener('DOMContentLoaded', function() {
686 const style = document.createElement('style');
687 style.textContent = `
688 .bdt-wizard-step {
689 transition: opacity 0.3s ease, transform 0.3s ease;
690 display: none; /* Hide all steps by default */
691 }
692
693 .bdt-wizard-step.active {
694 display: block; /* Show only active step */
695 opacity: 1;
696 transform: translateY(0);
697 }
698
699 .item-highlight {
700 transition: all 0.3s ease;
701 box-shadow: 0 0 0 2px var(--upk-primary);
702 transform: translateY(-3px);
703 }
704
705 .button-pulse {
706 animation: buttonPulse 0.5s ease;
707 }
708
709 .pulse-animation {
710 animation: pulse 1.5s infinite;
711 }
712
713 /* Spinner for plugin installation and template importing */
714 .spinner {
715 display: inline-block;
716 width: 16px;
717 height: 16px;
718 border: 2px solid rgba(255,255,255,0.3);
719 border-radius: 50%;
720 border-top-color: #fff;
721 animation: spin 1s linear infinite;
722 margin-right: 8px;
723 }
724
725 /* Plugin installation progress styles */
726 .install-progress-container {
727 margin-top: 20px;
728 padding: 15px;
729 background: var(--upk-gray-light);
730 border-radius: var(--upk-border-radius);
731 }
732
733 .progress-bar-wrapper {
734 height: 8px;
735 background: var(--upk-gray-medium);
736 border-radius: 4px;
737 overflow: hidden;
738 margin-bottom: 10px;
739 }
740
741 .progress-bar {
742 height: 100%;
743 background: var(--upk-primary);
744 border-radius: 4px;
745 transition: width 0.3s ease;
746 color: transparent;
747 font-size: 0;
748 }
749
750 .install-status {
751 font-size: 14px;
752 color: var(--upk-text-light);
753 }
754
755 .success-indicator {
756 color: var(--upk-success);
757 }
758
759 .error-indicator {
760 color: var(--upk-danger);
761 }
762
763 .plugin-installed {
764 border-color: var(--upk-success) !important;
765 }
766
767 /* Template import states */
768 .template-importing {
769 opacity: 0.7;
770 pointer-events: none;
771 }
772
773 .template-imported {
774 border-color: var(--upk-success) !important;
775 }
776
777 .template-import-failed {
778 border-color: var(--upk-danger) !important;
779 }
780
781 /* Template import button states */
782 .template-import:disabled {
783 cursor: not-allowed;
784 }
785
786 .template-import.template-imported {
787 background-color: #28a745;
788 border-color: #28a745;
789 color: white;
790 }
791
792 .template-import.template-import-failed {
793 background-color: #dc3545;
794 border-color: #dc3545;
795 color: white;
796 }
797
798 /* Animations */
799 @keyframes buttonPulse {
800 0%, 100% { transform: scale(1); }
801 50% { transform: scale(1.05); }
802 }
803
804 @keyframes pulse {
805 0% { box-shadow: 0 0 0 0 rgba(108, 92, 231, 0.4); }
806 70% { box-shadow: 0 0 0 10px rgba(108, 92, 231, 0); }
807 100% { box-shadow: 0 0 0 0 rgba(108, 92, 231, 0); }
808 }
809
810 @keyframes spin {
811 0% { transform: rotate(0deg); }
812 100% { transform: rotate(360deg); }
813 }
814 `;
815 document.head.appendChild(style);
816 });
817