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

atomic14 / web-serial-plotter / 17385316705

01 Sep 2025 06:55PM UTC coverage: 62.711% (-0.4%) from 63.077%
17385316705

push

github

cgreening
Adds a side panel for settings

410 of 522 branches covered (78.54%)

Branch coverage included in aggregate %.

57 of 113 new or added lines in 6 files covered. (50.44%)

53 existing lines in 3 files now uncovered.

2000 of 3321 relevant lines covered (60.22%)

35.47 hits per line

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

53.27
/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
      }
×
UNCOV
28
    }
×
29

30
    if (showExportMenu) {
1!
31
      document.addEventListener('mousedown', handleClickOutside)
×
32
      return () => document.removeEventListener('mousedown', handleClickOutside)
×
UNCOV
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 size="sm" variant="neutral" aria-label={frozen ? 'Play' : 'Pause'} onClick={onToggleFrozen}>
1!
39
          {frozen ? (
1!
NEW
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 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 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
            size="sm" 
1✔
62
            variant="neutral" 
1✔
63
            aria-label="Export CSV" 
1✔
64
            disabled={!hasData}
1✔
65
            onClick={() => setShowExportMenu(!showExportMenu)}
1✔
66
          >
67
            <ArrowDownTrayIcon className="w-5 h-5" />
1✔
68
          </Button>
1✔
69
        </Tooltip>
1✔
70
        
71
        {showExportMenu && (
1!
72
          <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">
×
73
            <div className="p-2 space-y-1">
×
74
              <button
×
75
                onClick={() => {
×
76
                  onExportCsv({ scope: 'visible', includeTimestamps: true, timeFormat: 'iso' })
×
77
                  setShowExportMenu(false)
×
78
                }}
×
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 Visible Data
82
              </button>
×
83
              <button
×
84
                onClick={() => {
×
85
                  onExportCsv({ scope: 'all', includeTimestamps: true, timeFormat: 'iso' })
×
86
                  setShowExportMenu(false)
×
87
                }}
×
88
                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
89
              >
×
90
                📈 Export All Data
91
              </button>
×
92
              <div className="border-t border-gray-200 dark:border-neutral-700 my-1" />
×
93
              <button
×
94
                onClick={() => {
×
95
                  onExportCsv({ scope: 'visible', includeTimestamps: true, timeFormat: 'relative' })
×
96
                  setShowExportMenu(false)
×
97
                }}
×
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
                ⏱️ Visible (Relative Time)
101
              </button>
×
102
              <button
×
103
                onClick={() => {
×
104
                  onExportCsv({ scope: 'all', includeTimestamps: true, timeFormat: 'relative' })
×
105
                  setShowExportMenu(false)
×
106
                }}
×
107
                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
108
              >
×
109
                ⏱️ All Data (Relative Time)
110
              </button>
×
111
            </div>
×
UNCOV
112
          </div>
×
113
        )}
114
      </div>
1✔
115
      
116
      <Tooltip label="Save PNG">
1✔
117
        <Button size="sm" variant="neutral" aria-label="Save PNG" onClick={onSavePng}>
1✔
118
          <CameraIcon className="w-5 h-5" />
1✔
119
        </Button>
1✔
120
      </Tooltip>
1✔
121

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

131
export default PlotToolsOverlay
1✔
132

133

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