├── .github └── workflows │ └── hacs.yml ├── LICENSE ├── README.md ├── ha-navbar-position.js ├── hacs.json └── images └── screenshot.png /.github/workflows/hacs.yml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | hacs: 11 | name: HACS Action 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - uses: "actions/checkout@v2" 15 | - name: HACS Action 16 | uses: "hacs/action@main" 17 | with: 18 | category: "plugin" 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Alex Boyd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Navbar Position 2 | 3 | A Home Assistant frontend plugin that allows the navigation bar to be moved to the bottom of the screen. 4 | 5 | # Note: This repo has been updated to work with HA versions > 2023.04, it will not work on anything lower, please keep this in mind if you open an issue and are using an older version. 6 | 7 | ![screenshot](images/screenshot.png) 8 | 9 | # Installation 10 | 11 | Install via HACS by searching for "Navbar Position". 12 | 13 | Or install manually by dropping `ha-navbar-position.js` into your Lovelace dashboard resources. 14 | 15 | # Usage 16 | 17 | ## Via the URL 18 | 19 | The primary way this plugin is activated is by adding `navbar=bottom` to the dashboard URL, like so: 20 | 21 | ``` 22 | http://:8123/some-dashboard/some-view?navbar=bottom 23 | ``` 24 | 25 | The navigation bar will be moved to the bottom of the screen until the next page refresh. 26 | 27 | To save the navigation bar position to `localStorage` and move the navbar to the bottom automatically without having to specify `navbar=bottom` every time, use `navbar_cache`: 28 | 29 | ``` 30 | http://:8123/some-dashboard/some-view?navbar=bottom&navbar_cache 31 | ``` 32 | 33 | Thereafter, page loads that don't specify any `navbar` query parameters will default to moving the navbar to the bottom. 34 | 35 | To clear the saved position and return the navbar to the top on future page loads, use `navbar_cache` without `navbar=...`: 36 | 37 | ``` 38 | http://:8123/some-dashboard/some-view?navbar_cache 39 | ``` 40 | 41 | There will likely be additional ways to configure this plugin in the future - pull requests gladly accepted. 42 | 43 | ## Via custom card 44 | 45 | For scenarios where it isn't possible to edit the URL directly (e.g. when using the Home Assistant mobile apps), you can toggle the navigation bar position on a per-device basis using the `Navbar Position Configuration Card` custom card. 46 | 47 | Add it to a dashboard of your choosing, then click the card's "toggle navigation bar position" button on any device where you want to move the navbar to the bottom of the screen. The button will save the change to `localStorage`; you can then get rid of the card and the navbar will continue to show up at the bottom of the screen on the device in question. 48 | 49 | # Limitations 50 | 51 | The page is scanned every second to see if the header has appeared or has been replaced. This means that while switching between views within a single dashboard leaves the navbar at the bottom, **switching between dashboards or other pages in the Home Assistant UI will cause the navbar to jump back to the top for ~1 second when you return to a dashboard.** PRs gladly accepted to sprinkle some more `MutationObserver`s around to get rid of that delay. 52 | 53 | This also only moves the dashboard navigation bar; it doesn't move navigation bars associated with any other screens. PRs welcome to handle that as well. 54 | 55 | -------------------------------------------------------------------------------- /ha-navbar-position.js: -------------------------------------------------------------------------------- 1 | class NavbarPosition { 2 | constructor() { 3 | this.queryParams = new URLSearchParams(window.location.search); 4 | } 5 | 6 | start() { 7 | let navbarPosition; 8 | 9 | if (this.queryParams.get('navbar_cache') !== null) { 10 | navbarPosition = this.queryParams.get('navbar'); 11 | 12 | if (navbarPosition) { 13 | window.localStorage.setItem('navbar_position', navbarPosition); 14 | } else { 15 | window.localStorage.removeItem('navbar_position'); 16 | } 17 | } else if (this.queryParams.get('navbar') !== null) { 18 | navbarPosition = this.queryParams.get('navbar'); 19 | } else { 20 | navbarPosition = window.localStorage.getItem('navbar_position'); 21 | } 22 | 23 | if (navbarPosition === 'bottom') { 24 | this.applyChangesAndReschedule(); 25 | } 26 | } 27 | 28 | applyChangesAndReschedule() { 29 | try { 30 | this.applyNavbarPositionChanges(); 31 | this.applyPaddingChanges(); 32 | } catch (e) { 33 | console.error('ERROR while applying navbar changes:', e); 34 | } finally { 35 | setTimeout(() => this.applyChangesAndReschedule(), 1000); 36 | } 37 | } 38 | 39 | get huiRootElement() { 40 | return document 41 | .querySelector("home-assistant")?.shadowRoot 42 | ?.querySelector("home-assistant-main")?.shadowRoot 43 | ?.querySelector("ha-panel-lovelace")?.shadowRoot 44 | ?.querySelector("hui-root")?.shadowRoot; 45 | } 46 | 47 | applyNavbarPositionChanges() { 48 | let appHeader = this.huiRootElement?.querySelector(".header"); 49 | 50 | if (appHeader && (appHeader.style.top !== 'auto' || appHeader.style.bottom !== '0px')) { 51 | appHeader.style.setProperty('top', 'auto', 'important'); 52 | appHeader.style.setProperty('bottom', '0px', 'important'); 53 | } 54 | } 55 | 56 | applyPaddingChanges() { 57 | let contentContainer = this.huiRootElement?.querySelector("#view"); 58 | 59 | const topPadding = 'env(safe-area-inset-top)'; 60 | const bottomPadding = 'calc(var(--header-height) + env(safe-area-inset-bottom))'; 61 | 62 | if (contentContainer) { 63 | if (contentContainer.style.top !== topPadding || contentContainer.style.paddingBottom !== bottomPadding) { 64 | contentContainer.style.setProperty('padding-top', 'env(safe-area-inset-top)'); 65 | contentContainer.style.setProperty('padding-bottom', 'calc(var(--header-height) + env(safe-area-inset-bottom))'); 66 | } 67 | } 68 | } 69 | } 70 | 71 | window.navbarPosition = new NavbarPosition(); 72 | window.navbarPosition.start(); 73 | 74 | // This is the quickest, hackiest thing I could throw together to allow the 75 | // navbar to be moved on devices that don't readily allow inputting a custom 76 | // URL (like the HA mobile apps). It really ought to be redone to actually 77 | // look halfway decent. 78 | class NavbarPositionConfigurationCard extends HTMLElement { 79 | set hass(hass) { 80 | if (!this.content) { 81 | this.innerHTML = ` 82 | 83 |
84 | 85 |
86 |
87 | `; 88 | } 89 | } 90 | 91 | setConfig(config) { 92 | } 93 | } 94 | 95 | customElements.define('navbar-position-configuration-card', NavbarPositionConfigurationCard); 96 | window.customCards.push({ 97 | type: 'navbar-position-configuration-card', 98 | name: 'Navbar Position Configuration Card', 99 | description: 'A simple card that allows toggling where the dashboard navigation bar is shown' 100 | }); 101 | -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Navbar Position", 3 | "render_readme": true 4 | } 5 | -------------------------------------------------------------------------------- /images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javawizard/ha-navbar-position/c5a38a15ae281fa4639c9d90646c3b95448e497c/images/screenshot.png --------------------------------------------------------------------------------