| import 'dart:async'; |
| import 'package:jaspr/dom.dart'; |
| import 'package:jaspr/jaspr.dart'; |
| |
| import '../../../utils/provider.dart'; |
| import '../../../utils/styles.dart'; |
| import '../../workspace/data/workspace_repository.dart'; |
| |
| class AnalysisStatus extends StatefulComponent { |
| const AnalysisStatus({super.key}); |
| |
| @override |
| State<AnalysisStatus> createState() => _AnalysisStatusState(); |
| |
| @css |
| static List<StyleRule> get styles => _AnalysisStatusState.styles; |
| } |
| |
| class _AnalysisStatusState extends State<AnalysisStatus> { |
| StreamSubscription<bool>? _subscription; |
| Timer? _debounceTimer; |
| bool _showAnalyzing = false; |
| |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| if (_subscription == null) { |
| final WorkspaceRepository repo = context.get<WorkspaceRepository>(); |
| _subscription = repo.languageServerClient.codeMirrorLspClient.analysisStatus.listen(_handleStatusChange); |
| } |
| } |
| |
| void _handleStatusChange(bool isAnalyzing) { |
| _debounceTimer?.cancel(); |
| if (isAnalyzing) { |
| _debounceTimer = Timer(const Duration(milliseconds: 100), () { |
| setState(() { |
| _showAnalyzing = true; |
| }); |
| }); |
| } else { |
| setState(() { |
| _showAnalyzing = false; |
| }); |
| } |
| } |
| |
| @override |
| void dispose() { |
| _subscription?.cancel(); |
| _debounceTimer?.cancel(); |
| super.dispose(); |
| } |
| |
| @override |
| Component build(BuildContext context) { |
| return div( |
| classes: 'analysis-status', |
| styles: Styles( |
| display: _showAnalyzing ? Display.flex : Display.none, |
| ), |
| [ |
| const div(classes: 'analysis-status-spinner', []), |
| const span(classes: 'analysis-status-text', [.text('Analyzing...')]), |
| ], |
| ); |
| } |
| |
| static List<StyleRule> get styles => [ |
| css.keyframes('spin', { |
| '0%': const Styles(raw: {'transform': 'rotate(0deg)'}), |
| '100%': const Styles(raw: {'transform': 'rotate(360deg)'}), |
| }), |
| css('.analysis-status').styles( |
| display: Display.flex, |
| padding: Padding.symmetric(horizontal: 12.px, vertical: 8.px), |
| border: Border.only( |
| top: BorderSide.solid( |
| color: colorBorder, |
| width: 1.px, |
| ), |
| ), |
| userSelect: UserSelect.none, |
| alignItems: AlignItems.center, |
| gap: Gap.all(8.px), |
| color: colorOnSurfaceVariant, |
| fontSize: 11.px, |
| fontWeight: FontWeight.w500, |
| raw: {'flex-shrink': '0'}, |
| ), |
| css('.analysis-status-spinner').styles( |
| width: 12.px, |
| height: 12.px, |
| border: Border.all(color: colorPrimary.withOpacity(0.3), width: 2.px), |
| raw: { |
| 'border-top-color': 'var(--color-primary)', |
| 'border-radius': '50%', |
| 'animation': 'spin 1s linear infinite', |
| }, |
| ), |
| ]; |
| } |