|
| 1 | +<?php |
| 2 | +/** |
| 3 | + * Gravity Connect // OpenAI // Stream Loading Text Animation |
| 4 | + * |
| 5 | + * Adds a customizable shimmer animation and rotating spinner icon to the Stream field's |
| 6 | + * loading placeholders. Replaces the static "Loading..." text with animated text and/or |
| 7 | + * a rotating spinner icon. |
| 8 | + */ |
| 9 | +class GCOAI_Loading_Animation { |
| 10 | + |
| 11 | + private $args; |
| 12 | + |
| 13 | + public function __construct( $args = array() ) { |
| 14 | + $this->args = wp_parse_args( $args, array( |
| 15 | + 'text' => 'Thinking...', |
| 16 | + 'base_color' => '#000', |
| 17 | + 'shimmer_color' => '#fff', |
| 18 | + 'shimmer_duration' => '2.2s', |
| 19 | + 'show_shimmer' => true, |
| 20 | + 'show_spinner' => false, |
| 21 | + 'spinner_size' => '24', |
| 22 | + 'form_id' => null, |
| 23 | + 'field_id' => null, |
| 24 | + ) ); |
| 25 | + |
| 26 | + add_filter( 'gform_gcoai_field_loading_text', array( $this, 'filter_loading_text' ), 10, 3 ); |
| 27 | + add_action( 'gform_register_init_scripts', array( $this, 'register_init_script' ), 10, 2 ); |
| 28 | + } |
| 29 | + |
| 30 | + public function register_init_script( $form, $is_ajax ) { |
| 31 | + if ( empty( $form['id'] ) ) { |
| 32 | + return; |
| 33 | + } |
| 34 | + |
| 35 | + // If form_id is specified, only run scripts on those forms |
| 36 | + if ( $this->args['form_id'] !== null ) { |
| 37 | + $form_ids = is_array( $this->args['form_id'] ) ? $this->args['form_id'] : array( $this->args['form_id'] ); |
| 38 | + if ( ! in_array( $form['id'], $form_ids ) ) { |
| 39 | + return; |
| 40 | + } |
| 41 | + } |
| 42 | + |
| 43 | + $markup = $this->get_shimmer_markup(); |
| 44 | + $css = $this->get_styles_css(); |
| 45 | + |
| 46 | + ?> |
| 47 | + <script type="text/javascript"> |
| 48 | + (function($) { |
| 49 | + var shimmerMarkup = <?php echo wp_json_encode( $markup ); ?>; |
| 50 | + var shimmerStyles = <?php echo wp_json_encode( $css ); ?>; |
| 51 | + |
| 52 | + function addStylesToPage() { |
| 53 | + if ( ! $('style.gw-gcoai-shimmer-style').length ) { |
| 54 | + $('<style>') |
| 55 | + .addClass('gw-gcoai-shimmer-style') |
| 56 | + .text(shimmerStyles) |
| 57 | + .appendTo('head'); |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + function applyShimmerToPlaceholders($container) { |
| 62 | + var $searchContext = $container && $container.length ? $container : $(document); |
| 63 | + $searchContext.find('.gcoai-output .gcoai-placeholder').html(shimmerMarkup); |
| 64 | + } |
| 65 | + |
| 66 | + if ( window.gform && typeof window.gform.addFilter === 'function' ) { |
| 67 | + window.gform.addFilter('gcoai_stream_loading_placeholder', function(current, instance) { |
| 68 | + return shimmerMarkup; |
| 69 | + }); |
| 70 | + } |
| 71 | + |
| 72 | + $(function() { |
| 73 | + addStylesToPage(); |
| 74 | + applyShimmerToPlaceholders(); |
| 75 | + }); |
| 76 | + |
| 77 | + // Re-apply after Generate/Regenerate clicks |
| 78 | + $(document).on('click', '.gcoai-trigger, .gcoai-regenerate', function() { |
| 79 | + setTimeout(function() { |
| 80 | + applyShimmerToPlaceholders(); |
| 81 | + }, 50); |
| 82 | + }); |
| 83 | + |
| 84 | + // Re-apply after AJAX completes |
| 85 | + $(document).ajaxComplete(function() { |
| 86 | + applyShimmerToPlaceholders(); |
| 87 | + }); |
| 88 | + })(jQuery); |
| 89 | + </script> |
| 90 | + <?php |
| 91 | + } |
| 92 | + |
| 93 | + public function get_shimmer_markup() { |
| 94 | + $spinner = ''; |
| 95 | + |
| 96 | + if ( $this->args['show_spinner'] ) { |
| 97 | + $spinner = sprintf( |
| 98 | + '<svg class="shimmer-spinner" xmlns="http://www.w3.org/2000/svg" width="%s" height="%s" stroke="%s" viewBox="0 0 24 24"> |
| 99 | + <g class="spinner-rotate"> |
| 100 | + <circle cx="12" cy="12" r="9.5" fill="none" stroke-width="1.5"/> |
| 101 | + </g> |
| 102 | + </svg>', |
| 103 | + esc_attr( $this->args['spinner_size'] ), |
| 104 | + esc_attr( $this->args['spinner_size'] ), |
| 105 | + esc_attr( $this->args['base_color'] ) |
| 106 | + ); |
| 107 | + } |
| 108 | + |
| 109 | + $text_class = $this->args['show_shimmer'] ? 'shimmer' : 'shimmer-text'; |
| 110 | + |
| 111 | + return sprintf( |
| 112 | + '<span class="shimmer-wrapper">%s<span class="%s">%s</span></span>', |
| 113 | + $spinner, |
| 114 | + $text_class, |
| 115 | + esc_html( $this->args['text'] ) |
| 116 | + ); |
| 117 | + } |
| 118 | + |
| 119 | + public function filter_loading_text( $placeholder, $field, $form = null ) { |
| 120 | + if ( ! class_exists( '\\GC_OpenAI\\Fields\\Stream' ) || ! $field instanceof \GC_OpenAI\Fields\Stream ) { |
| 121 | + return $placeholder; |
| 122 | + } |
| 123 | + |
| 124 | + // If form_id is specified, only apply to those forms |
| 125 | + if ( $this->args['form_id'] !== null ) { |
| 126 | + $form_ids = is_array( $this->args['form_id'] ) ? $this->args['form_id'] : array( $this->args['form_id'] ); |
| 127 | + if ( $form && ! in_array( rgar( $form, 'id' ), $form_ids ) ) { |
| 128 | + return $placeholder; |
| 129 | + } |
| 130 | + } |
| 131 | + |
| 132 | + // If field_id is specified, only apply to those fields |
| 133 | + if ( $this->args['field_id'] !== null ) { |
| 134 | + $field_ids = is_array( $this->args['field_id'] ) ? $this->args['field_id'] : array( $this->args['field_id'] ); |
| 135 | + if ( ! in_array( rgar( $field, 'id' ), $field_ids ) ) { |
| 136 | + return $placeholder; |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + return $this->get_shimmer_markup(); |
| 141 | + } |
| 142 | + |
| 143 | + private function get_styles_css() { |
| 144 | + $base = esc_attr( $this->args['base_color'] ); |
| 145 | + $shimmer = esc_attr( $this->args['shimmer_color'] ); |
| 146 | + $dur = esc_attr( $this->args['shimmer_duration'] ); |
| 147 | + |
| 148 | + return |
| 149 | + ".shimmer-wrapper { display: inline-flex; align-items: center; gap: 8px; } " . |
| 150 | + ".shimmer-spinner { flex-shrink: 0; } " . |
| 151 | + ".spinner-rotate { transform-origin: center; animation: spinner-rotation 2s linear infinite; } " . |
| 152 | + ".spinner-rotate circle { stroke-linecap: round; animation: spinner-stroke 1.5s ease-in-out infinite; } " . |
| 153 | + "@keyframes spinner-rotation { 100% { transform: rotate(360deg); } } " . |
| 154 | + "@keyframes spinner-stroke { 0% { stroke-dasharray: 0 150; stroke-dashoffset: 0; } 47.5% { stroke-dasharray: 42 150; stroke-dashoffset: -16; } 95%, 100% { stroke-dasharray: 42 150; stroke-dashoffset: -59; } } " . |
| 155 | + ".shimmer-text { display: inline-block; color: {$base}; line-height: 1.2; } " . |
| 156 | + ".shimmer { display: inline-block; color: {$base}; line-height: 1.2; background: {$base} linear-gradient(to left, {$base}, {$shimmer} 50%, {$base}); background-position: -4rem top; background-repeat: no-repeat; background-size: 4rem 100%; -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent; animation: shimmer {$dur} infinite; } " . |
| 157 | + "@keyframes shimmer { 0% { background-position: -4rem top; } 70%, 100% { background-position: 12.5rem top; } }"; |
| 158 | + } |
| 159 | +} |
| 160 | + |
| 161 | +# Configuration |
| 162 | + |
| 163 | +new GCOAI_Loading_Animation( array( |
| 164 | + 'text' => 'Thinking...', |
| 165 | + 'base_color' => '#292929', |
| 166 | + 'shimmer_color' => '#fff', |
| 167 | + 'shimmer_duration' => '2.2s', |
| 168 | + 'show_shimmer' => true, |
| 169 | + 'show_spinner' => true, |
| 170 | + 'spinner_size' => '16', |
| 171 | + // 'form_id' => 123, // Uncomment and set to target specific form(s): 123 or array( 18, 22, 35 ) |
| 172 | + // 'field_id' => 4, // Uncomment and set to target specific field(s): 5 or array( 5, 7, 12 ) |
| 173 | +) ); |
0 commit comments