Dim installed software in the Install menus instead of hiding it (#6955)
Install rows hid themselves with `when:"! <present>"`, so software you already had vanished from the very list it was installed from. Add a `disabled:` guard that keeps a row listed but dim, ✓-marked, unselectable and out of search, and move every Install row onto it. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b724f76156
commit
4b93f8d84d
@@ -14,6 +14,10 @@
|
|||||||
// title header text shown when the submenu is open; defaults to label
|
// title header text shown when the submenu is open; defaults to label
|
||||||
// when shell condition; hide row when it fails
|
// when shell condition; hide row when it fails
|
||||||
// checked shell condition; append ✓ when it succeeds
|
// checked shell condition; append ✓ when it succeeds
|
||||||
|
// disabled shell condition; when it succeeds the row stays listed but
|
||||||
|
// goes dim, takes a ✓ and can no longer be selected. Install
|
||||||
|
// rows use it so software already on the machine reads as
|
||||||
|
// installed instead of vanishing from the list.
|
||||||
|
|
||||||
// Root Menu
|
// Root Menu
|
||||||
"apps": {"icon":"","label":"Apps","aliases":["app","applications"],"provider":"apps"},
|
"apps": {"icon":"","label":"Apps","aliases":["app","applications"],"provider":"apps"},
|
||||||
@@ -201,71 +205,71 @@
|
|||||||
"install.browser": {"icon":"","label":"Browser"},
|
"install.browser": {"icon":"","label":"Browser"},
|
||||||
"install.ai": {"icon":"","label":"AI"},
|
"install.ai": {"icon":"","label":"AI"},
|
||||||
"install.gaming": {"icon":"","label":"Gaming"},
|
"install.gaming": {"icon":"","label":"Gaming"},
|
||||||
"install.windows": {"icon":"","label":"Windows","when":"[[ ! -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm install'"},
|
"install.windows": {"icon":"","label":"Windows","disabled":"[[ -f $HOME/.local/share/applications/windows-vm.desktop ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-windows-vm install'"},
|
||||||
"install.preinstalls": {"icon":"","label":"Preinstalls","when":"[[ -f $HOME/.local/state/omarchy/preinstalls-removed ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-preinstalls"},
|
"install.preinstalls": {"icon":"","label":"Preinstalls","disabled":"[[ ! -f $HOME/.local/state/omarchy/preinstalls-removed ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-preinstalls"},
|
||||||
"install.browser.chrome": {"icon":"","label":"Chrome","when":"! omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser chrome'"},
|
"install.browser.chrome": {"icon":"","label":"Chrome","disabled":"omarchy-pkg-present google-chrome","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser chrome'"},
|
||||||
"install.browser.edge": {"icon":"","label":"Edge","when":"! omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser edge'"},
|
"install.browser.edge": {"icon":"","label":"Edge","disabled":"omarchy-pkg-present microsoft-edge-stable-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser edge'"},
|
||||||
"install.browser.brave": {"icon":"","label":"Brave","when":"! omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave'"},
|
"install.browser.brave": {"icon":"","label":"Brave","disabled":"omarchy-pkg-present brave-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave'"},
|
||||||
"install.browser.brave-origin": {"icon":"","label":"Brave Origin","when":"! omarchy-pkg-present brave-origin-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave-origin'"},
|
"install.browser.brave-origin": {"icon":"","label":"Brave Origin","disabled":"omarchy-pkg-present brave-origin-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser brave-origin'"},
|
||||||
"install.browser.firefox": {"icon":"","label":"Firefox","when":"! omarchy-pkg-present firefox","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser firefox'"},
|
"install.browser.firefox": {"icon":"","label":"Firefox","disabled":"omarchy-pkg-present firefox","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser firefox'"},
|
||||||
"install.browser.zen": {"icon":"","label":"Zen","when":"! omarchy-pkg-present zen-browser-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser zen'"},
|
"install.browser.zen": {"icon":"","label":"Zen","disabled":"omarchy-pkg-present zen-browser-bin","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-browser zen'"},
|
||||||
"install.service.1password": {"icon":"","label":"1Password","when":"! omarchy-pkg-present 1password","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-1password"},
|
"install.service.1password": {"icon":"","label":"1Password","disabled":"omarchy-pkg-present 1password","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-1password"},
|
||||||
"install.service.dropbox": {"icon":"","label":"Dropbox","when":"! omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-dropbox"},
|
"install.service.dropbox": {"icon":"","label":"Dropbox","disabled":"omarchy-pkg-present dropbox","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-dropbox"},
|
||||||
"install.service.spotify": {"icon":"","label":"Spotify","when":"! omarchy-pkg-present spotify","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-spotify"},
|
"install.service.spotify": {"icon":"","label":"Spotify","disabled":"omarchy-pkg-present spotify","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-spotify"},
|
||||||
"install.service.signal": {"icon":"","label":"Signal","when":"! omarchy-pkg-present signal-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-signal"},
|
"install.service.signal": {"icon":"","label":"Signal","disabled":"omarchy-pkg-present signal-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-signal"},
|
||||||
"install.service.tailscale": {"icon":"","label":"Tailscale","when":"! omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-tailscale"},
|
"install.service.tailscale": {"icon":"","label":"Tailscale","disabled":"omarchy-pkg-present tailscale","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-tailscale"},
|
||||||
"install.service.nordvpn": {"icon":"","label":"NordVPN","when":"! omarchy-pkg-present nordvpn-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-nordvpn"},
|
"install.service.nordvpn": {"icon":"","label":"NordVPN","disabled":"omarchy-pkg-present nordvpn-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-nordvpn"},
|
||||||
"install.service.once": {"icon":"","label":"ONCE","when":"! omarchy-pkg-present once-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-once"},
|
"install.service.once": {"icon":"","label":"ONCE","disabled":"omarchy-pkg-present once-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-service-once"},
|
||||||
"install.service.bitwarden": {"icon":"","label":"Bitwarden","when":"! omarchy-pkg-present bitwarden","action":"omarchy-install-and-launch Bitwarden 'bitwarden bitwarden-cli' bitwarden"},
|
"install.service.bitwarden": {"icon":"","label":"Bitwarden","disabled":"omarchy-pkg-present bitwarden","action":"omarchy-install-and-launch Bitwarden 'bitwarden bitwarden-cli' bitwarden"},
|
||||||
"install.service.chromium-account": {"icon":"","label":"Chromium Account","when":"[[ -f ~/.config/chromium-flags.conf ]] && ! grep -q oauth2-client-id ~/.config/chromium-flags.conf","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-chromium-google-account"},
|
"install.service.chromium-account": {"icon":"","label":"Chromium Account","when":"[[ -f ~/.config/chromium-flags.conf ]]","disabled":"grep -q oauth2-client-id ~/.config/chromium-flags.conf","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-chromium-google-account"},
|
||||||
"install.editor.vscode": {"icon":"","label":"VSCode","when":"! omarchy-pkg-present visual-studio-code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-vscode"},
|
"install.editor.vscode": {"icon":"","label":"VSCode","disabled":"omarchy-pkg-present visual-studio-code-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-vscode"},
|
||||||
"install.editor.cursor": {"icon":"","label":"Cursor","when":"! omarchy-pkg-present cursor-bin","action":"omarchy-install-and-launch Cursor cursor-bin cursor"},
|
"install.editor.cursor": {"icon":"","label":"Cursor","disabled":"omarchy-pkg-present cursor-bin","action":"omarchy-install-and-launch Cursor cursor-bin cursor"},
|
||||||
"install.editor.zed": {"icon":"","label":"Zed","when":"! omarchy-pkg-present zed","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-zed"},
|
"install.editor.zed": {"icon":"","label":"Zed","disabled":"omarchy-pkg-present zed","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-zed"},
|
||||||
"install.editor.sublime": {"icon":"","label":"Sublime Text","when":"! omarchy-pkg-present sublime-text-4","action":"omarchy-install-and-launch 'Sublime Text' sublime-text-4 sublime_text"},
|
"install.editor.sublime": {"icon":"","label":"Sublime Text","disabled":"omarchy-pkg-present sublime-text-4","action":"omarchy-install-and-launch 'Sublime Text' sublime-text-4 sublime_text"},
|
||||||
"install.editor.helix": {"icon":"","label":"Helix","when":"! omarchy-pkg-present helix","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-helix"},
|
"install.editor.helix": {"icon":"","label":"Helix","disabled":"omarchy-pkg-present helix","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-helix"},
|
||||||
"install.editor.vim": {"icon":"","label":"Vim","when":"! omarchy-pkg-present vim","action":"omarchy-install-app Vim vim"},
|
"install.editor.vim": {"icon":"","label":"Vim","disabled":"omarchy-pkg-present vim","action":"omarchy-install-app Vim vim"},
|
||||||
"install.editor.emacs": {"icon":"","label":"Emacs","when":"! omarchy-pkg-present omarchy-emacs","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-emacs"},
|
"install.editor.emacs": {"icon":"","label":"Emacs","disabled":"omarchy-pkg-present omarchy-emacs","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-editor-emacs"},
|
||||||
"install.terminal.alacritty": {"icon":"","label":"Alacritty","when":"! omarchy-pkg-present alacritty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal alacritty'"},
|
"install.terminal.alacritty": {"icon":"","label":"Alacritty","disabled":"omarchy-pkg-present alacritty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal alacritty'"},
|
||||||
"install.terminal.foot": {"icon":"","label":"Foot","when":"! omarchy-pkg-present foot","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal foot'"},
|
"install.terminal.foot": {"icon":"","label":"Foot","disabled":"omarchy-pkg-present foot","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal foot'"},
|
||||||
"install.terminal.ghostty": {"icon":"","label":"Ghostty","when":"! omarchy-pkg-present ghostty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal ghostty'"},
|
"install.terminal.ghostty": {"icon":"","label":"Ghostty","disabled":"omarchy-pkg-present ghostty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal ghostty'"},
|
||||||
"install.terminal.kitty": {"icon":"","label":"Kitty","when":"! omarchy-pkg-present kitty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal kitty'"},
|
"install.terminal.kitty": {"icon":"","label":"Kitty","disabled":"omarchy-pkg-present kitty","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-terminal kitty'"},
|
||||||
"install.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","when":"! omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-chatgpt"},
|
"install.ai.chatgpt": {"icon":"","iconFont":"omarchy","label":"ChatGPT Desktop","disabled":"omarchy-pkg-present openai-codex-desktop","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-ai-chatgpt"},
|
||||||
"install.ai.dictation": {"icon":"","label":"Dictation","when":"! omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
|
"install.ai.dictation": {"icon":"","label":"Dictation","disabled":"omarchy-pkg-present voxtype-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install"},
|
||||||
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","when":"! omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
|
"install.ai.grok-bot": {"icon":"","iconFont":"omarchy","label":"Grok Bot","disabled":"omarchy-pkg-present grok-bot","action":"omarchy-install-and-launch 'Grok Bot' grok-bot grok-bot"},
|
||||||
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","when":"! omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
"install.ai.lm-studio": {"icon":"","iconFont":"omarchy","label":"LM Studio","disabled":"omarchy-pkg-present lmstudio-bin","action":"omarchy-install-app 'LM Studio' lmstudio-bin"},
|
||||||
"install.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","when":"! omarchy-cmd-present ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; omarchy-install-app Ollama \"$ollama_pkg\""},
|
"install.ai.ollama": {"icon":"","iconFont":"omarchy","label":"Ollama","disabled":"omarchy-cmd-present ollama","action":"if omarchy-cmd-present nvidia-smi; then ollama_pkg=ollama-cuda; elif omarchy-cmd-present rocminfo; then ollama_pkg=ollama-rocm; else ollama_pkg=ollama; fi; omarchy-install-app Ollama \"$ollama_pkg\""},
|
||||||
"install.gaming.steam": {"icon":"","label":"Steam","when":"! omarchy-pkg-present steam","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-steam"},
|
"install.gaming.steam": {"icon":"","label":"Steam","disabled":"omarchy-pkg-present steam","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-steam"},
|
||||||
"install.gaming.retroarch": {"icon":"","label":"RetroArch","when":"! omarchy-pkg-present retroarch","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-retroarch"},
|
"install.gaming.retroarch": {"icon":"","label":"RetroArch","disabled":"omarchy-pkg-present retroarch","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-retroarch"},
|
||||||
"install.gaming.minecraft": {"icon":"","label":"Minecraft","when":"! omarchy-pkg-present minecraft-launcher","action":"omarchy-install-and-launch Minecraft minecraft-launcher minecraft-launcher"},
|
"install.gaming.minecraft": {"icon":"","label":"Minecraft","disabled":"omarchy-pkg-present minecraft-launcher","action":"omarchy-install-and-launch Minecraft minecraft-launcher minecraft-launcher"},
|
||||||
"install.gaming.geforce-now": {"icon":"","label":"NVIDIA GeForce NOW","when":"! flatpak info com.nvidia.geforcenow","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-geforce-now"},
|
"install.gaming.geforce-now": {"icon":"","label":"NVIDIA GeForce NOW","disabled":"flatpak info com.nvidia.geforcenow","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-geforce-now"},
|
||||||
"install.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","when":"[[ ! -f \"$HOME/.local/share/applications/Xbox Cloud Gaming.desktop\" ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-cloud"},
|
"install.gaming.xbox-cloud": {"icon":"","label":"Xbox Cloud Gaming","disabled":"[[ -f \"$HOME/.local/share/applications/Xbox Cloud Gaming.desktop\" ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-cloud"},
|
||||||
"install.gaming.xbox-controllers": {"icon":"","label":"Xbox Controllers","when":"! omarchy-pkg-present xpadneo-dkms","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-controllers"},
|
"install.gaming.xbox-controllers": {"icon":"","label":"Xbox Controllers","disabled":"omarchy-pkg-present xpadneo-dkms","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-xbox-controllers"},
|
||||||
"install.gaming.battlenet": {"icon":"","label":"Battle.net","when":"[[ ! -d $HOME/Games/battlenet ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-battlenet"},
|
"install.gaming.battlenet": {"icon":"","label":"Battle.net","disabled":"[[ -d $HOME/Games/battlenet ]]","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-battlenet"},
|
||||||
"install.gaming.lutris": {"icon":"","label":"Lutris","when":"! omarchy-pkg-present lutris","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-lutris"},
|
"install.gaming.lutris": {"icon":"","label":"Lutris","disabled":"omarchy-pkg-present lutris","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-lutris"},
|
||||||
"install.gaming.heroic": {"icon":"","label":"Heroic (Epic Games)","when":"! omarchy-pkg-present heroic-games-launcher-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-heroic"},
|
"install.gaming.heroic": {"icon":"","label":"Heroic (Epic Games)","disabled":"omarchy-pkg-present heroic-games-launcher-bin","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-gaming-heroic"},
|
||||||
"install.gaming.retro-launcher": {"icon":"","label":"RetroArch Game Launcher","action":"omarchy-games-retro-install"},
|
"install.gaming.retro-launcher": {"icon":"","label":"RetroArch Game Launcher","action":"omarchy-games-retro-install"},
|
||||||
"install.development.rails": {"icon":"","label":"Ruby on Rails","when":"[[ ! -d $HOME/.local/share/mise/installs/ruby ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ruby'"},
|
"install.development.rails": {"icon":"","label":"Ruby on Rails","disabled":"[[ -d $HOME/.local/share/mise/installs/ruby ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ruby'"},
|
||||||
"install.development.docker-dbs": {"icon":"","label":"Docker DB","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-docker-dbs"},
|
"install.development.docker-dbs": {"icon":"","label":"Docker DB","action":"omarchy-launch-floating-terminal-with-presentation omarchy-install-docker-dbs"},
|
||||||
"install.development.javascript": {"icon":"","label":"JavaScript"},
|
"install.development.javascript": {"icon":"","label":"JavaScript"},
|
||||||
"install.development.go": {"icon":"","label":"Go","when":"[[ ! -d $HOME/.local/share/mise/installs/go ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env go'"},
|
"install.development.go": {"icon":"","label":"Go","disabled":"[[ -d $HOME/.local/share/mise/installs/go ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env go'"},
|
||||||
"install.development.php": {"icon":"","label":"PHP"},
|
"install.development.php": {"icon":"","label":"PHP"},
|
||||||
"install.development.python": {"icon":"","label":"Python","when":"[[ ! -d $HOME/.local/share/mise/installs/python ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env python'"},
|
"install.development.python": {"icon":"","label":"Python","disabled":"[[ -d $HOME/.local/share/mise/installs/python ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env python'"},
|
||||||
"install.development.elixir": {"icon":"","label":"Elixir"},
|
"install.development.elixir": {"icon":"","label":"Elixir"},
|
||||||
"install.development.zig": {"icon":"","label":"Zig","when":"[[ ! -d $HOME/.local/share/mise/installs/zig ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env zig'"},
|
"install.development.zig": {"icon":"","label":"Zig","disabled":"[[ -d $HOME/.local/share/mise/installs/zig ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env zig'"},
|
||||||
"install.development.rust": {"icon":"","label":"Rust","when":"[[ ! -d $HOME/.rustup ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env rust'"},
|
"install.development.rust": {"icon":"","label":"Rust","disabled":"[[ -d $HOME/.rustup ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env rust'"},
|
||||||
"install.development.java": {"icon":"","label":"Java","when":"[[ ! -d $HOME/.local/share/mise/installs/java ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env java'"},
|
"install.development.java": {"icon":"","label":"Java","disabled":"[[ -d $HOME/.local/share/mise/installs/java ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env java'"},
|
||||||
"install.development.dotnet": {"icon":"","label":".NET","when":"[[ ! -d $HOME/.local/share/mise/installs/dotnet ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env dotnet'"},
|
"install.development.dotnet": {"icon":"","label":".NET","disabled":"[[ -d $HOME/.local/share/mise/installs/dotnet ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env dotnet'"},
|
||||||
"install.development.ocaml": {"icon":"","label":"OCaml","when":"[[ ! -d $HOME/.opam ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ocaml'"},
|
"install.development.ocaml": {"icon":"","label":"OCaml","disabled":"[[ -d $HOME/.opam ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env ocaml'"},
|
||||||
"install.development.clojure": {"icon":"","label":"Clojure","when":"[[ ! -d $HOME/.local/share/mise/installs/clojure ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env clojure'"},
|
"install.development.clojure": {"icon":"","label":"Clojure","disabled":"[[ -d $HOME/.local/share/mise/installs/clojure ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env clojure'"},
|
||||||
"install.development.scala": {"icon":"","label":"Scala","when":"[[ ! -d $HOME/.local/share/mise/installs/scala ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env scala'"},
|
"install.development.scala": {"icon":"","label":"Scala","disabled":"[[ -d $HOME/.local/share/mise/installs/scala ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env scala'"},
|
||||||
"install.development.javascript.node": {"icon":"","label":"Node.js","when":"[[ ! -d $HOME/.local/share/mise/installs/node ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env node'"},
|
"install.development.javascript.node": {"icon":"","label":"Node.js","disabled":"[[ -d $HOME/.local/share/mise/installs/node ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env node'"},
|
||||||
"install.development.javascript.bun": {"icon":"","label":"Bun","when":"[[ ! -d $HOME/.local/share/mise/installs/bun ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env bun'"},
|
"install.development.javascript.bun": {"icon":"","label":"Bun","disabled":"[[ -d $HOME/.local/share/mise/installs/bun ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env bun'"},
|
||||||
"install.development.javascript.deno": {"icon":"","label":"Deno","when":"[[ ! -d $HOME/.local/share/mise/installs/deno ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env deno'"},
|
"install.development.javascript.deno": {"icon":"","label":"Deno","disabled":"[[ -d $HOME/.local/share/mise/installs/deno ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env deno'"},
|
||||||
"install.development.php.php": {"icon":"","label":"PHP","when":"! omarchy-pkg-present php","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env php'"},
|
"install.development.php.php": {"icon":"","label":"PHP","disabled":"omarchy-pkg-present php","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env php'"},
|
||||||
"install.development.php.laravel": {"icon":"","label":"Laravel","when":"[[ ! -x $HOME/.config/composer/vendor/bin/laravel ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env laravel'"},
|
"install.development.php.laravel": {"icon":"","label":"Laravel","disabled":"[[ -x $HOME/.config/composer/vendor/bin/laravel ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env laravel'"},
|
||||||
"install.development.php.symfony": {"icon":"","label":"Symfony","when":"! omarchy-pkg-present symfony-cli","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env symfony'"},
|
"install.development.php.symfony": {"icon":"","label":"Symfony","disabled":"omarchy-pkg-present symfony-cli","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env symfony'"},
|
||||||
"install.development.elixir.elixir": {"icon":"","label":"Elixir","when":"[[ ! -d $HOME/.local/share/mise/installs/elixir ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env elixir'"},
|
"install.development.elixir.elixir": {"icon":"","label":"Elixir","disabled":"[[ -d $HOME/.local/share/mise/installs/elixir ]]","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env elixir'"},
|
||||||
"install.development.elixir.phoenix": {"icon":"","label":"Phoenix","when":"! compgen -G \"$HOME/.mix/archives/phx_new*\"","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env phoenix'"},
|
"install.development.elixir.phoenix": {"icon":"","label":"Phoenix","disabled":"compgen -G \"$HOME/.mix/archives/phx_new*\"","action":"omarchy-launch-floating-terminal-with-presentation 'omarchy-install-dev-env phoenix'"},
|
||||||
|
|
||||||
// Remove
|
// Remove
|
||||||
"remove.package": {"icon":"","label":"Package","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-remove"},
|
"remove.package": {"icon":"","label":"Package","action":"xdg-terminal-exec --app-id=org.omarchy.terminal omarchy-pkg-remove"},
|
||||||
|
|||||||
+69
-16
@@ -317,6 +317,7 @@ Item {
|
|||||||
aliases: aliases,
|
aliases: aliases,
|
||||||
when: "",
|
when: "",
|
||||||
checked: "",
|
checked: "",
|
||||||
|
disabled: "",
|
||||||
order: 0
|
order: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -382,6 +383,7 @@ Item {
|
|||||||
aliases: [],
|
aliases: [],
|
||||||
when: "",
|
when: "",
|
||||||
checked: "",
|
checked: "",
|
||||||
|
disabled: "",
|
||||||
order: 0
|
order: 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -470,9 +472,10 @@ Item {
|
|||||||
return MenuModel.isVisible(root.items, root.itemOrder, root.whenResults, entry)
|
return MenuModel.isVisible(root.items, root.itemOrder, root.whenResults, entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label with the ✓ marker baked in when `checked:` evaluated truthy.
|
// Label with the ✓ marker baked in when `checked:` or `disabled:` evaluated
|
||||||
|
// truthy.
|
||||||
function labelFor(entry) {
|
function labelFor(entry) {
|
||||||
return MenuModel.labelFor(entry, root.checkedResults)
|
return MenuModel.labelFor(entry, root.checkedResults, root.disabledResults)
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchableToken(value) {
|
function searchableToken(value) {
|
||||||
@@ -495,8 +498,17 @@ Item {
|
|||||||
return MenuModel.descriptionTextMatches(query, text)
|
return MenuModel.descriptionTextMatches(query, text)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rows whose `disabled:` evaluated truthy stay listed but dimmed, and the
|
||||||
|
// cursor steps over them.
|
||||||
|
function isDisabled(entry) {
|
||||||
|
return MenuModel.isDisabled(root.disabledResults, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A disabled row earns its place in the submenu it belongs to, where the
|
||||||
|
// list around it is the point. Search is a list of what you can do, so it
|
||||||
|
// leaves them out.
|
||||||
function matchesQuery(entry, query) {
|
function matchesQuery(entry, query) {
|
||||||
return MenuModel.matchesQuery(entry, query, root.isVisible(entry))
|
return MenuModel.matchesQuery(entry, query, root.isVisible(entry) && !root.isDisabled(entry))
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchScore(entry, query) {
|
function searchScore(entry, query) {
|
||||||
@@ -504,7 +516,38 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function displayRow(entry, detail, score, section) {
|
function displayRow(entry, detail, score, section) {
|
||||||
return MenuModel.displayRow(root.items, root.itemOrder, root.checkedResults, entry, detail, score, section)
|
return MenuModel.displayRow(root.items, root.itemOrder, root.checkedResults, root.disabledResults, entry, detail, score, section)
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowSelectable(index) {
|
||||||
|
if (index < 0 || index >= displayModel.count) return false
|
||||||
|
return !displayModel.get(index).disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// First selectable row at or past `from`, continuing in the direction of
|
||||||
|
// travel and wrapping. -1 when every row is disabled, which leaves the menu
|
||||||
|
// with no cursor at all rather than one parked on a row Enter won't run.
|
||||||
|
function nextSelectable(from, direction) {
|
||||||
|
var count = displayModel.count
|
||||||
|
if (count === 0) return -1
|
||||||
|
|
||||||
|
var step = direction < 0 ? -1 : 1
|
||||||
|
var index = ((from % count) + count) % count
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
if (root.rowSelectable(index)) return index
|
||||||
|
index = (index + step + count) % count
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Park the cursor on a selectable row after the rows underneath it changed.
|
||||||
|
// A menu with nothing selectable in it -- every app in it already installed
|
||||||
|
// -- shows no cursor at all, and grows one the moment a row can take it.
|
||||||
|
function settleCursor() {
|
||||||
|
var target = root.nextSelectable(root.selectedIndex, 1)
|
||||||
|
root.selectedIndex = target >= 0 ? target : 0
|
||||||
|
root.cursorActive = target >= 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function rebuildDmenuDisplay() {
|
function rebuildDmenuDisplay() {
|
||||||
@@ -530,6 +573,7 @@ Item {
|
|||||||
&& detail.toLowerCase().indexOf(query) < 0) continue
|
&& detail.toLowerCase().indexOf(query) < 0) continue
|
||||||
displayModel.append({
|
displayModel.append({
|
||||||
itemId: "dmenu." + i,
|
itemId: "dmenu." + i,
|
||||||
|
disabled: false,
|
||||||
kind: "dmenu",
|
kind: "dmenu",
|
||||||
icon: icon,
|
icon: icon,
|
||||||
iconFont: "",
|
iconFont: "",
|
||||||
@@ -630,9 +674,7 @@ Item {
|
|||||||
for (var k = 0; k < rows.length; k++) displayModel.append(rows[k])
|
for (var k = 0; k < rows.length; k++) displayModel.append(rows[k])
|
||||||
layoutSerial += 1
|
layoutSerial += 1
|
||||||
|
|
||||||
if (displayModel.count === 0) selectedIndex = 0
|
root.settleCursor()
|
||||||
else if (selectedIndex >= displayModel.count) selectedIndex = displayModel.count - 1
|
|
||||||
else if (selectedIndex < 0) selectedIndex = 0
|
|
||||||
|
|
||||||
Qt.callLater(function() {
|
Qt.callLater(function() {
|
||||||
if (displayModel.count > 0) root.revealCursor()
|
if (displayModel.count > 0) root.revealCursor()
|
||||||
@@ -665,12 +707,12 @@ Item {
|
|||||||
if (displayModel.count === 0) return
|
if (displayModel.count === 0) return
|
||||||
|
|
||||||
root.disarmPointer()
|
root.disarmPointer()
|
||||||
if (!cursorActive) {
|
var from = cursorActive ? selectedIndex + delta : (delta < 0 ? displayModel.count - 1 : 0)
|
||||||
cursorActive = true
|
var target = root.nextSelectable(from, delta)
|
||||||
selectedIndex = delta < 0 ? displayModel.count - 1 : 0
|
if (target < 0) return
|
||||||
} else {
|
|
||||||
selectedIndex = (selectedIndex + delta + displayModel.count) % displayModel.count
|
cursorActive = true
|
||||||
}
|
selectedIndex = target
|
||||||
revealCursor()
|
revealCursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,7 +769,7 @@ Item {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < 0 || index >= displayModel.count) return
|
if (!root.rowSelectable(index)) return
|
||||||
|
|
||||||
var row = displayModel.get(index)
|
var row = displayModel.get(index)
|
||||||
if (row.kind === "menu" || row.kind === "link") {
|
if (row.kind === "menu" || row.kind === "link") {
|
||||||
@@ -874,6 +916,7 @@ Item {
|
|||||||
|
|
||||||
function selectFromPointer(index, item, mouse) {
|
function selectFromPointer(index, item, mouse) {
|
||||||
if (!pointerGate.moved(item, mouse)) return
|
if (!pointerGate.moved(item, mouse)) return
|
||||||
|
if (!root.rowSelectable(index)) return
|
||||||
root.cursorActive = true
|
root.cursorActive = true
|
||||||
root.selectedIndex = index
|
root.selectedIndex = index
|
||||||
}
|
}
|
||||||
@@ -947,6 +990,7 @@ Item {
|
|||||||
|
|
||||||
property var whenResults: ({}) // id → true|false (allow visibility)
|
property var whenResults: ({}) // id → true|false (allow visibility)
|
||||||
property var checkedResults: ({}) // id → true|false (show ✓)
|
property var checkedResults: ({}) // id → true|false (show ✓)
|
||||||
|
property var disabledResults: ({}) // id → true|false (dim, skip cursor)
|
||||||
property bool guardsPending: false
|
property bool guardsPending: false
|
||||||
|
|
||||||
function evaluateGuards() {
|
function evaluateGuards() {
|
||||||
@@ -966,6 +1010,7 @@ Item {
|
|||||||
if (!script) {
|
if (!script) {
|
||||||
root.whenResults = ({})
|
root.whenResults = ({})
|
||||||
root.checkedResults = ({})
|
root.checkedResults = ({})
|
||||||
|
root.disabledResults = ({})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guardProc.collected = ""
|
guardProc.collected = ""
|
||||||
@@ -991,6 +1036,7 @@ Item {
|
|||||||
|
|
||||||
var nextWhen = ({})
|
var nextWhen = ({})
|
||||||
var nextChecked = ({})
|
var nextChecked = ({})
|
||||||
|
var nextDisabled = ({})
|
||||||
var lines = guardProc.collected.split("\n")
|
var lines = guardProc.collected.split("\n")
|
||||||
for (var i = 0; i < lines.length; i++) {
|
for (var i = 0; i < lines.length; i++) {
|
||||||
var line = lines[i].trim()
|
var line = lines[i].trim()
|
||||||
@@ -1005,9 +1051,11 @@ Item {
|
|||||||
var tag = rest.substring(tagAt + 1)
|
var tag = rest.substring(tagAt + 1)
|
||||||
if (tag === "w") nextWhen[id] = value
|
if (tag === "w") nextWhen[id] = value
|
||||||
else if (tag === "c") nextChecked[id] = value
|
else if (tag === "c") nextChecked[id] = value
|
||||||
|
else if (tag === "d") nextDisabled[id] = value
|
||||||
}
|
}
|
||||||
root.whenResults = nextWhen
|
root.whenResults = nextWhen
|
||||||
root.checkedResults = nextChecked
|
root.checkedResults = nextChecked
|
||||||
|
root.disabledResults = nextDisabled
|
||||||
if (root.opened) root.rebuildDisplay()
|
if (root.opened) root.rebuildDisplay()
|
||||||
// Run the evaluation that had to stand aside. Deferred by a turn so the
|
// Run the evaluation that had to stand aside. Deferred by a turn so the
|
||||||
// process is settled before its command is set again.
|
// process is settled before its command is set again.
|
||||||
@@ -1108,7 +1156,7 @@ Item {
|
|||||||
if (root.mode === "input") root.applyDmenuSelection(root.filterText)
|
if (root.mode === "input") root.applyDmenuSelection(root.filterText)
|
||||||
else if (displayModel.count > 0) root.activateIndex(root.cursorActive ? root.selectedIndex : 0)
|
else if (displayModel.count > 0) root.activateIndex(root.cursorActive ? root.selectedIndex : 0)
|
||||||
} else if (root.cursorActive) root.activateIndex(root.selectedIndex)
|
} else if (root.cursorActive) root.activateIndex(root.selectedIndex)
|
||||||
else if (displayModel.count > 0) root.cursorActive = true
|
else root.settleCursor()
|
||||||
event.accepted = true
|
event.accepted = true
|
||||||
} else if (event.text && event.text.length === 1 && event.text.charCodeAt(0) >= 32 && event.text.charCodeAt(0) !== 127 && (event.modifiers === Qt.NoModifier || event.modifiers === Qt.ShiftModifier)) {
|
} else if (event.text && event.text.length === 1 && event.text.charCodeAt(0) >= 32 && event.text.charCodeAt(0) !== 127 && (event.modifiers === Qt.NoModifier || event.modifiers === Qt.ShiftModifier)) {
|
||||||
root.setFilter(root.filterText + event.text)
|
root.setFilter(root.filterText + event.text)
|
||||||
@@ -1211,6 +1259,7 @@ Item {
|
|||||||
required property string path
|
required property string path
|
||||||
required property string action
|
required property string action
|
||||||
required property int childCount
|
required property int childCount
|
||||||
|
required property bool disabled
|
||||||
|
|
||||||
readonly property bool hasCursor: root.cursorActive && row.index === root.selectedIndex
|
readonly property bool hasCursor: root.cursorActive && row.index === root.selectedIndex
|
||||||
readonly property bool isApp: row.kind === "app"
|
readonly property bool isApp: row.kind === "app"
|
||||||
@@ -1218,6 +1267,9 @@ Item {
|
|||||||
|
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
height: root.rowHeightForDetail(row.detail)
|
height: root.rowHeightForDetail(row.detail)
|
||||||
|
// Faded: the row is here to say the software is already
|
||||||
|
// installed, not to be picked.
|
||||||
|
opacity: row.disabled ? 0.4 : 1
|
||||||
radius: root.cornerRadius
|
radius: root.cornerRadius
|
||||||
color: row.hasCursor ? root.selectedBackground : "transparent"
|
color: row.hasCursor ? root.selectedBackground : "transparent"
|
||||||
borderSpec: row.hasCursor ? root.selectedBorderSpec : Border.none()
|
borderSpec: row.hasCursor ? root.selectedBorderSpec : Border.none()
|
||||||
@@ -1330,7 +1382,7 @@ Item {
|
|||||||
id: mouseArea
|
id: mouseArea
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: row.disabled ? Qt.ArrowCursor : Qt.PointingHandCursor
|
||||||
onEntered: root.selectFromPointer(row.index, row, {
|
onEntered: root.selectFromPointer(row.index, row, {
|
||||||
x: mouseArea.mouseX,
|
x: mouseArea.mouseX,
|
||||||
y: mouseArea.mouseY
|
y: mouseArea.mouseY
|
||||||
@@ -1339,6 +1391,7 @@ Item {
|
|||||||
root.selectFromPointer(row.index, row, mouse)
|
root.selectFromPointer(row.index, row, mouse)
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (row.disabled) return
|
||||||
root.cursorActive = true
|
root.cursorActive = true
|
||||||
root.selectedIndex = row.index
|
root.selectedIndex = row.index
|
||||||
root.activateIndex(row.index, true)
|
root.activateIndex(row.index, true)
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ function normalizeItem(id, raw) {
|
|||||||
provider: value.provider || "",
|
provider: value.provider || "",
|
||||||
aliases: aliases,
|
aliases: aliases,
|
||||||
when: value.when || "",
|
when: value.when || "",
|
||||||
checked: value.checked || ""
|
checked: value.checked || "",
|
||||||
|
disabled: value.disabled || ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +84,7 @@ function mergeMenuSources(defaultItems, userItems) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!nextItems.root) {
|
if (!nextItems.root) {
|
||||||
nextItems.root = { id: "root", parent: "", kind: "menu", icon: "", iconFont: "", label: "Go", title: "", target: "", description: "", aliases: [], when: "", checked: "", action: "", provider: "" }
|
nextItems.root = { id: "root", parent: "", kind: "menu", icon: "", iconFont: "", label: "Go", title: "", target: "", description: "", aliases: [], when: "", checked: "", disabled: "", action: "", provider: "" }
|
||||||
nextOrder.unshift("root")
|
nextOrder.unshift("root")
|
||||||
}
|
}
|
||||||
for (var k3 = 0; k3 < nextOrder.length; k3++) nextItems[nextOrder[k3]].order = k3
|
for (var k3 = 0; k3 < nextOrder.length; k3++) nextItems[nextOrder[k3]].order = k3
|
||||||
@@ -270,10 +271,20 @@ function isVisible(items, itemOrder, whenResults, entry, depth) {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function labelFor(entry, checkedResults) {
|
// A `disabled:` row stays listed but goes dim and unselectable. The
|
||||||
|
// Install submenus use it so software already on the machine reads as
|
||||||
|
// installed rather than disappearing from the list it was installed from.
|
||||||
|
function isDisabled(disabledResults, entry) {
|
||||||
|
if (!entry || !entry.disabled) return false
|
||||||
|
return !!(disabledResults && disabledResults[entry.id])
|
||||||
|
}
|
||||||
|
|
||||||
|
// A disabled row is software you already have, which is the same thing the ✓
|
||||||
|
// says everywhere else in the menu, so it earns the same marker.
|
||||||
|
function labelFor(entry, checkedResults, disabledResults) {
|
||||||
if (!entry) return ""
|
if (!entry) return ""
|
||||||
if (entry.checked && checkedResults && checkedResults[entry.id]) return entry.label + " ✓"
|
var marked = (entry.checked && checkedResults && checkedResults[entry.id]) || isDisabled(disabledResults, entry)
|
||||||
return entry.label
|
return marked ? entry.label + " ✓" : entry.label
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchableToken(value) {
|
function searchableToken(value) {
|
||||||
@@ -351,16 +362,17 @@ function searchScore(items, entry, query) {
|
|||||||
return score * 1000 + depthFor(items, entry.id) * 25 + entry.order
|
return score * 1000 + depthFor(items, entry.id) * 25 + entry.order
|
||||||
}
|
}
|
||||||
|
|
||||||
function displayRow(items, itemOrder, checkedResults, entry, detail, score, section) {
|
function displayRow(items, itemOrder, checkedResults, disabledResults, entry, detail, score, section) {
|
||||||
var target = entry.kind === "link" ? entry.target : entry.id
|
var target = entry.kind === "link" ? entry.target : entry.id
|
||||||
return {
|
return {
|
||||||
itemId: entry.id,
|
itemId: entry.id,
|
||||||
|
disabled: isDisabled(disabledResults, entry),
|
||||||
kind: entry.kind,
|
kind: entry.kind,
|
||||||
icon: entry.icon,
|
icon: entry.icon,
|
||||||
iconFont: entry.iconFont || "",
|
iconFont: entry.iconFont || "",
|
||||||
appIcon: entry.appIcon || "",
|
appIcon: entry.appIcon || "",
|
||||||
appId: entry.appId || "",
|
appId: entry.appId || "",
|
||||||
label: labelFor(entry, checkedResults),
|
label: labelFor(entry, checkedResults, disabledResults),
|
||||||
target: target,
|
target: target,
|
||||||
detail: detail || "",
|
detail: detail || "",
|
||||||
path: pathFor(items, entry.id),
|
path: pathFor(items, entry.id),
|
||||||
@@ -459,10 +471,10 @@ function guardLine(id, tag, expression) {
|
|||||||
+ id + ":" + tag + ":1; else echo " + id + ":" + tag + ":0; fi\n"
|
+ id + ":" + tag + ":1; else echo " + id + ":" + tag + ":0; fi\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
// One bash script for every `when:` and `checked:` in the menu, reporting
|
// One bash script for every `when:`, `checked:` and `disabled:` in the menu,
|
||||||
// `<id>:<w|c>:<0|1>` per line. Speed is the whole point: the menu opens on
|
// reporting `<id>:<w|c|d>:<0|1>` per line. Speed is the whole point: the menu
|
||||||
// the last evaluation's answers, so however long this takes is how long a row
|
// opens on the last evaluation's answers, so however long this takes is how
|
||||||
// can contradict the state it describes.
|
// long a row can contradict the state it describes.
|
||||||
function guardScript(items) {
|
function guardScript(items) {
|
||||||
var guards = ""
|
var guards = ""
|
||||||
var ids = Object.keys(items || {})
|
var ids = Object.keys(items || {})
|
||||||
@@ -472,6 +484,7 @@ function guardScript(items) {
|
|||||||
if (!entry) continue
|
if (!entry) continue
|
||||||
if (entry.when) guards += guardLine(ids[i], "w", entry.when)
|
if (entry.when) guards += guardLine(ids[i], "w", entry.when)
|
||||||
if (entry.checked) guards += guardLine(ids[i], "c", entry.checked)
|
if (entry.checked) guards += guardLine(ids[i], "c", entry.checked)
|
||||||
|
if (entry.disabled) guards += guardLine(ids[i], "d", entry.disabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
return guards ? guardPrelude(guards) + guards : ""
|
return guards ? guardPrelude(guards) + guards : ""
|
||||||
@@ -497,6 +510,7 @@ if (typeof module !== "undefined") {
|
|||||||
isDescendantOf: isDescendantOf,
|
isDescendantOf: isDescendantOf,
|
||||||
childCount: childCount,
|
childCount: childCount,
|
||||||
isVisible: isVisible,
|
isVisible: isVisible,
|
||||||
|
isDisabled: isDisabled,
|
||||||
labelFor: labelFor,
|
labelFor: labelFor,
|
||||||
searchableToken: searchableToken,
|
searchableToken: searchableToken,
|
||||||
leafIdFor: leafIdFor,
|
leafIdFor: leafIdFor,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const menu = requireFromRoot('shell/plugins/menu/MenuModel.js')
|
|||||||
const items = {
|
const items = {
|
||||||
'setup.default.browser.brave': { id: 'setup.default.browser.brave', when: 'omarchy-pkg-present brave-bin', checked: '[[ "$(omarchy-default-browser)" == "brave" ]]' },
|
'setup.default.browser.brave': { id: 'setup.default.browser.brave', when: 'omarchy-pkg-present brave-bin', checked: '[[ "$(omarchy-default-browser)" == "brave" ]]' },
|
||||||
'setup.default.browser.zen': { id: 'setup.default.browser.zen', when: 'omarchy-pkg-present zen-browser-bin', checked: '[[ "$(omarchy-default-browser)" == "zen" ]]' },
|
'setup.default.browser.zen': { id: 'setup.default.browser.zen', when: 'omarchy-pkg-present zen-browser-bin', checked: '[[ "$(omarchy-default-browser)" == "zen" ]]' },
|
||||||
|
'install.browser.zen': { id: 'install.browser.zen', disabled: 'omarchy-pkg-present zen-browser-bin' },
|
||||||
'plain': { id: 'plain', label: 'No guards' }
|
'plain': { id: 'plain', label: 'No guards' }
|
||||||
}
|
}
|
||||||
const script = menu.guardScript(items)
|
const script = menu.guardScript(items)
|
||||||
@@ -23,7 +24,11 @@ assert(
|
|||||||
script.includes('then echo setup.default.browser.zen:c:1; else echo setup.default.browser.zen:c:0; fi'),
|
script.includes('then echo setup.default.browser.zen:c:1; else echo setup.default.browser.zen:c:0; fi'),
|
||||||
'guard script reports a checked: as <id>:c:<0|1>'
|
'guard script reports a checked: as <id>:c:<0|1>'
|
||||||
)
|
)
|
||||||
assert(!/\bplain:[wc]:/.test(script), 'guard script skips items with nothing to evaluate')
|
assert(
|
||||||
|
script.includes('if { omarchy-pkg-present zen-browser-bin; } >/dev/null 2>&1; then echo install.browser.zen:d:1; else echo install.browser.zen:d:0; fi'),
|
||||||
|
'guard script reports a disabled: as <id>:d:<0|1>'
|
||||||
|
)
|
||||||
|
assert(!/\bplain:[wcd]:/.test(script), 'guard script skips items with nothing to evaluate')
|
||||||
assertEqual(menu.guardScript({ plain: items.plain }), '', 'guard script is empty when no item carries a guard')
|
assertEqual(menu.guardScript({ plain: items.plain }), '', 'guard script is empty when no item carries a guard')
|
||||||
|
|
||||||
// The cost the menu is paying is per fork, not per expression, so what makes
|
// The cost the menu is paying is per fork, not per expression, so what makes
|
||||||
@@ -64,7 +69,7 @@ assert(
|
|||||||
// keeps forking once per row that reads it.
|
// keeps forking once per row that reads it.
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const defaultItems = menu.parseMenuJsonc(fs.readFileSync(path.join(root, 'default/omarchy/omarchy-menu.jsonc'), 'utf8'))
|
const defaultItems = menu.parseMenuJsonc(fs.readFileSync(path.join(root, 'default/omarchy/omarchy-menu.jsonc'), 'utf8'))
|
||||||
const guardText = defaultItems.map(item => `${item.when}\n${item.checked}`).join('\n')
|
const guardText = defaultItems.map(item => `${item.when}\n${item.checked}\n${item.disabled}`).join('\n')
|
||||||
const repeated = [...new Set(
|
const repeated = [...new Set(
|
||||||
(guardText.match(/\$\((omarchy-[a-z0-9-]+)\)/g) || []).map(match => match.slice(2, -1))
|
(guardText.match(/\$\((omarchy-[a-z0-9-]+)\)/g) || []).map(match => match.slice(2, -1))
|
||||||
)].filter(command => guardText.split(`$(${command})`).length > 2)
|
)].filter(command => guardText.split(`$(${command})`).length > 2)
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ assertDeepEqual(
|
|||||||
provider: '',
|
provider: '',
|
||||||
aliases: ['theme'],
|
aliases: ['theme'],
|
||||||
when: '',
|
when: '',
|
||||||
checked: ''
|
checked: '',
|
||||||
|
disabled: ''
|
||||||
},
|
},
|
||||||
'menu normalizes parsed items'
|
'menu normalizes parsed items'
|
||||||
)
|
)
|
||||||
@@ -63,6 +64,8 @@ assertEqual(menu.parentPathFor(merged.items, 'style.theme'), 'Style', 'menu buil
|
|||||||
assert(menu.isDescendantOf(merged.items, 'style.theme', 'style'), 'menu detects descendants')
|
assert(menu.isDescendantOf(merged.items, 'style.theme', 'style'), 'menu detects descendants')
|
||||||
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')
|
||||||
|
assertEqual(menu.labelFor({ id: 'install.browser.zen', label: 'Zen', disabled: 'cmd' }, {}, { 'install.browser.zen': true }), 'Zen ✓', 'menu marks a disabled row as something you already have')
|
||||||
|
assertEqual(menu.labelFor({ id: 'install.browser.zen', label: 'Zen', disabled: 'cmd' }, {}, { 'install.browser.zen': false }), 'Zen', 'menu leaves an uninstalled row unmarked')
|
||||||
|
|
||||||
const visibilityItems = {
|
const visibilityItems = {
|
||||||
hardware: menu.normalizeItem('hardware', { label: 'Hardware' }),
|
hardware: menu.normalizeItem('hardware', { label: 'Hardware' }),
|
||||||
@@ -78,6 +81,22 @@ assert(menu.isVisible(visibilityItems, visibilityOrder, { 'hardware.laptop': tru
|
|||||||
assert(!menu.isVisible(visibilityItems, visibilityOrder, { 'nested.branch.leaf': false }, visibilityItems.nested), 'menu hides recursively empty submenus')
|
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')
|
assert(menu.isVisible(visibilityItems, visibilityOrder, {}, visibilityItems.dynamic), 'menu keeps provider-backed submenus visible')
|
||||||
|
|
||||||
|
// `disabled:` is the softer guard: the row stays listed and only loses the
|
||||||
|
// cursor, which is how an already-installed app keeps its place in Install.
|
||||||
|
const installed = menu.normalizeItem('install.browser.zen', { label: 'Zen', disabled: 'omarchy-pkg-present zen-browser-bin', action: 'install-zen' })
|
||||||
|
assert(menu.isVisible({ 'install.browser.zen': installed }, ['install.browser.zen'], { 'install.browser.zen': false }, installed), 'menu keeps a disabled row visible')
|
||||||
|
assert(menu.isDisabled({ 'install.browser.zen': true }, installed), 'menu disables a row whose disabled: succeeded')
|
||||||
|
assert(!menu.isDisabled({ 'install.browser.zen': false }, installed), 'menu leaves a row selectable when its disabled: failed')
|
||||||
|
assert(!menu.isDisabled({ 'install.browser.zen': true }, visibilityItems.laptop), 'menu never disables a row that declares no disabled:')
|
||||||
|
assert(
|
||||||
|
menu.displayRow({ 'install.browser.zen': installed }, ['install.browser.zen'], {}, { 'install.browser.zen': true }, installed, '', 0).disabled,
|
||||||
|
'menu display rows carry their disabled state'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
/function matchesQuery\(entry, query\) \{\s*\n\s*return MenuModel\.matchesQuery\(entry, query, root\.isVisible\(entry\) && !root\.isDisabled\(entry\)\)/.test(menuQml),
|
||||||
|
'menu search skips disabled rows, which belong to the submenu they sit in rather than a list of what you can do'
|
||||||
|
)
|
||||||
|
|
||||||
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')
|
||||||
@@ -86,9 +105,10 @@ assert(!menu.matchesQuery(entry, 'theme', false), 'menu hides invisible matches'
|
|||||||
assert(menu.searchScore(merged.items, entry, 'theme') < menu.searchScore(merged.items, entry, 'appearance'), 'menu scores name matches above description matches')
|
assert(menu.searchScore(merged.items, entry, 'theme') < menu.searchScore(merged.items, entry, 'appearance'), 'menu scores name matches above description matches')
|
||||||
|
|
||||||
assertDeepEqual(
|
assertDeepEqual(
|
||||||
menu.displayRow(merged.items, merged.itemOrder, {}, entry, 'Style', 12, 'search'),
|
menu.displayRow(merged.items, merged.itemOrder, {}, {}, entry, 'Style', 12, 'search'),
|
||||||
{
|
{
|
||||||
itemId: 'style.theme',
|
itemId: 'style.theme',
|
||||||
|
disabled: false,
|
||||||
kind: 'action',
|
kind: 'action',
|
||||||
icon: '',
|
icon: '',
|
||||||
iconFont: '',
|
iconFont: '',
|
||||||
@@ -235,6 +255,40 @@ assert(
|
|||||||
'menu always exposes every supported browser, terminal, and editor under Defaults'
|
'menu always exposes every supported browser, terminal, and editor under Defaults'
|
||||||
)
|
)
|
||||||
assert(!defaultById['install.ai.crush'], 'menu removes Crush from Install > AI')
|
assert(!defaultById['install.ai.crush'], 'menu removes Crush from Install > AI')
|
||||||
|
// Software you already have keeps its place in Install, dimmed rather than
|
||||||
|
// dropped, so the list reads as a catalog of what Omarchy can install.
|
||||||
|
// Chromium Account is the sole Install row with anything left to hide for, so
|
||||||
|
// any other `when:` here is a row that went back to vanishing once installed.
|
||||||
|
assertDeepEqual(
|
||||||
|
defaultItems
|
||||||
|
.filter(item => item.id.startsWith('install.') && item.action && item.when)
|
||||||
|
.map(item => item.id),
|
||||||
|
['install.service.chromium-account'],
|
||||||
|
'menu never hides an Install row because the software is already there'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
['install.browser.zen', 'install.editor.vscode', 'install.gaming.steam', 'install.development.rust', 'install.windows'].every(
|
||||||
|
id => defaultById[id].disabled && !defaultById[id].when
|
||||||
|
),
|
||||||
|
'menu dims the Install rows for software that is already installed'
|
||||||
|
)
|
||||||
|
assertEqual(
|
||||||
|
defaultById['install.browser.zen'].disabled,
|
||||||
|
'omarchy-pkg-present zen-browser-bin',
|
||||||
|
'menu asks the same presence question it used to hide the row with'
|
||||||
|
)
|
||||||
|
// A guard can still be about something other than having the software: no
|
||||||
|
// Chromium at all means no account to wire up, and that row stays hidden.
|
||||||
|
assert(
|
||||||
|
defaultById['install.service.chromium-account'].when === '[[ -f ~/.config/chromium-flags.conf ]]'
|
||||||
|
&& defaultById['install.service.chromium-account'].disabled.includes('oauth2-client-id'),
|
||||||
|
'menu keeps hiding Chromium Account without Chromium, and dims it once the account is set up'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
defaultItems.filter(item => item.id.startsWith('remove.')).every(item => !item.disabled)
|
||||||
|
&& defaultById['remove.browser.zen'].when === 'omarchy-pkg-present zen-browser-bin',
|
||||||
|
'menu still hides Remove rows for software that is not installed'
|
||||||
|
)
|
||||||
assert(
|
assert(
|
||||||
defaultById['setup.security.passwordless-sudo'].action.includes('omarchy-sudo-passwordless'),
|
defaultById['setup.security.passwordless-sudo'].action.includes('omarchy-sudo-passwordless'),
|
||||||
'menu places Passwordless Sudo under Setup > Security'
|
'menu places Passwordless Sudo under Setup > Security'
|
||||||
@@ -367,6 +421,40 @@ assert(
|
|||||||
/function select\(delta\)[\s\S]*root\.disarmPointer\(\)[\s\S]*selectedIndex =/.test(menuQml),
|
/function select\(delta\)[\s\S]*root\.disarmPointer\(\)[\s\S]*selectedIndex =/.test(menuQml),
|
||||||
'menu keyboard navigation disarms pointer selection'
|
'menu keyboard navigation disarms pointer selection'
|
||||||
)
|
)
|
||||||
|
// A dimmed row is not a target: the cursor steps over it, the pointer refuses
|
||||||
|
// to land on it, and neither Enter nor a click can reach it.
|
||||||
|
assert(
|
||||||
|
/function select\(delta\)[\s\S]*?var target = root\.nextSelectable\(from, delta\)\s*\n\s*if \(target < 0\) return/.test(menuQml),
|
||||||
|
'menu keyboard navigation skips disabled rows in the direction of travel'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
/function rowSelectable\(index\)[\s\S]*?return !displayModel\.get\(index\)\.disabled/.test(menuQml),
|
||||||
|
'menu reads selectability off the row'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
/function activateIndex\(index, fromPointer\)[\s\S]*?if \(!root\.rowSelectable\(index\)\) return/.test(menuQml),
|
||||||
|
'menu refuses to activate a disabled row'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
/function selectFromPointer\(index, item, mouse\)[\s\S]*?if \(!root\.rowSelectable\(index\)\) return/.test(menuQml)
|
||||||
|
&& /onClicked: \{\s*\n\s*if \(row\.disabled\) return/.test(menuQml),
|
||||||
|
'menu leaves the cursor put when the pointer crosses a disabled row'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
/opacity: row\.disabled \? 0\.4 : 1/.test(menuQml) && !/font\.italic/.test(menuQml),
|
||||||
|
'menu renders a disabled row faded, and leaves it at that'
|
||||||
|
)
|
||||||
|
assert(
|
||||||
|
/function rebuildDisplay\(\)[\s\S]*?root\.settleCursor\(\)/.test(menuQml),
|
||||||
|
'menu parks the cursor on a selectable row after the rows change'
|
||||||
|
)
|
||||||
|
// A menu with nothing selectable in it has no cursor, and Return must not
|
||||||
|
// conjure one onto a disabled row just because rows exist.
|
||||||
|
assert(
|
||||||
|
/function settleCursor\(\)[\s\S]*?root\.cursorActive = target >= 0/.test(menuQml)
|
||||||
|
&& /else if \(root\.cursorActive\) root\.activateIndex\(root\.selectedIndex\)\s*\n\s*else root\.settleCursor\(\)/.test(menuQml),
|
||||||
|
'menu ties the cursor to a selectable row existing, both ways'
|
||||||
|
)
|
||||||
assert(
|
assert(
|
||||||
/function setFilter\(nextFilter\)[\s\S]*root\.disarmPointer\(\)/.test(menuQml),
|
/function setFilter\(nextFilter\)[\s\S]*root\.disarmPointer\(\)/.test(menuQml),
|
||||||
'menu filter changes disarm pointer selection'
|
'menu filter changes disarm pointer selection'
|
||||||
|
|||||||
Reference in New Issue
Block a user