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

atomic14 / web-serial-plotter / 17385998257

01 Sep 2025 07:42PM UTC coverage: 61.901% (-0.8%) from 62.741%
17385998257

push

github

cgreening
Switch to driver-js

410 of 522 branches covered (78.54%)

Branch coverage included in aggregate %.

0 of 34 new or added lines in 1 file covered. (0.0%)

82 existing lines in 4 files now uncovered.

2006 of 3381 relevant lines covered (59.33%)

34.85 hits per line

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

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

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

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

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

30
    if (showExportMenu) {
1!
31
      document.addEventListener('mousedown', handleClickOutside)
×
32
      return () => document.removeEventListener('mousedown', handleClickOutside)
×
33
    }
×
34
  }, [showExportMenu])
1✔
35
  return (
1✔
36
    <div className="flex items-center gap-2" ref={ref}>
1✔
37
      <Tooltip label={frozen ? 'Resume (play)' : 'Freeze (pause)'}>
1!
38
        <Button id="tour-tool-freeze" size="sm" variant="neutral" aria-label={frozen ? 'Play' : 'Pause'} onClick={onToggleFrozen}>
1!
39
          {frozen ? (
1!
40
            <PlayIcon className="w-5 h-5" />
×
41
          ) : (
42
            <PauseIcon className="w-5 h-5" />
1✔
43
          )}
44
        </Button>
1✔
45
      </Tooltip>
1✔
46
      <Tooltip label="Zoom in">
1✔
47
        <Button id="tour-tool-zoomin" size="sm" variant="neutral" aria-label="Zoom in" onClick={onZoomIn}>
1✔
48
          <MagnifyingGlassPlusIcon className="w-5 h-5" />
1✔
49
        </Button>
1✔
50
      </Tooltip>
1✔
51
      <Tooltip label="Zoom out">
1✔
52
        <Button id="tour-tool-zoomout" size="sm" variant="neutral" aria-label="Zoom out" onClick={onZoomOut}>
1✔
53
          <MagnifyingGlassMinusIcon className="w-5 h-5" />
1✔
54
        </Button>
1✔
55
      </Tooltip>
1✔
56
      
57
      {/* CSV Export Button with Dropdown */}
58
      <div className="relative" ref={exportMenuRef}>
1✔
59
        <Tooltip label="Export CSV">
1✔
60
          <Button 
1✔
61
            id="tour-tool-export"
1✔
62
            size="sm" 
1✔
63
            variant="neutral" 
1✔
64
            aria-label="Export CSV" 
1✔
65
            disabled={!hasData}
1✔
66
            onClick={() => setShowExportMenu(!showExportMenu)}
1✔
67
          >
68
            <ArrowDownTrayIcon className="w-5 h-5" />
1✔
69
          </Button>
1✔
70
        </Tooltip>
1✔
71
        
72
        {showExportMenu && (
1!
73
          <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">
×
74
            <div className="p-2 space-y-1">
×
75
              <button
×
76
                onClick={() => {
×
77
                  onExportCsv({ scope: 'visible', includeTimestamps: true, timeFormat: 'iso' })
×
78
                  setShowExportMenu(false)
×
79
                }}
×
80
                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
81
              >
×
82
                📊 Export Visible Data
83
              </button>
×
84
              <button
×
85
                onClick={() => {
×
86
                  onExportCsv({ scope: 'all', includeTimestamps: true, timeFormat: 'iso' })
×
87
                  setShowExportMenu(false)
×
88
                }}
×
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
                📈 Export All Data
92
              </button>
×
93
              <div className="border-t border-gray-200 dark:border-neutral-700 my-1" />
×
94
              <button
×
95
                onClick={() => {
×
96
                  onExportCsv({ scope: 'visible', includeTimestamps: true, timeFormat: 'relative' })
×
97
                  setShowExportMenu(false)
×
98
                }}
×
99
                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
100
              >
×
101
                ⏱️ Visible (Relative Time)
102
              </button>
×
103
              <button
×
104
                onClick={() => {
×
105
                  onExportCsv({ scope: 'all', includeTimestamps: true, timeFormat: 'relative' })
×
106
                  setShowExportMenu(false)
×
107
                }}
×
108
                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
109
              >
×
110
                ⏱️ All Data (Relative Time)
111
              </button>
×
112
            </div>
×
UNCOV
113
          </div>
×
114
        )}
115
      </div>
1✔
116
      
117
      <Tooltip label="Save PNG">
1✔
118
        <Button id="tour-tool-savepng" size="sm" variant="neutral" aria-label="Save PNG" onClick={onSavePng}>
1✔
119
          <CameraIcon className="w-5 h-5" />
1✔
120
        </Button>
1✔
121
      </Tooltip>
1✔
122

123
      <Tooltip label="Settings">
1✔
124
        <Button id="tour-tool-settings" size="sm" variant="neutral" aria-label="Settings" onClick={onShowSettings}>
1✔
125
          <CogIcon className="w-5 h-5" />
1✔
126
        </Button>
1✔
127
      </Tooltip>
1✔
128
    </div>
1✔
129
  )
130
})
1✔
131

132
export default PlotToolsOverlay
1✔
133

134

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