diff --git a/README.md b/README.md index b0986be..c183784 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,7 @@ This template should help get you started developing with Tauri, React and Types - Check render to improve perfoormances - Dont draw geq/peq on gain if bypass - Diplay windows on reset geq, peq +- Preset change dont reset +- Disbale slider on peq when filter dont use it - Check end to end behavioour \ No newline at end of file diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 1cae7cf..f5b4c8a 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3300,9 +3300,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tauri" -version = "2.11.5" +version = "2.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +checksum = "6fa5bacdb9bbad5954af3d1bd6cf6ae9192cab1b2e270f4a07f904610b9e85f4" dependencies = [ "anyhow", "bytes", @@ -3429,9 +3429,9 @@ dependencies = [ [[package]] name = "tauri-plugin-opener" -version = "2.5.4" +version = "2.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17e1bea14edce6b793a04e2417e3fd924b9bc4faae83cdee7d714156cceeed29" +checksum = "60d60366174b745b4ef5824b8bbc1c457fd08f0ce101ff643c0a49181a9f4e91" dependencies = [ "dunce", "glob", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 0c64bce..50b526b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,8 +18,8 @@ crate-type = ["staticlib", "cdylib", "rlib"] tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = [] } -tauri-plugin-opener = "2" +tauri = { version = "2.11.6", features = [] } +tauri-plugin-opener = "2.5.5" serde = { version = "1", features = ["derive"] } serde_json = "1" dsp_thomann = { path = "../../dsp-thomann", features = ["serialization"] } diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png index 6be5e50..59f93e0 100644 Binary files a/src-tauri/icons/128x128.png and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png index e81bece..21069bf 100644 Binary files a/src-tauri/icons/128x128@2x.png and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png index a437dd5..9302d47 100644 Binary files a/src-tauri/icons/32x32.png and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png new file mode 100644 index 0000000..1b6e193 Binary files /dev/null and b/src-tauri/icons/64x64.png differ diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png index 0ca4f27..581fd32 100644 Binary files a/src-tauri/icons/Square107x107Logo.png and b/src-tauri/icons/Square107x107Logo.png differ diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png index b81f820..8b06ea4 100644 Binary files a/src-tauri/icons/Square142x142Logo.png and b/src-tauri/icons/Square142x142Logo.png differ diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png index 624c7bf..26e17c0 100644 Binary files a/src-tauri/icons/Square150x150Logo.png and b/src-tauri/icons/Square150x150Logo.png differ diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png index c021d2b..3b58bd9 100644 Binary files a/src-tauri/icons/Square284x284Logo.png and b/src-tauri/icons/Square284x284Logo.png differ diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png index 6219700..6813d2b 100644 Binary files a/src-tauri/icons/Square30x30Logo.png and b/src-tauri/icons/Square30x30Logo.png differ diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png index f9bc048..6105f3a 100644 Binary files a/src-tauri/icons/Square310x310Logo.png and b/src-tauri/icons/Square310x310Logo.png differ diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png index d5fbfb2..52b2c59 100644 Binary files a/src-tauri/icons/Square44x44Logo.png and b/src-tauri/icons/Square44x44Logo.png differ diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png index 63440d7..835bac7 100644 Binary files a/src-tauri/icons/Square71x71Logo.png and b/src-tauri/icons/Square71x71Logo.png differ diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png index f3f705a..ac20a83 100644 Binary files a/src-tauri/icons/Square89x89Logo.png and b/src-tauri/icons/Square89x89Logo.png differ diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png index 4556388..8317bcd 100644 Binary files a/src-tauri/icons/StoreLogo.png and b/src-tauri/icons/StoreLogo.png differ diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a6ca024 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..a709f08 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..a7f6076 Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..a9eeedc Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..83bc970 Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..79c88fa Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..7ba9360 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..7ac82c1 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..0bbbb4e Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..acc1ba1 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..590330b Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..9129098 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..a690e83 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..cb81363 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..c9082b5 Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns index 12a5bce..16db073 100644 Binary files a/src-tauri/icons/icon.icns and b/src-tauri/icons/icon.icns differ diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico index b3636e4..8722516 100644 Binary files a/src-tauri/icons/icon.ico and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png index e1cd261..1302358 100644 Binary files a/src-tauri/icons/icon.png and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/icon.svg b/src-tauri/icons/icon.svg new file mode 100644 index 0000000..241232c --- /dev/null +++ b/src-tauri/icons/icon.svg @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..f007a3a Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..7fe469d Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..7fe469d Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..925363f Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..042a5f0 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..19138e8 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..19138e8 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..ff4a4e3 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..7fe469d Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..0f012fe Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..0f012fe Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..2c01315 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..721ccc5 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..2c01315 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..68654b2 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..220cc8b Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..4c06be0 Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..6bd2ceb Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 0612537..5a9f843 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -12,7 +12,7 @@ "app": { "windows": [ { - "title": "thomann-dsp-app", + "title": "Thomann DSP Controller", "width": 1600, "height": 950, "minWidth": 1200, @@ -27,11 +27,11 @@ "active": true, "targets": "all", "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" + "./icons/32x32.png", + "./icons/128x128.png", + "./icons/128x128@2x.png", + "./icons/icon.icns", + "./icons/icon.ico" ] } } diff --git a/src/components/ChoiceSelect/ChoiceSelect.css b/src/components/ChoiceSelect/ChoiceSelect.css index 97d5524..cd2e8dc 100644 --- a/src/components/ChoiceSelect/ChoiceSelect.css +++ b/src/components/ChoiceSelect/ChoiceSelect.css @@ -1,65 +1,125 @@ +/* ChoiceSelect.css */ .choice-select { position: relative; display: flex; - align-items: stretch; + + color: var(--accent-brand); } .choice-select[data-disabled] { opacity: 0.5; } -.choice-select__input { +.choice-select__trigger { flex: 1 1 auto; + width: 100%; + height: 100%; - height: var(--card-control-height); - padding: 0 var(--space-6) 0 var(--space-3); + display: flex; + align-items: center; + justify-content: space-between; + + padding: 0; + padding-inline: 5%; background: transparent; - color: var(--text-primary); border: var(--border-width) solid var(--border-hairline); border-radius: var(--radius-sm); font-family: var(--font-ui); - font-size: var(--font-sm); + font-size: 90%; + + color: inherit; - appearance: none; - -webkit-appearance: none; - -moz-appearance: none; - - outline: none; cursor: pointer; - transition: border-color var(--dur-base) var(--ease-standard); } -.choice-select__input:focus { +.choice-select__trigger:hover, +.choice-select__trigger--open { border-color: var(--accent-brand); } -.choice-select__input:disabled { +.choice-select__trigger:disabled { cursor: not-allowed; } +.choice-select__label { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .choice-select__chevron { - position: absolute; - top: 0; - right: 2%; - bottom: 0; - - width: 4cqh; - padding-top: 3%; - padding-bottom: 3%; - + flex-shrink: 0; display: flex; align-items: center; justify-content: center; - pointer-events: none; + width: 4cqh; + aspect-ratio: 1; + color: var(--accent-brand); + transition: transform var(--dur-base) var(--ease-standard); +} + +.choice-select__trigger--open .choice-select__chevron { + transform: rotate(180deg); } .choice-select__chevron svg { - width: 75%; + width: 50%; height: auto; display: block; } + +.choice-select__option { + padding: 1%; + border-radius: var(--radius-sm); + font-size: 100%; + color: var(--text-primary); + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.choice-select__option--highlight { + background: var(--bg-raised); +} + +.choice-select__option--selected { + color: var(--accent-brand); + font-weight: var(--font-weight-semibold); +} + +.choice-select__listbox { + position: absolute; + left: 0; + right: 0; + z-index: 40; + + top: calc(100% + var(--space-1)); + + margin: 0; + padding: var(--space-1); + list-style: none; + + background: var(--bg-panel); + border: var(--border-width) solid var(--border-hairline); + border-radius: var(--radius-sm); + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); + + max-height: 240px; + overflow-y: auto; +} + +.choice-select__listbox--up { + top: auto; + bottom: calc(100% + var(--space-1)); +} + +.choice-select__listbox--portal { + position: fixed; + z-index: 1000; /* clear of any local stacking contexts in the panel */ +} \ No newline at end of file diff --git a/src/components/ChoiceSelect/ChoiceSelect.tsx b/src/components/ChoiceSelect/ChoiceSelect.tsx index 4c2ca43..374ffea 100644 --- a/src/components/ChoiceSelect/ChoiceSelect.tsx +++ b/src/components/ChoiceSelect/ChoiceSelect.tsx @@ -1,4 +1,6 @@ // ChoiceSelect.tsx +import { useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; import { TriangleDownIcon } from '../../assets/icons/Triangle'; import './ChoiceSelect.css'; @@ -13,6 +15,11 @@ type ChoiceSelectProps = { style?: React.CSSProperties; }; +const LISTBOX_MAX_HEIGHT = 240; // keep in sync with .choice-select__listbox max-height +const LISTBOX_GAP = 4; // px, ~= var(--space-1) + +type Coords = { top: number; left: number; width: number; fontSize: number }; + function ChoiceSelect({ value, options, @@ -23,52 +30,203 @@ function ChoiceSelect({ className = '', style, }: ChoiceSelectProps) { + const [open, setOpen] = useState(false); + const [highlight, setHighlight] = useState(0); + const [dropUp, setDropUp] = useState(false); + const [coords, setCoords] = useState(null); + + const rootRef = useRef(null); + const triggerRef = useRef(null); + const listboxRef = useRef(null); + const suppressNextClick = useRef(false); + + useEffect(() => { + function handleMouseDown(e: MouseEvent) { + const target = e.target as Node; + const clickedOutside = + rootRef.current && + !rootRef.current.contains(target) && + !listboxRef.current?.contains(target); + + if (clickedOutside && open) { + setOpen(false); + suppressNextClick.current = true; + } + } + + function handleClickCapture(e: MouseEvent) { + if (suppressNextClick.current) { + e.preventDefault(); + e.stopPropagation(); + suppressNextClick.current = false; + } + } + + document.addEventListener('mousedown', handleMouseDown); + document.addEventListener('click', handleClickCapture, true); // capture phase + return () => { + document.removeEventListener('mousedown', handleMouseDown); + document.removeEventListener('click', handleClickCapture, true); + }; +}, [open]); + + // Re-check placement/position on scroll/resize while open — the trigger's + // position can change (e.g. inside a scrollable panel), and since the + // listbox is portaled it won't move with it automatically. + useEffect(() => { + if (!open) return; + const recalc = () => updatePlacement(); + window.addEventListener('scroll', recalc, true); + window.addEventListener('resize', recalc); + return () => { + window.removeEventListener('scroll', recalc, true); + window.removeEventListener('resize', recalc); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [open]); + const step = (direction: 1 | -1) => { const currentIndex = options.indexOf(value); - if (currentIndex === -1 || options.length === 0) return; - const nextIndex = (currentIndex + direction + options.length) % options.length; - onChange(options[nextIndex]); }; + function updatePlacement() { + const el = triggerRef.current; + if (!el) return; + + const rect = el.getBoundingClientRect(); + const spaceBelow = window.innerHeight - rect.bottom; + const spaceAbove = rect.top; + const needsFlip = spaceBelow < LISTBOX_MAX_HEIGHT && spaceAbove > spaceBelow; + setDropUp(needsFlip); + + // Capture the trigger's resolved font-size (its cqh has already resolved + // correctly here, since it's still inside the real container tree) and + // carry it as a plain px value into the portal, where cqh would break. + const fontSize = parseFloat(getComputedStyle(el).fontSize); + + setCoords({ + top: needsFlip ? rect.top - LISTBOX_GAP : rect.bottom + LISTBOX_GAP, + left: rect.left, + width: rect.width, + fontSize, + }); +} + + function openList() { + if (disabled) return; + updatePlacement(); + setOpen(true); + const idx = options.indexOf(value); + setHighlight(idx >= 0 ? idx : 0); + } + + function pick(opt: T) { + if (opt !== value) onChange(opt); + setOpen(false); + triggerRef.current?.focus(); + } + + function handleKeyDown(e: React.KeyboardEvent) { + if (disabled) return; + + if (!open) { + if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') { + e.preventDefault(); + openList(); + } + return; + } + + if (e.key === 'ArrowDown' || (e.key === 'Tab' && !e.shiftKey)) { + e.preventDefault(); + setHighlight((h) => Math.min(h + 1, options.length - 1)); + } else if (e.key === 'ArrowUp' || (e.key === 'Tab' && e.shiftKey)) { + e.preventDefault(); + setHighlight((h) => Math.max(h - 1, 0)); + } else if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + pick(options[highlight]); + } else if (e.key === 'Escape') { + e.preventDefault(); + setOpen(false); + triggerRef.current?.focus(); + } + } + return ( -
- + {formatLabel(value)} + + - + {open && + coords && + createPortal( +
    + {options.map((option, i) => ( +
  • setHighlight(i)} + onClick={() => pick(option)} + > + {formatLabel(option)} +
  • + ))} +
, + document.body + )}
); } -export default ChoiceSelect; +export default ChoiceSelect; \ No newline at end of file diff --git a/src/features/dsp408/components/InputSection/ChannelSection.css b/src/features/dsp408/components/ChannelSection/ChannelSection.css similarity index 100% rename from src/features/dsp408/components/InputSection/ChannelSection.css rename to src/features/dsp408/components/ChannelSection/ChannelSection.css diff --git a/src/features/dsp408/components/InputSection/ChannelSection.tsx b/src/features/dsp408/components/ChannelSection/ChannelSection.tsx similarity index 100% rename from src/features/dsp408/components/InputSection/ChannelSection.tsx rename to src/features/dsp408/components/ChannelSection/ChannelSection.tsx diff --git a/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx b/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx index e3c7bfe..98bcd8d 100644 --- a/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx +++ b/src/features/dsp408/components/DSP408Panel/DSP408Panel.tsx @@ -11,7 +11,7 @@ import LoadingOverlay from '../../../../components/LoadingOverlay/LoadingOverlay import { InputChannel, OutputChannel } from '../../types'; import MatrixSection from '../Matrixsection/Matrixsection'; import GeqSection from '../GeqSection/GeqSection'; -import ChannelSection from '../InputSection/ChannelSection'; +import ChannelSection from '../ChannelSection/ChannelSection'; import { DSP408Api } from '../../dsp408'; import { DSP408 } from '../../../../types'; import { SidebarId } from '../../constants'; diff --git a/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx b/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx index 64c28e3..b60b42a 100644 --- a/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx +++ b/src/features/dsp408/components/MiscellaneousSection/MiscellaneousSection.tsx @@ -112,6 +112,7 @@ function MiscellaneousPanel({ value={inputSource.type} options={INPUT_TYPES} onChange={handleInputTypeChange} + style={{color: 'var(--font-primary)'}} /> @@ -131,6 +132,7 @@ function MiscellaneousPanel({ return `${frequency.slice(1)} Hz`; }} onChange={handleFrequencyChange} + style={{color: 'var(--font-primary)'}} /> )} diff --git a/src/features/dsp408/dsp408.ts b/src/features/dsp408/dsp408.ts index 7dfe6c1..58bd80d 100644 --- a/src/features/dsp408/dsp408.ts +++ b/src/features/dsp408/dsp408.ts @@ -720,6 +720,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify(`Preset stored in ${dsp.state.presets.names[presetIndex]}`, 'success', dsp.name); + return true; } catch (err) { notify(`Store configuration failed: ${String(err)}`, 'error', dsp.name); @@ -746,6 +748,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify('Factory reset completed', 'success', dsp.name); + return true; } catch (err) { notify(`Factory reset failed: ${String(err)}`, 'error', dsp.name); @@ -769,6 +773,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify('Channels copied successfully', 'success', dsp.name) + return true; } catch (err) { notify(`Channel copy failed: ${String(err)}`, 'error', dsp.name); @@ -791,6 +797,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify('Input source updated', 'success', dsp.name); + return true; } catch (err) { notify(`Input source update failed: ${String(err)}`, 'error', dsp.name); @@ -814,6 +822,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify('Channels linked successfully', 'success', dsp.name); + return true; } catch (err) { notify(`Channel linking failed: ${String(err)}`, 'error', dsp.name); @@ -837,6 +847,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify('Channel name updated', 'success', dsp.name); + return true; } catch (err) { notify(`Channel name update failed: ${String(err)}`, 'error', dsp.name); @@ -859,6 +871,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify('Device unlocked', 'success', dsp.name); + return true; } catch (err) { notify(`Unlock failed: ${String(err)}`, 'error', dsp.name); @@ -881,6 +895,8 @@ export function forDSP408(dsp: DSP408, setDsps: SetDsps, notify: NotifyFn) { await updateDSPState(); + notify('Device locked', 'success', dsp.name); + return true; } catch (err) { notify(`Lock failed: ${String(err)}`, 'error', dsp.name); diff --git a/src/pages/AddDspPage/AddDspPage.css b/src/pages/AddDspPage/AddDspPage.css index 0715d0f..929c2a8 100644 --- a/src/pages/AddDspPage/AddDspPage.css +++ b/src/pages/AddDspPage/AddDspPage.css @@ -155,6 +155,11 @@ height: var(--card-control-height); } +.add-dsp-form__selector { + color: var(--text-primary); + height: 100%; +} + .add-dsp-form__field .choice-stepper__input { border: none; background: transparent; diff --git a/src/pages/AddDspPage/AddDspPage.tsx b/src/pages/AddDspPage/AddDspPage.tsx index fc9a943..c715dce 100644 --- a/src/pages/AddDspPage/AddDspPage.tsx +++ b/src/pages/AddDspPage/AddDspPage.tsx @@ -107,7 +107,7 @@ function AddDSPForm({