File size: 4,346 Bytes
3c76719
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
'use client';

import { useEffect, useState, useRef, useCallback } from 'react';

interface SearchLoadingAnimationProps {
  currentSource?: string;
  checkedSources?: number;
  totalSources?: number;
  isPaused?: boolean;
  onComplete?: (checkedSources: number, totalSources: number) => void;
}

export function SearchLoadingAnimation({
  currentSource,
  checkedSources = 0,
  totalSources = 16,
  isPaused = false,
  onComplete,
}: SearchLoadingAnimationProps) {
  const [dots, setDots] = useState('');
  const dotIntervalRef = useRef<NodeJS.Timeout | null>(null);
  const hasCalledComplete = useRef(false);

  // Calculate progress (0-100%)
  const progress = totalSources > 0 ? (checkedSources / totalSources) * 100 : 0;
  const isComplete = progress >= 100;

  // Animation pause/resume logic - Optimized interval
  useEffect(() => {
    if (isPaused || isComplete) {
      if (dotIntervalRef.current) {
        clearInterval(dotIntervalRef.current);
        dotIntervalRef.current = null;
      }
      return;
    }

    dotIntervalRef.current = setInterval(() => {
      setDots((prev) => (prev.length >= 3 ? '' : prev + '.'));
    }, 600); // Increased from 500ms to 600ms for better performance

    return () => {
      if (dotIntervalRef.current) {
        clearInterval(dotIntervalRef.current);
        dotIntervalRef.current = null;
      }
    };
  }, [isPaused, isComplete]);

  // Call onComplete callback when animation finishes
  useEffect(() => {
    if (isComplete && onComplete && !hasCalledComplete.current) {
      hasCalledComplete.current = true;
      // Small delay to allow animation to settle
      const timeout = setTimeout(() => {
        onComplete(checkedSources, totalSources);
      }, 100);
      return () => clearTimeout(timeout);
    }
  }, [isComplete, onComplete, checkedSources, totalSources]);

  const statusText = `${checkedSources}/${totalSources} 个源`;

  return (
    <div className="w-full space-y-3 animate-fade-in">
      {/* Loading Message with Icon */}
      <div className="flex items-center justify-center gap-3">
        {/* Spinning Icon */}
        <svg className="w-5 h-5 animate-spin-slow" viewBox="0 0 24 24">
          <circle
            cx="12"
            cy="12"
            r="10"
            fill="none"
            stroke="var(--accent-color)"
            strokeWidth="3"
            strokeDasharray="60 40"
            strokeLinecap="round"
          />
        </svg>

        <span className="text-sm font-medium text-[var(--text-color-secondary)]">
          正在搜索视频源{dots}
        </span>
      </div>

      {/* Progress Bar - Unified 0-100% */}
      <div className="w-full">
        <div
          className="h-1 bg-[color-mix(in_srgb,var(--glass-bg)_50%,transparent)] overflow-hidden rounded-[var(--radius-full)]"
        >
          <div
            className="h-full bg-[var(--accent-color)] transition-all duration-500 ease-out relative rounded-[var(--radius-full)]"
            style={{
              width: `${progress}%`
            }}
          >
            {/* Shimmer Effect - Optimized for GPU with contain for better performance */}
            <div
              className="absolute inset-0 animate-shimmer"
              style={{
                background: 'linear-gradient(90deg, transparent 0%, rgba(255, 255, 255, 0.3) 50%, transparent 100%)',
                willChange: 'transform',
                transform: 'translateZ(0)',
                contain: 'strict'
              }}
            ></div>
          </div>
        </div>

        {/* Progress Info - Real-time count with pause indicator */}
        <div className="flex items-center justify-between mt-2 text-xs text-[var(--text-color-secondary)]">
          <span className="flex items-center gap-2">
            {statusText}
            {isPaused && (
              <span className="px-2 py-0.5 rounded-[var(--radius-full)] bg-[var(--glass-bg)] text-[10px]">
                已暂停
              </span>
            )}
            {isComplete && (
              <span className="px-2 py-0.5 rounded-[var(--radius-full)] bg-[var(--accent-color)] text-white text-[10px]">
                完成
              </span>
            )}
          </span>
          <span className="font-medium">{Math.round(progress)}%</span>
        </div>
      </div>
    </div>
  );
}