diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 03f560d6..9d1178b6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,12 @@ on: push: branches: [main] pull_request: - branches: [main] + # Long-lived INTEGRATION branches too, not just main. `feat/0.3.0` collects + # a whole release, and with `[main]` alone a PR into it ran no tests at all + # — #82 through #88 each landed a wave that way, and the matrix first saw + # them only when 0.3.0 itself went at main. A PR whose checks are green + # because nothing ran is worse than a red one. + branches: [main, "feat/[0-9]*"] concurrency: group: build-${{ github.ref }} diff --git a/electron/src/main/index.ts b/electron/src/main/index.ts index f967d084..a1f953e3 100644 --- a/electron/src/main/index.ts +++ b/electron/src/main/index.ts @@ -476,10 +476,11 @@ function buildMenu(): void { const result = await dialog.showOpenDialog(win!, { properties: ['openFile', 'multiSelections'], filters: [ - { name: 'EM Data', extensions: ['hspy', 'zspy', 'mrc', 'tif', 'tiff', 'de5'] }, + { name: 'EM Data', extensions: ['hspy', 'zspy', 'mrc', 'tif', 'tiff', 'de5', 'csb'] }, { name: 'HyperSpy', extensions: ['hspy', 'zspy'] }, { name: 'MRC', extensions: ['mrc'] }, { name: 'TIFF', extensions: ['tif', 'tiff'] }, + { name: 'DE CSB (.csb)', extensions: ['csb'] }, ], }) if (!result.canceled) { @@ -607,7 +608,7 @@ ipcMain.handle('spyde:open-file', async () => { const result = await dialog.showOpenDialog(win!, { properties: ['openFile', 'multiSelections'], filters: [ - { name: 'EM Data', extensions: ['hspy', 'zspy', 'mrc', 'tif', 'tiff', 'de5'] }, + { name: 'EM Data', extensions: ['hspy', 'zspy', 'mrc', 'tif', 'tiff', 'de5', 'csb'] }, ], }) if (!result.canceled) { diff --git a/electron/src/renderer/src/components/Dropdown.tsx b/electron/src/renderer/src/components/Dropdown.tsx index 526d72a2..55d93077 100644 --- a/electron/src/renderer/src/components/Dropdown.tsx +++ b/electron/src/renderer/src/components/Dropdown.tsx @@ -17,12 +17,26 @@ */ import React from 'react' -export function Dropdown({ value, options, onChange, testid, width }: { +export function Dropdown({ + value, options, onChange, testid, width, triggerText, bare, caretColor, +}: { value: T options: readonly { value: T; label: string }[] onChange: (v: T) => void testid: string width?: number | string + /** Override what the TRIGGER shows, while the menu keeps the full labels. + * For a dropdown attached to another control, where the selection is + * already displayed beside it and repeating the whole label would just be + * noise — pass '' for a bare caret. */ + triggerText?: string + /** Drop the trigger's own border and background so it can sit INSIDE + * another control as a caret. Buttons cannot nest, so a split control has + * to be a styled wrapper around two siblings — this makes the second one + * disappear into it. `caretColor` keeps the caret legible on whatever the + * wrapper's background is. */ + bare?: boolean + caretColor?: string }) { const [open, setOpen] = React.useState(false) // Auto drop-UP when the menu would clip the bottom of the window (e.g. the @@ -60,11 +74,21 @@ export function Dropdown({ value, options, onChange, testid, w {open && (
= { textAlign: 'left', }, triggerOpen: { borderColor: '#45475a', background: '#181825' }, + triggerBare: { + background: 'transparent', border: 'none', padding: '0 5px 0 2px', + width: 'auto', + }, triggerLabel: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }, caret: { fontSize: 9, color: '#6c7086', flex: '0 0 auto' }, // The panel is a copy of MenuBar's `styles.dropdown` / `styles.item`. diff --git a/electron/src/renderer/src/components/PlotControlDock.tsx b/electron/src/renderer/src/components/PlotControlDock.tsx index 798bf134..d4a1a480 100644 --- a/electron/src/renderer/src/components/PlotControlDock.tsx +++ b/electron/src/renderer/src/components/PlotControlDock.tsx @@ -629,14 +629,54 @@ export function PlotControlDock() { style={{ ...styles.selectorDot, background: s.color ?? '#00e676' }} title={s.title ?? 'Navigator'} /> - + {/* Point is a SPLIT control: the button picks the mode, and a + caret at its right edge opens the frame-width menu. It has to + be a styled wrapper around two siblings rather than a caret + nested in the button, because buttons cannot contain buttons + — so the wrapper carries the active/inactive look and both + children are transparent inside it. */} +
+ + {s.sumFrames != null && s.mode === 'crosshair' && ( + sendAction('set_selector_sum', + { frames: Number(v), selector_id: s.selectorId }, s.windowId)} + /> + )} +