Hide menu submenus when none of their children are visible
Static submenus and links now inherit visibility from their descendants, so hardware sections like Touchpad Haptics disappear on machines without the hardware instead of opening empty. Laptop Display and Mirror Display are guarded by omarchy-hw-laptop. The guard evaluator brace-wraps every when/checked condition before silencing it, so conditions in the jsonc no longer need their own >/dev/null plumbing, and parents no longer need to repeat their children's guards. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9d3cb70ee7
commit
2bf4f4a9fb
@@ -14,13 +14,13 @@
|
|||||||
// checked shell condition; append ✓ when it succeeds
|
// checked shell condition; append ✓ when it succeeds
|
||||||
|
|
||||||
// Root Menu
|
// Root Menu
|
||||||
"apps": {"icon":"","label":"Apps","aliases":["app","applications"],"keywords":"","action":"omarchy-shell shell summon omarchy.launcher"},
|
"apps": {"icon":"","label":"Apps","aliases":["app","applications"],"action":"omarchy-shell shell summon omarchy.launcher"},
|
||||||
"learn": {"icon":"","label":"Learn","keywords":"manual docs help"},
|
"learn": {"icon":"","label":"Learn","keywords":"manual docs help"},
|
||||||
"trigger": {"icon":"","label":"Trigger","keywords":""},
|
"trigger": {"icon":"","label":"Trigger"},
|
||||||
"style": {"icon":"","label":"Style","keywords":""},
|
"style": {"icon":"","label":"Style"},
|
||||||
"setup": {"icon":"","label":"Setup","aliases":["settings"],"keywords":""},
|
"setup": {"icon":"","label":"Setup","aliases":["settings"]},
|
||||||
"install": {"icon":"","label":"Install","keywords":""},
|
"install": {"icon":"","label":"Install"},
|
||||||
"remove": {"icon":"","label":"Remove","aliases":["uninstall"],"keywords":""},
|
"remove": {"icon":"","label":"Remove","aliases":["uninstall"]},
|
||||||
"update": {"icon":"","label":"Update","aliases":["restart","refresh"],"keywords":"upgrade channel firmware timezone"},
|
"update": {"icon":"","label":"Update","aliases":["restart","refresh"],"keywords":"upgrade channel firmware timezone"},
|
||||||
"about": {"icon":"","label":"About","keywords":"omarchy system information","action":"omarchy-launch-about"},
|
"about": {"icon":"","label":"About","keywords":"omarchy system information","action":"omarchy-launch-about"},
|
||||||
"system": {"icon":"","label":"System","aliases":["power-menu"],"keywords":"lock logout reboot shutdown suspend hibernate"},
|
"system": {"icon":"","label":"System","aliases":["power-menu"],"keywords":"lock logout reboot shutdown suspend hibernate"},
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"trigger.reminder": {"icon":"","label":"Reminder","aliases":["reminder"],"keywords":"timer notification remind"},
|
"trigger.reminder": {"icon":"","label":"Reminder","aliases":["reminder"],"keywords":"timer notification remind"},
|
||||||
"trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"],"keywords":"color text extraction"},
|
"trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"],"keywords":"color text extraction"},
|
||||||
"trigger.capture.screenshot": {"icon":"","label":"Screenshot","keywords":"picture","action":"omarchy-capture-screenshot"},
|
"trigger.capture.screenshot": {"icon":"","label":"Screenshot","keywords":"picture","action":"omarchy-capture-screenshot"},
|
||||||
"trigger.capture.screenrecord.stop": {"icon":"","label":"Stop Screenrecording","keywords":"recording video","when":"pgrep -f '^gpu-screen-recorder' >/dev/null","action":"omarchy-capture-screenrecording --stop-recording"},
|
"trigger.capture.screenrecord.stop": {"icon":"","label":"Stop Screenrecording","keywords":"recording video","when":"pgrep -f '^gpu-screen-recorder'","action":"omarchy-capture-screenrecording --stop-recording"},
|
||||||
"trigger.capture.screenrecord": {"icon":"","label":"Screenrecord","keywords":"record screen video"},
|
"trigger.capture.screenrecord": {"icon":"","label":"Screenrecord","keywords":"record screen video"},
|
||||||
"trigger.capture.text": {"icon":"","label":"Text","keywords":"ocr text extraction","action":"omarchy-capture-text"},
|
"trigger.capture.text": {"icon":"","label":"Text","keywords":"ocr text extraction","action":"omarchy-capture-text"},
|
||||||
"trigger.capture.color": {"icon":"","label":"Color","keywords":"picker hyprpicker","action":"pkill hyprpicker || hyprpicker -a"},
|
"trigger.capture.color": {"icon":"","label":"Color","keywords":"picker hyprpicker","action":"pkill hyprpicker || hyprpicker -a"},
|
||||||
@@ -59,15 +59,15 @@
|
|||||||
"trigger.share": {"icon":"","label":"Share","aliases":["share"],"keywords":"clipboard file folder localsend"},
|
"trigger.share": {"icon":"","label":"Share","aliases":["share"],"keywords":"clipboard file folder localsend"},
|
||||||
"trigger.toggle": {"icon":"","label":"Toggle","aliases":["toggle","toggles"],"keywords":"screensaver nightlight idle notifications bar quickshell"},
|
"trigger.toggle": {"icon":"","label":"Toggle","aliases":["toggle","toggles"],"keywords":"screensaver nightlight idle notifications bar quickshell"},
|
||||||
"trigger.hardware": {"icon":"","label":"Hardware","aliases":["hardware","hw"],"keywords":"monitor touchpad touchscreen gpu"},
|
"trigger.hardware": {"icon":"","label":"Hardware","aliases":["hardware","hw"],"keywords":"monitor touchpad touchscreen gpu"},
|
||||||
"trigger.hardware.laptop-display": {"icon":"","label":"Laptop Display","keywords":"monitor","action":"omarchy-hyprland-monitor-internal toggle"},
|
"trigger.hardware.laptop-display": {"icon":"","label":"Laptop Display","keywords":"monitor","when":"omarchy-hw-laptop","action":"omarchy-hyprland-monitor-internal toggle"},
|
||||||
"trigger.hardware.mirror-display": {"icon":"","label":"Mirror Display","keywords":"monitor","action":"omarchy-hyprland-monitor-internal-mirror toggle"},
|
"trigger.hardware.mirror-display": {"icon":"","label":"Mirror Display","keywords":"monitor","when":"omarchy-hw-laptop","action":"omarchy-hyprland-monitor-internal-mirror toggle"},
|
||||||
"trigger.hardware.hybrid-gpu": {"icon":"","label":"Hybrid GPU","keywords":"graphics","when":"omarchy-hw-hybrid-gpu >/dev/null","action":"omarchy-launch-floating-terminal-with-presentation omarchy-toggle-hybrid-gpu"},
|
"trigger.hardware.hybrid-gpu": {"icon":"","label":"Hybrid GPU","keywords":"graphics","when":"omarchy-hw-hybrid-gpu","action":"omarchy-launch-floating-terminal-with-presentation omarchy-toggle-hybrid-gpu"},
|
||||||
"trigger.hardware.touchpad": {"icon":"","label":"Touchpad","keywords":"trackpad","when":"omarchy-hw-touchpad >/dev/null","action":"omarchy-toggle-touchpad"},
|
"trigger.hardware.touchpad": {"icon":"","label":"Touchpad","keywords":"trackpad","when":"omarchy-hw-touchpad","action":"omarchy-toggle-touchpad"},
|
||||||
"trigger.hardware.touchpad-haptics": {"icon":"","label":"Touchpad Haptics","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics"},
|
"trigger.hardware.touchpad-haptics": {"icon":"","label":"Touchpad Haptics"},
|
||||||
"trigger.hardware.touchpad-haptics.low": {"icon":"","label":"low","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"low\" ]]","action":"dell-xps-touchpad-haptics set low"},
|
"trigger.hardware.touchpad-haptics.low": {"icon":"","label":"low","when":"omarchy-hw-dell-xps-haptic-touchpad && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"low\" ]]","action":"dell-xps-touchpad-haptics set low"},
|
||||||
"trigger.hardware.touchpad-haptics.mid": {"icon":"","label":"mid","keywords":"medium","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"mid\" ]]","action":"dell-xps-touchpad-haptics set mid"},
|
"trigger.hardware.touchpad-haptics.mid": {"icon":"","label":"mid","keywords":"medium","when":"omarchy-hw-dell-xps-haptic-touchpad && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"mid\" ]]","action":"dell-xps-touchpad-haptics set mid"},
|
||||||
"trigger.hardware.touchpad-haptics.high": {"icon":"","label":"high","when":"omarchy-hw-dell-xps-haptic-touchpad >/dev/null && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"high\" ]]","action":"dell-xps-touchpad-haptics set high"},
|
"trigger.hardware.touchpad-haptics.high": {"icon":"","label":"high","when":"omarchy-hw-dell-xps-haptic-touchpad && omarchy-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"high\" ]]","action":"dell-xps-touchpad-haptics set high"},
|
||||||
"trigger.hardware.touchscreen": {"icon":"","label":"Touchscreen","keywords":"tablet","when":"omarchy-hw-touchscreen >/dev/null","action":"omarchy-toggle-touchscreen"},
|
"trigger.hardware.touchscreen": {"icon":"","label":"Touchscreen","keywords":"tablet","when":"omarchy-hw-touchscreen","action":"omarchy-toggle-touchscreen"},
|
||||||
"trigger.reminder.set": {"icon":"","label":"Set one","aliases":["reminder-set","remind"],"keywords":"timer","action":"omarchy-reminder -i"},
|
"trigger.reminder.set": {"icon":"","label":"Set one","aliases":["reminder-set","remind"],"keywords":"timer","action":"omarchy-reminder -i"},
|
||||||
"trigger.reminder.show": {"icon":"","label":"Show all","keywords":"list","action":"omarchy-reminder show"},
|
"trigger.reminder.show": {"icon":"","label":"Show all","keywords":"list","action":"omarchy-reminder show"},
|
||||||
"trigger.reminder.clear": {"icon":"","label":"Clear all","keywords":"delete","action":"omarchy-reminder clear"},
|
"trigger.reminder.clear": {"icon":"","label":"Clear all","keywords":"delete","action":"omarchy-reminder clear"},
|
||||||
@@ -83,6 +83,7 @@
|
|||||||
"trigger.toggle.workspace-layout": {"icon":"","label":"Workspace Layout","action":"omarchy-hyprland-workspace-layout-toggle"},
|
"trigger.toggle.workspace-layout": {"icon":"","label":"Workspace Layout","action":"omarchy-hyprland-workspace-layout-toggle"},
|
||||||
"trigger.toggle.window-gaps": {"icon":"","label":"Window Gaps","action":"omarchy-hyprland-window-gaps-toggle"},
|
"trigger.toggle.window-gaps": {"icon":"","label":"Window Gaps","action":"omarchy-hyprland-window-gaps-toggle"},
|
||||||
"trigger.toggle.one-window-ratio": {"icon":"","label":"1-Window Ratio","keywords":"square","action":"omarchy-hyprland-window-single-square-aspect-toggle"},
|
"trigger.toggle.one-window-ratio": {"icon":"","label":"1-Window Ratio","keywords":"square","action":"omarchy-hyprland-window-single-square-aspect-toggle"},
|
||||||
|
|
||||||
// Style
|
// Style
|
||||||
"style.theme": {"icon":"","label":"Theme","aliases":["theme","themes"],"keywords":"colors","action":"theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""},
|
"style.theme": {"icon":"","label":"Theme","aliases":["theme","themes"],"keywords":"colors","action":"theme=$(omarchy-theme-switcher); [[ -n $theme ]] && omarchy-theme-set \"$theme\""},
|
||||||
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""},
|
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(omarchy-theme-bg-switcher); [[ -n $background ]] && omarchy-theme-bg-set \"$background\""},
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
"remove.browser": {"icon":"","label":"Browser","keywords":"uninstall"},
|
"remove.browser": {"icon":"","label":"Browser","keywords":"uninstall"},
|
||||||
"remove.dictation": {"icon":"","label":"Dictation","keywords":"uninstall voice","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
|
"remove.dictation": {"icon":"","label":"Dictation","keywords":"uninstall voice","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-remove"},
|
||||||
"remove.gaming": {"icon":"","label":"Gaming","keywords":"uninstall steam retroarch minecraft"},
|
"remove.gaming": {"icon":"","label":"Gaming","keywords":"uninstall steam retroarch minecraft"},
|
||||||
"remove.service": {"icon":"","label":"Services","keywords":"uninstall dropbox tailscale vpn","when":"omarchy-pkg-present dropbox || omarchy-pkg-present tailscale"},
|
"remove.service": {"icon":"","label":"Services","keywords":"uninstall dropbox tailscale vpn"},
|
||||||
"remove.windows": {"icon":"","label":"Windows","keywords":"uninstall vm","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm remove'"},
|
"remove.windows": {"icon":"","label":"Windows","keywords":"uninstall vm","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm remove'"},
|
||||||
"remove.preinstalls": {"icon":"","label":"Preinstalls","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-preinstalls"},
|
"remove.preinstalls": {"icon":"","label":"Preinstalls","action":"omarchy-launch-floating-terminal-with-presentation omarchy-remove-preinstalls"},
|
||||||
"remove.security": {"icon":"","label":"Security","keywords":"fingerprint fido2 ssh sshd"},
|
"remove.security": {"icon":"","label":"Security","keywords":"fingerprint fido2 ssh sshd"},
|
||||||
|
|||||||
@@ -347,13 +347,11 @@ Item {
|
|||||||
return MenuModel.childCount(root.items, root.itemOrder, id)
|
return MenuModel.childCount(root.items, root.itemOrder, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Items whose `when:` evaluated to false are hidden everywhere — nav,
|
// Guarded items are hidden when their `when:` evaluates false. Static
|
||||||
// drilldown, and search. Items with no `when:` are always visible.
|
// submenus are also hidden when none of their descendants are visible;
|
||||||
|
// provider-backed menus stay visible because their rows load on demand.
|
||||||
function isVisible(entry) {
|
function isVisible(entry) {
|
||||||
if (!entry) return false
|
return MenuModel.isVisible(root.items, root.itemOrder, root.whenResults, entry)
|
||||||
if (!entry.when) return true
|
|
||||||
var result = root.whenResults[entry.id]
|
|
||||||
return result === undefined ? true : result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label with the ✓ marker baked in when `checked:` evaluated truthy.
|
// Label with the ✓ marker baked in when `checked:` evaluated truthy.
|
||||||
@@ -752,8 +750,8 @@ Item {
|
|||||||
for (var i = 0; i < ids.length; i++) {
|
for (var i = 0; i < ids.length; i++) {
|
||||||
var entry = root.items[ids[i]]
|
var entry = root.items[ids[i]]
|
||||||
if (!entry) continue
|
if (!entry) continue
|
||||||
if (entry.when) script += "if " + entry.when + " >/dev/null 2>&1; then echo " + ids[i] + ":w:1; else echo " + ids[i] + ":w:0; fi\n"
|
if (entry.when) script += "if { " + entry.when + "; } >/dev/null 2>&1; then echo " + ids[i] + ":w:1; else echo " + ids[i] + ":w:0; fi\n"
|
||||||
if (entry.checked) script += "if " + entry.checked + " >/dev/null 2>&1; then echo " + ids[i] + ":c:1; else echo " + ids[i] + ":c:0; fi\n"
|
if (entry.checked) script += "if { " + entry.checked + "; } >/dev/null 2>&1; then echo " + ids[i] + ":c:1; else echo " + ids[i] + ":c:0; fi\n"
|
||||||
}
|
}
|
||||||
if (!script) {
|
if (!script) {
|
||||||
root.whenResults = ({})
|
root.whenResults = ({})
|
||||||
|
|||||||
@@ -173,6 +173,25 @@ function childCount(items, itemOrder, id) {
|
|||||||
return count
|
return count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isVisible(items, itemOrder, whenResults, entry, depth) {
|
||||||
|
if (!entry) return false
|
||||||
|
if (entry.when && whenResults && whenResults[entry.id] === false) return false
|
||||||
|
if (entry.kind !== "menu" && entry.kind !== "link") return true
|
||||||
|
if (entry.provider) return true
|
||||||
|
|
||||||
|
var guard = depth || 0
|
||||||
|
if (guard >= 32) return false
|
||||||
|
|
||||||
|
var target = entry.kind === "link" ? entry.target : entry.id
|
||||||
|
var order = Array.isArray(itemOrder) ? itemOrder : []
|
||||||
|
for (var i = 0; i < order.length; i++) {
|
||||||
|
var child = item(items, order[i])
|
||||||
|
if (child && child.parent === target && isVisible(items, itemOrder, whenResults, child, guard + 1)) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
function labelFor(entry, checkedResults) {
|
function labelFor(entry, checkedResults) {
|
||||||
if (!entry) return ""
|
if (!entry) return ""
|
||||||
if (entry.checked && checkedResults && checkedResults[entry.id]) return entry.label + " ✓"
|
if (entry.checked && checkedResults && checkedResults[entry.id]) return entry.label + " ✓"
|
||||||
@@ -282,6 +301,7 @@ if (typeof module !== "undefined") {
|
|||||||
parentPathFor: parentPathFor,
|
parentPathFor: parentPathFor,
|
||||||
isDescendantOf: isDescendantOf,
|
isDescendantOf: isDescendantOf,
|
||||||
childCount: childCount,
|
childCount: childCount,
|
||||||
|
isVisible: isVisible,
|
||||||
labelFor: labelFor,
|
labelFor: labelFor,
|
||||||
searchableToken: searchableToken,
|
searchableToken: searchableToken,
|
||||||
leafIdFor: leafIdFor,
|
leafIdFor: leafIdFor,
|
||||||
|
|||||||
@@ -64,6 +64,20 @@ assert(menu.isDescendantOf(merged.items, 'style.theme', 'style'), 'menu detects
|
|||||||
assertEqual(menu.childCount(merged.items, merged.itemOrder, 'style'), 1, 'menu counts children')
|
assertEqual(menu.childCount(merged.items, merged.itemOrder, 'style'), 1, 'menu counts children')
|
||||||
assertEqual(menu.labelFor({ id: 'style.theme', label: 'Theme', checked: 'cmd' }, { 'style.theme': true }), 'Theme ✓', 'menu appends checked marker')
|
assertEqual(menu.labelFor({ id: 'style.theme', label: 'Theme', checked: 'cmd' }, { 'style.theme': true }), 'Theme ✓', 'menu appends checked marker')
|
||||||
|
|
||||||
|
const visibilityItems = {
|
||||||
|
hardware: menu.normalizeItem('hardware', { label: 'Hardware' }),
|
||||||
|
laptop: menu.normalizeItem('hardware.laptop', { label: 'Laptop', when: 'is-laptop', action: 'toggle-laptop' }),
|
||||||
|
nested: menu.normalizeItem('nested', { label: 'Nested' }),
|
||||||
|
branch: menu.normalizeItem('nested.branch', { label: 'Branch' }),
|
||||||
|
leaf: menu.normalizeItem('nested.branch.leaf', { label: 'Leaf', when: 'has-leaf', action: 'run-leaf' }),
|
||||||
|
dynamic: menu.normalizeItem('dynamic', { label: 'Dynamic', provider: 'items' })
|
||||||
|
}
|
||||||
|
const visibilityOrder = Object.keys(visibilityItems)
|
||||||
|
assert(!menu.isVisible(visibilityItems, visibilityOrder, { 'hardware.laptop': false }, visibilityItems.hardware), 'menu hides a submenu with no visible children')
|
||||||
|
assert(menu.isVisible(visibilityItems, visibilityOrder, { 'hardware.laptop': true }, visibilityItems.hardware), 'menu shows a submenu with a visible child')
|
||||||
|
assert(!menu.isVisible(visibilityItems, visibilityOrder, { 'nested.branch.leaf': false }, visibilityItems.nested), 'menu hides recursively empty submenus')
|
||||||
|
assert(menu.isVisible(visibilityItems, visibilityOrder, {}, visibilityItems.dynamic), 'menu keeps provider-backed submenus visible')
|
||||||
|
|
||||||
const entry = merged.items['style.theme']
|
const entry = merged.items['style.theme']
|
||||||
assert(menu.matchesQuery(entry, 'theme', true), 'menu matches labels and aliases')
|
assert(menu.matchesQuery(entry, 'theme', true), 'menu matches labels and aliases')
|
||||||
assert(menu.matchesQuery(entry, 'colors', true), 'menu matches aliases')
|
assert(menu.matchesQuery(entry, 'colors', true), 'menu matches aliases')
|
||||||
@@ -122,6 +136,16 @@ assert(
|
|||||||
!defaultById['trigger.toggle.direct-boot'] && !defaultById['trigger.toggle.passwordless-sudo'],
|
!defaultById['trigger.toggle.direct-boot'] && !defaultById['trigger.toggle.passwordless-sudo'],
|
||||||
'menu removes the relocated toggles from Trigger > Toggle'
|
'menu removes the relocated toggles from Trigger > Toggle'
|
||||||
)
|
)
|
||||||
|
assertEqual(
|
||||||
|
defaultById['trigger.hardware.laptop-display'].when,
|
||||||
|
'omarchy-hw-laptop',
|
||||||
|
'menu only shows Laptop Display on laptops'
|
||||||
|
)
|
||||||
|
assertEqual(
|
||||||
|
defaultById['trigger.hardware.mirror-display'].when,
|
||||||
|
'omarchy-hw-laptop',
|
||||||
|
'menu only shows Mirror Display on laptops'
|
||||||
|
)
|
||||||
assert(
|
assert(
|
||||||
/font\.family: row\.iconFont\.length > 0 \? row\.iconFont : root\.fontFamily/.test(menuQml),
|
/font\.family: row\.iconFont\.length > 0 \? row\.iconFont : root\.fontFamily/.test(menuQml),
|
||||||
'menu rows support per-icon font families'
|
'menu rows support per-icon font families'
|
||||||
|
|||||||
Reference in New Issue
Block a user