• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atomic14 / web-serial-plotter / 17380292065

01 Sep 2025 02:18PM UTC coverage: 63.442% (-5.4%) from 68.885%
17380292065

push

github

cgreening
Export data

407 of 517 branches covered (78.72%)

Branch coverage included in aggregate %.

22 of 23 new or added lines in 3 files covered. (95.65%)

142 existing lines in 4 files now uncovered.

1941 of 3184 relevant lines covered (60.96%)

36.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

45.65
/src/components/PlotToolsOverlay.tsx
1
import { forwardRef, useState, useRef, useEffect } from 'react'
1✔
2
import Button from './ui/Button'
1✔
3
import { PlayIcon, PauseIcon, MagnifyingGlassPlusIcon, MagnifyingGlassMinusIcon, CameraIcon, ArrowDownTrayIcon } from '@heroicons/react/24/outline'
1✔
4
import type { ChartExportOptions } from '../utils/chartExport'
5

6
interface Props {
7
  frozen: boolean
8
  onToggleFrozen: () => void
9
  onZoomIn: () => void
10
  onZoomOut: () => void
11
  onSavePng: () => Promise<void> | void
12
  onExportCsv: (options: ChartExportOptions) => void
13
  hasData: boolean
14
}
15

16
const PlotToolsOverlay = forwardRef<HTMLDivElement, Props>(function PlotToolsOverlay({ frozen, onToggleFrozen, onZoomIn, onZoomOut, onSavePng, onExportCsv, hasData }, ref) {
1✔
17
  const [showExportMenu, setShowExportMenu] = useState(false)
1✔
18
  const exportMenuRef = useRef<HTMLDivElement>(null)
1✔
19

20
  // Close menu when clicking outside
21
  useEffect(() => {
1✔
22
    const handleClickOutside = (event: MouseEvent) => {
1✔
UNCOV
23
      if (exportMenuRef.current && !exportMenuRef.current.contains(event.target as Node)) {
×
UNCOV
24
        setShowExportMenu(false)
×
UNCOV
25
      }
×
UNCOV
26
    }
×
27

28
    if (showExportMenu) {
1!
UNCOV
29
      document.addEventListener('mousedown', handleClickOutside)
×
UNCOV
30
      return () => document.removeEventListener('mousedown', handleClickOutside)
×
UNCOV
31
    }
×
32
  }, [showExportMenu])
1✔
33
  return (
1✔
34
    <div className="absolute top-2 right-2 flex items-center gap-2 pointer-events-auto" ref={ref}>
1✔
35
      <Button size="sm" variant="neutral" aria-label={frozen ? 'Play' : 'Pause'} title={frozen ? 'Play' : 'Pause'} onClick={onToggleFrozen}>
1!
36
        {frozen ? (
1!
UNCOV
37
          <PlayIcon className="w-5 h-5" />
×
38
        ) : (
39
          <PauseIcon className="w-5 h-5" />
1✔
40
        )}
41
      </Button>
1✔
42
      <Button size="sm" variant="neutral" aria-label="Zoom in" title="Zoom in" onClick={onZoomIn}>
1✔
43
        <MagnifyingGlassPlusIcon className="w-5 h-5" />
1✔
44
      </Button>
1✔
45
      <Button size="sm" variant="neutral" aria-label="Zoom out" title="Zoom out" onClick={onZoomOut}>
1✔
46
        <MagnifyingGlassMinusIcon className="w-5 h-5" />
1✔
47
      </Button>
1✔
48
      
49
      {/* CSV Export Button with Dropdown */}
50
      <div className="relative" ref={exportMenuRef}>
1✔
51
        <Button 
1✔
52
          size="sm" 
1✔
53
          variant="neutral" 
1✔
54
          aria-label="Export CSV" 
1✔
55
          title="Export CSV" 
1✔
56
          disabled={!hasData}
1✔
57
          onClick={() => setShowExportMenu(!showExportMenu)}
1✔
58
        >
59
          <ArrowDownTrayIcon className="w-5 h-5" />
1✔
60
        </Button>
1✔
61
        
62
        {showExportMenu && (
1!
UNCOV
63
          <div className="absolute right-0 top-full mt-1 bg-white dark:bg-neutral-800 border border-gray-200 dark:border-neutral-700 rounded-md shadow-lg z-10 min-w-48">
×
UNCOV
64
            <div className="p-2 space-y-1">
×
UNCOV
65
              <button
×
UNCOV
66
                onClick={() => {
×
UNCOV
67
                  onExportCsv({ scope: 'visible', includeTimestamps: true, timeFormat: 'iso' })
×
UNCOV
68
                  setShowExportMenu(false)
×
UNCOV
69
                }}
×
UNCOV
70
                className="w-full text-left px-2 py-1 text-sm text-gray-700 dark:text-neutral-300 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded"
×
UNCOV
71
              >
×
72
                📊 Export Visible Data
UNCOV
73
              </button>
×
UNCOV
74
              <button
×
UNCOV
75
                onClick={() => {
×
UNCOV
76
                  onExportCsv({ scope: 'all', includeTimestamps: true, timeFormat: 'iso' })
×
UNCOV
77
                  setShowExportMenu(false)
×
UNCOV
78
                }}
×
UNCOV
79
                className="w-full text-left px-2 py-1 text-sm text-gray-700 dark:text-neutral-300 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded"
×
UNCOV
80
              >
×
81
                📈 Export All Data
UNCOV
82
              </button>
×
UNCOV
83
              <div className="border-t border-gray-200 dark:border-neutral-700 my-1" />
×
UNCOV
84
              <button
×
UNCOV
85
                onClick={() => {
×
UNCOV
86
                  onExportCsv({ scope: 'visible', includeTimestamps: true, timeFormat: 'relative' })
×
UNCOV
87
                  setShowExportMenu(false)
×
UNCOV
88
                }}
×
UNCOV
89
                className="w-full text-left px-2 py-1 text-sm text-gray-700 dark:text-neutral-300 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded"
×
UNCOV
90
              >
×
91
                ⏱️ Visible (Relative Time)
UNCOV
92
              </button>
×
UNCOV
93
              <button
×
UNCOV
94
                onClick={() => {
×
UNCOV
95
                  onExportCsv({ scope: 'all', includeTimestamps: true, timeFormat: 'relative' })
×
UNCOV
96
                  setShowExportMenu(false)
×
UNCOV
97
                }}
×
UNCOV
98
                className="w-full text-left px-2 py-1 text-sm text-gray-700 dark:text-neutral-300 hover:bg-gray-100 dark:hover:bg-neutral-700 rounded"
×
UNCOV
99
              >
×
100
                ⏱️ All Data (Relative Time)
UNCOV
101
              </button>
×
UNCOV
102
            </div>
×
UNCOV
103
          </div>
×
104
        )}
105
      </div>
1✔
106
      
107
      <Button size="sm" variant="neutral" aria-label="Save PNG" title="Save PNG" onClick={onSavePng}>
1✔
108
        <CameraIcon className="w-5 h-5" />
1✔
109
      </Button>
1✔
110
    </div>
1✔
111
  )
112
})
1✔
113

114
export default PlotToolsOverlay
1✔
115

116

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc