From 671049beb21a67106626749c60cb380b9f5018e9 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Sun, 6 Apr 2025 09:02:52 +0200 Subject: [PATCH] Add dialog to show keyboard shortcuts (#24918) * Add dialog to show keyboard shortcuts we have * Add missing translation * No need for function anymore * Run updated prettier * Replace translation keys * Replace translation keys * Remove automations for now * Check whether shortcuts are enabled * Use plain css for shortcuts --- src/dialogs/shortcuts/dialog-shortcuts.ts | 213 ++++++++++++++++++ .../shortcuts/show-shortcuts-dialog.ts | 8 + .../config/dashboard/ha-config-dashboard.ts | 35 ++- src/panels/config/info/ha-config-info.ts | 32 ++- .../state/developer-tools-state.ts | 14 +- src/panels/lovelace/hui-root.ts | 37 ++- .../profile/ha-profile-section-general.ts | 19 ++ src/translations/en.json | 45 +++- 8 files changed, 378 insertions(+), 25 deletions(-) create mode 100644 src/dialogs/shortcuts/dialog-shortcuts.ts create mode 100644 src/dialogs/shortcuts/show-shortcuts-dialog.ts diff --git a/src/dialogs/shortcuts/dialog-shortcuts.ts b/src/dialogs/shortcuts/dialog-shortcuts.ts new file mode 100644 index 0000000000..01513fd025 --- /dev/null +++ b/src/dialogs/shortcuts/dialog-shortcuts.ts @@ -0,0 +1,213 @@ +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import { fireEvent } from "../../common/dom/fire_event"; +import "../../components/ha-button"; +import { createCloseHeading } from "../../components/ha-dialog"; +import type { HomeAssistant } from "../../types"; +import { haStyleDialog } from "../../resources/styles"; +import "../../components/ha-alert"; +import "../../components/chips/ha-assist-chip"; +import type { LocalizeKeys } from "../../common/translations/localize"; + +interface Text { + type: "text"; + key: LocalizeKeys; +} + +interface Shortcut { + type: "shortcut"; + shortcut: string[]; + key: LocalizeKeys; +} + +interface Section { + key: LocalizeKeys; + items: (Text | Shortcut)[]; +} + +const _SHORTCUTS: Section[] = [ + { + key: "ui.dialogs.shortcuts.searching.title", + items: [ + { type: "text", key: "ui.dialogs.shortcuts.searching.on_any_page" }, + { + type: "shortcut", + shortcut: ["C"], + key: "ui.dialogs.shortcuts.searching.search_command", + }, + { + type: "shortcut", + shortcut: ["E"], + key: "ui.dialogs.shortcuts.searching.search_entities", + }, + { + type: "shortcut", + shortcut: ["D"], + key: "ui.dialogs.shortcuts.searching.search_devices", + }, + { + type: "text", + key: "ui.dialogs.shortcuts.searching.on_pages_with_tables", + }, + { + type: "shortcut", + shortcut: ["CRTL/CMND", "F"], + key: "ui.dialogs.shortcuts.searching.search_in_table", + }, + ], + }, + { + key: "ui.dialogs.shortcuts.assist.title", + items: [ + { + type: "shortcut", + shortcut: ["A"], + key: "ui.dialogs.shortcuts.assist.open_assist", + }, + ], + }, + { + key: "ui.dialogs.shortcuts.charts.title", + items: [ + { + type: "shortcut", + shortcut: ["CRTL/CMND", "DRAG"], + key: "ui.dialogs.shortcuts.charts.drag_to_zoom", + }, + { + type: "shortcut", + shortcut: ["CRTL/CMND", "SCROLL WHEEL"], + key: "ui.dialogs.shortcuts.charts.scroll_to_zoom", + }, + { + type: "shortcut", + shortcut: ["DOUBLE CLICK"], + key: "ui.dialogs.shortcuts.charts.double_click", + }, + ], + }, + { + key: "ui.dialogs.shortcuts.other.title", + items: [ + { + type: "shortcut", + shortcut: ["M"], + key: "ui.dialogs.shortcuts.other.my_link", + }, + ], + }, +]; + +@customElement("dialog-shortcuts") +class DialogShortcuts extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _opened = false; + + public async showDialog(): Promise { + this._opened = true; + } + + public async closeDialog(): Promise { + this._opened = false; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + private _renderShortcut(shortcut: string[], translationKey: LocalizeKeys) { + return html` +
+ ${shortcut.map((key) => html` ${key}`)} + ${this.hass.localize(translationKey)} +
+ `; + } + + protected render() { + if (!this._opened) { + return nothing; + } + + return html` + +
+ ${_SHORTCUTS.map( + (section) => html` +

${this.hass.localize(section.key)}

+
+ ${section.items.map((item) => { + if (item.type === "text") { + return html`

${this.hass.localize(item.key)}

`; + } + if (item.type === "shortcut") { + return this._renderShortcut(item.shortcut, item.key); + } + return nothing; + })} +
+ ` + )} +
+ + + ${this.hass.localize("ui.dialogs.shortcuts.enable_shortcuts_hint", { + user_profile: html`${this.hass.localize( + "ui.dialogs.shortcuts.enable_shortcuts_hint_user_profile" + )}`, + })} + +
+ `; + } + + static styles = [ + haStyleDialog, + css` + ha-dialog { + --dialog-z-index: 15; + } + + h3:first-of-type { + margin-top: 0; + } + + .content { + margin-bottom: 24px; + } + + .shortcut { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + margin: 4px 0; + } + + span { + padding: 8px; + border: 1px solid var(--divider-color); + border-radius: 8px; + } + + .items p { + margin-bottom: 8px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-shortcuts": DialogShortcuts; + } +} diff --git a/src/dialogs/shortcuts/show-shortcuts-dialog.ts b/src/dialogs/shortcuts/show-shortcuts-dialog.ts new file mode 100644 index 0000000000..bf0912081d --- /dev/null +++ b/src/dialogs/shortcuts/show-shortcuts-dialog.ts @@ -0,0 +1,8 @@ +import { fireEvent } from "../../common/dom/fire_event"; + +export const showShortcutsDialog = (element: HTMLElement) => + fireEvent(element, "show-dialog", { + dialogTag: "dialog-shortcuts", + dialogImport: () => import("./dialog-shortcuts"), + dialogParams: {}, + }); diff --git a/src/panels/config/dashboard/ha-config-dashboard.ts b/src/panels/config/dashboard/ha-config-dashboard.ts index 726434b6de..f1f5280c6d 100644 --- a/src/panels/config/dashboard/ha-config-dashboard.ts +++ b/src/panels/config/dashboard/ha-config-dashboard.ts @@ -48,8 +48,9 @@ import { configSections } from "../ha-panel-config"; import "../repairs/ha-config-repairs"; import "./ha-config-navigation"; import "./ha-config-updates"; +import { showShortcutsDialog } from "../../../dialogs/shortcuts/show-shortcuts-dialog"; -const randomTip = (hass: HomeAssistant, narrow: boolean) => { +const randomTip = (openFn: any, hass: HomeAssistant, narrow: boolean) => { const weighted: string[] = []; let tips = [ { @@ -105,18 +106,28 @@ const randomTip = (hass: HomeAssistant, narrow: boolean) => { ]; if (hass?.enableShortcuts) { + const localizeParam = { + keyboard_shortcut: html`${hass.localize("ui.tips.keyboard_shortcut")}`, + }; + tips.push( { - content: hass.localize("ui.tips.key_c_hint"), + content: hass.localize("ui.tips.key_c_tip", localizeParam), weight: 1, narrow: false, }, { - content: hass.localize("ui.tips.key_m_hint"), + content: hass.localize("ui.tips.key_m_tip", localizeParam), weight: 1, narrow: false, }, - { content: hass.localize("ui.tips.key_a_hint"), weight: 1, narrow: false } + { + content: hass.localize("ui.tips.key_a_tip", localizeParam), + weight: 1, + narrow: false, + } ); } @@ -318,10 +329,16 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) { super.updated(changedProps); if (!this._tip && changedProps.has("hass")) { - this._tip = randomTip(this.hass, this.narrow); + this._tip = randomTip(this._openShortcutDialog, this.hass, this.narrow); } } + private _openShortcutDialog(ev: Event) { + ev.preventDefault(); + + showShortcutsDialog(this); + } + private _filterUpdateEntitiesWithInstall = memoizeOne( ( entities: HomeAssistant["states"], @@ -339,10 +356,16 @@ class HaConfigDashboard extends SubscribeMixin(LitElement) { ); private _showQuickBar(): void { + const params = { + keyboard_shortcut: html`${this.hass.localize("ui.tips.keyboard_shortcut")}`, + }; + showQuickBar(this, { mode: QuickBarMode.Command, hint: this.hass.enableShortcuts - ? this.hass.localize("ui.dialogs.quick-bar.key_c_hint") + ? this.hass.localize("ui.dialogs.quick-bar.key_c_tip", params) : undefined, }); } diff --git a/src/panels/config/info/ha-config-info.ts b/src/panels/config/info/ha-config-info.ts index 7823c48a5d..cebbacb4ff 100644 --- a/src/panels/config/info/ha-config-info.ts +++ b/src/panels/config/info/ha-config-info.ts @@ -4,7 +4,9 @@ import { mdiFileDocument, mdiHandsPray, mdiHelp, + mdiKeyboard, mdiNewspaperVariant, + mdiOpenInNew, mdiTshirtCrew, } from "@mdi/js"; import type { CSSResultGroup, TemplateResult } from "lit"; @@ -23,6 +25,8 @@ import { mdiHomeAssistant } from "../../../resources/home-assistant-logo-svg"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant, Route } from "../../../types"; import { documentationUrl } from "../../../util/documentation-url"; +import "../../../components/ha-icon-next"; +import { showShortcutsDialog } from "../../../dialogs/shortcuts/show-shortcuts-dialog"; const JS_TYPE = __BUILD__; const JS_VERSION = __VERSION__; @@ -170,12 +174,26 @@ class HaConfigInfo extends LitElement { + +
+ +
+ ${this.hass.localize("ui.panel.config.info.shortcuts")} +
+ ${PAGES.map( (page) => html`
+ ` )} @@ -240,6 +262,11 @@ class HaConfigInfo extends LitElement { this._osInfo = osInfo; } + private _showShortcuts(ev): void { + ev.stopPropagation(); + showShortcutsDialog(this); + } + static get styles(): CSSResultGroup { return [ haStyle, @@ -331,11 +358,12 @@ class HaConfigInfo extends LitElement { --mdc-list-vertical-padding: 0; } - ha-clickable-list-item { + ha-clickable-list-item, + ha-list-item { height: 64px; } - ha-svg-icon { + .icon-background ha-svg-icon { height: 24px; width: 24px; display: block; diff --git a/src/panels/developer-tools/state/developer-tools-state.ts b/src/panels/developer-tools/state/developer-tools-state.ts index 3137674b23..5f432dae87 100644 --- a/src/panels/developer-tools/state/developer-tools-state.ts +++ b/src/panels/developer-tools/state/developer-tools-state.ts @@ -33,6 +33,7 @@ import "../../../components/search-input"; import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; +import { showShortcutsDialog } from "../../../dialogs/shortcuts/show-shortcuts-dialog"; @customElement("developer-tools-state") class HaPanelDevState extends LitElement { @@ -130,7 +131,13 @@ class HaPanelDevState extends LitElement { > ${this.hass.enableShortcuts ? html`${this.hass.localize("ui.tips.key_e_hint")}${this.hass.localize("ui.tips.key_e_tip", { + keyboard_shortcut: html`${this.hass.localize("ui.tips.keyboard_shortcut")}`, + })}` : nothing} { result.push( - html`` + placement="bottom" + .content=${[this.hass!.localize(i.key), i.suffix].join(" ")} + > + + ` ); }); if (overflowItems.length && !overflowCanPromote) { @@ -263,7 +271,7 @@ class HUIRoot extends LitElement { graphic="icon" @request-selected=${i.overflowAction} > - ${this.hass!.localize(i.key)} + ${[this.hass!.localize(i.key), i.suffix].join(" ")} ` ); @@ -689,10 +697,16 @@ class HUIRoot extends LitElement { } private _showQuickBar(): void { + const params = { + keyboard_shortcut: html`${this.hass.localize("ui.tips.keyboard_shortcut")}`, + }; + showQuickBar(this, { mode: QuickBarMode.Entity, hint: this.hass.enableShortcuts - ? this.hass.localize("ui.tips.key_e_hint") + ? this.hass.localize("ui.tips.key_e_tip", params) : undefined, }); } @@ -1005,6 +1019,11 @@ class HUIRoot extends LitElement { root.appendChild(view); } + private _openShortcutDialog(ev: Event) { + ev.preventDefault(); + showShortcutsDialog(this); + } + static get styles(): CSSResultGroup { return [ haStyle, diff --git a/src/panels/profile/ha-profile-section-general.ts b/src/panels/profile/ha-profile-section-general.ts index 4cd71d7ed7..3c06368a80 100644 --- a/src/panels/profile/ha-profile-section-general.ts +++ b/src/panels/profile/ha-profile-section-general.ts @@ -27,6 +27,7 @@ import "./ha-pick-time-zone-row"; import "./ha-push-notifications-row"; import "./ha-set-suspend-row"; import "./ha-set-vibrate-row"; +import { nextRender } from "../../common/util/render-status"; @customElement("ha-profile-section-general") class HaProfileSectionGeneral extends LitElement { @@ -54,6 +55,8 @@ class HaProfileSectionGeneral extends LitElement { if (this.hass) { this._getCoreData(); } + + this._scrollToHash(); } public firstUpdated() { @@ -70,6 +73,21 @@ class HaProfileSectionGeneral extends LitElement { } } + private async _scrollToHash() { + await nextRender(); + + const hash = window.location.hash.substring(1); + if (hash) { + const element = this.shadowRoot!.getElementById(hash); + element?.scrollIntoView(); + this._clearHash(); + } + } + + private _clearHash() { + history.replaceState(null, "", window.location.pathname); + } + protected render(): TemplateResult { return html` diff --git a/src/translations/en.json b/src/translations/en.json index 0f0ccbf264..d886b1865f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1225,7 +1225,7 @@ "filter_placeholder": "Search entities", "filter_placeholder_devices": "Search devices", "title": "Quick search", - "key_c_hint": "Press 'c' on any page to open the command dialog", + "key_c_tip": "[%key:ui::tips::key_c_tip%]", "nothing_found": "Nothing found!" }, "voice_command": { @@ -1890,6 +1890,34 @@ "code_instructions": "Search for the sharing mode in the app of your controller, and activate it. You will get a setup code, enter that below.", "setup_code": "Setup code" } + }, + "shortcuts": { + "title": "Shortcuts", + "enable_shortcuts_hint": "For keyboard shortcuts to work, make sure you have them enabled in your {user_profile}.", + "enable_shortcuts_hint_user_profile": "user profile", + "searching": { + "title": "Searching", + "on_any_page": "On any page", + "on_pages_with_tables": "On pages with tables", + "search_command": "search command", + "search_entities": "search entities", + "search_devices": "search devices", + "search_in_table": "to search in tables" + }, + "assist": { + "title": "Assist", + "open_assist": "open Assist dialog" + }, + "charts": { + "title": "Charts", + "drag_to_zoom": "to zoom in part of a chart", + "scroll_to_zoom": "to zoom in or out", + "double_click": "to zoom in on part of the chart" + }, + "other": { + "title": "Other", + "my_link": "get My Home Assistant link" + } } }, "weekdays": { @@ -3052,7 +3080,8 @@ "bug": "Bug reports", "help": "Help", "license": "License" - } + }, + "shortcuts": "Shortcuts" }, "logs": { "caption": "Logs", @@ -6507,8 +6536,9 @@ "menu": { "configure_ui": "Edit dashboard", "help": "Help", - "search": "Entity search", + "search_entities": "Search entities", "assist": "Assist", + "assist_tooltip": "Assist", "reload_resources": "Reload resources", "exit_edit_mode": "Done", "close": "Close" @@ -8554,10 +8584,11 @@ } }, "tips": { - "key_c_hint": "Press 'c' on any page to open the command dialog", - "key_e_hint": "Press 'e' on any page to open the entity search dialog", - "key_m_hint": "Press 'm' on any page to get the My Home Assistant link", - "key_a_hint": "Press 'a' on any page to open the Assist dialog" + "keyboard_shortcut": "keyboard shortcut", + "key_c_tip": "Press {keyboard_shortcut} 'c' on any page to open the command dialog", + "key_e_tip": "Press {keyboard_shortcut} 'e' on any page to open the entity search dialog", + "key_m_tip": "Press {keyboard_shortcut} 'm' on any page to get the My Home Assistant link", + "key_a_tip": "Press {keyboard_shortcut} 'a' on any page to open the Assist dialog" } }, "landing-page": {