Skip to content

Commit

Permalink
Query elements using home-assistant-query-selector
Browse files Browse the repository at this point in the history
  • Loading branch information
elchininet committed Nov 21, 2023
1 parent 7d6862b commit f84b7ad
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 174 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@
},
"devDependencies": {
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"rollup": "^3.28.1",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-ts": "^3.4.4",
"typescript": "^5.2.2"
},
"dependencies": {
"home-assistant-query-selector": "^1.1.3"
}
}
2 changes: 2 additions & 0 deletions rollup.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import ts from 'rollup-plugin-ts';
import json from '@rollup/plugin-json';
import { terser } from 'rollup-plugin-terser';
import { nodeResolve } from '@rollup/plugin-node-resolve';

export default {
plugins: [
nodeResolve(),
json(),
ts({
browserslist: false
Expand Down
33 changes: 0 additions & 33 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,15 @@
export const NAMESPACE = 'keep-texts-in-tabs';
export const MAX_ATTEMPTS = 500;
export const RETRY_DELAY = 50;
export const SHADOW_ROOT_SUFFIX = ':shadowRoot';
export const ARIA_LABEL_ATTRIBUTE = 'aria-label';
export const DEFAULT_MOBILE_WIDTH = 640;
export const WINDOW_RESIZE_DELAY = 100;

export enum ELEMENT {
HOME_ASSISTANT = 'home-assistant',
HOME_ASSISTANT_MAIN = 'home-assistant-main',
PARTIAL_PANEL_RESOLVER = 'partial-panel-resolver',
HA_PANEL_LOVELACE = 'ha-panel-lovelace',
HUI_ROOT = 'hui-root',
TOOLBAR = '.toolbar',
HA_TABS = 'ha-tabs',
PAPER_TABS = 'paper-tabs',
PAPER_TAB = 'paper-tab',
HUI_VIEW = 'hui-view',
HA_ICON = 'ha-icon'
/*
MENU_ITEM = 'ha-icon-button',
MENU_ITEM_ICON = 'mwc-icon-button',
BUTTON_MENU = 'ha-button-menu',
OVERLAY_MENU_ITEM = 'mwc-list-item',
HA_SIDEBAR = 'ha-sidebar',
HA_DRAWER = 'ha-drawer',
ACTION_ITEMS = '.action-items',
HA_MORE_INFO_DIALOG = 'ha-more-info-dialog',
HA_DIALOG = 'ha-dialog',
HA_DIALOG_HEADER = 'ha-dialog-header',
HA_DIALOG_CONTENT = '.content',
HA_DIALOG_MORE_INFO = 'ha-more-info-info',
HA_DIALOG_HISTORY = 'ha-more-info-history',
HA_DIALOG_LOGBOOK = 'ha-more-info-logbook',
HA_DIALOG_MORE_INFO_CONTENT = 'more-info-content',
HA_DIALOG_MORE_INFO_HISTORY_AND_LOGBOOK = 'ha-more-info-history-and-logbook',
HA_DIALOG_DEFAULT = 'more-info-default',
HA_DIALOG_TIMER = 'more-info-timer',
HA_DIALOG_VACUUM = 'more-info-vacuum',
HA_DIALOG_MEDIA_PLAYER = 'more-info-media_player',
HA_DIALOG_UPDATE = 'more-info-update',
HA_DIALOG_CLIMATE = 'more-info-climate',
HA_DIALOG_ATTRIBUTES = 'ha-attributes'*/
}

export enum NODE_TYPE {
Expand Down
132 changes: 23 additions & 109 deletions src/keep-texts-in-tabs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { HAQuerySelector } from 'home-assistant-query-selector';
import {
Lovelace,
KeepTextsInTabsConfig,
Expand All @@ -8,14 +9,12 @@ import {
import {
NAMESPACE,
ELEMENT,
SHADOW_ROOT_SUFFIX,
ARIA_LABEL_ATTRIBUTE,
DEFAULT_MOBILE_WIDTH,
NODE_TYPE,
WINDOW_RESIZE_DELAY
} from '@constants';
import {
getPromisableElement,
getSpan,
addStyle,
logVersionToConsole
Expand All @@ -24,103 +23,41 @@ import {
class KeepTextsInTabs {

constructor() {
this.resizeWindowBinded = this.resizeWindow.bind(this);
this.start();
const selector = new HAQuerySelector();
selector.addEventListener('onLovelacePanelLoad', async (event) => {
const {
HA_PANEL_LOVELACE,
HUI_ROOT,
HEADER
} = event.detail;

this.lovelace = await HA_PANEL_LOVELACE.element as Lovelace;
this.huiRoot = await HUI_ROOT.shadowRootQuerySelector('$');
this.appToolbar = await HEADER.querySelector(ELEMENT.TOOLBAR);
this.run();
});
selector.listen();
this.resizeWindowBinded = this.resizeWindow.bind(this);
window.addEventListener('resize', this.resizeWindowBinded);
}

private ha: HTMLElement;
private main: ShadowRoot;
private partialPanelResolver: HTMLElement;
private lovelace: Lovelace;
private huiRoot: ShadowRoot;
private appToolbar: HTMLElement;
private appToolbar: Element;

private panelResolverObserver: MutationObserver;
private lovelaceResolver: MutationObserver;
private toolBarObserver: MutationObserver;
private paperTabsEditionResolver: MutationObserver;

private resizeDelay: number;
private resizeWindowBinded: () => void;

protected async start() {

this.ha = await getPromisableElement(
(): HTMLElement => document.querySelector<HTMLElement>(ELEMENT.HOME_ASSISTANT),
(ha: HTMLElement) => !!(ha && ha.shadowRoot),
ELEMENT.HOME_ASSISTANT
);

this.main = await getPromisableElement(
(): ShadowRoot => this.ha.shadowRoot.querySelector(ELEMENT.HOME_ASSISTANT_MAIN)?.shadowRoot,
(main: ShadowRoot) => !!main,
`${ELEMENT.HOME_ASSISTANT_MAIN}${SHADOW_ROOT_SUFFIX}`
);

this.partialPanelResolver = await getPromisableElement(
(): HTMLElement => this.main.querySelector<HTMLElement>(ELEMENT.PARTIAL_PANEL_RESOLVER),
(partialPanelResolver: HTMLElement) => !!partialPanelResolver,
`${ELEMENT.HOME_ASSISTANT_MAIN} > ${ELEMENT.PARTIAL_PANEL_RESOLVER}`
);

this.lovelace = await getPromisableElement(
(): Lovelace => this.main.querySelector<Lovelace>(ELEMENT.HA_PANEL_LOVELACE),
(lovelace: Lovelace) => !!lovelace,
`${ELEMENT.HOME_ASSISTANT_MAIN} > ${ELEMENT.HA_PANEL_LOVELACE}`
);

if (this.panelResolverObserver) {
this.panelResolverObserver.disconnect();
}

this.panelResolverObserver = new MutationObserver(this.dashboardChanged.bind(this));

this.panelResolverObserver.observe(this.partialPanelResolver, {
childList: true,
});

window.removeEventListener('resize', this.resizeWindowBinded);
window.addEventListener('resize', this.resizeWindowBinded);

this.run();

}

protected async run() {

if (this.lovelaceResolver) {
this.lovelaceResolver.disconnect();
}

if (this.toolBarObserver) {
this.toolBarObserver.disconnect();
}

this.huiRoot = await getPromisableElement(
(): ShadowRoot => this.lovelace?.shadowRoot?.querySelector(ELEMENT.HUI_ROOT)?.shadowRoot,
(huiRoot: ShadowRoot) => !!huiRoot,
`${ELEMENT.HOME_ASSISTANT_MAIN} > ${ELEMENT.HA_PANEL_LOVELACE} > ${ELEMENT.HUI_ROOT}${SHADOW_ROOT_SUFFIX}`
);

this.appToolbar = await getPromisableElement(
(): HTMLElement => this.huiRoot.querySelector<HTMLElement>(ELEMENT.TOOLBAR),
(appToolbar: HTMLElement) => !!appToolbar,
`${ELEMENT.HOME_ASSISTANT_MAIN} > ${ELEMENT.HA_PANEL_LOVELACE} > ${ELEMENT.HUI_ROOT}${SHADOW_ROOT_SUFFIX} > ${ELEMENT.TOOLBAR}`
);

// Get the configuration and process it
const config = await getPromisableElement(
() => this.lovelace?.lovelace?.config,
(config: Lovelace['lovelace']['config']) => !!config,
'Lovelace config'
);
this.toolBarObserver?.disconnect();

addStyle(this.appToolbar);

this.lovelaceResolver = new MutationObserver(this.lovelaceChanged.bind(this));
this.lovelaceResolver.observe(this.lovelace.shadowRoot, {
childList: true,
});
// Get the configuration and process it
const config = this.lovelace.lovelace.config;

this.toolBarObserver = new MutationObserver(this.process.bind(this, config.keep_texts_in_tabs));
this.toolBarObserver.observe(this.appToolbar, {
Expand All @@ -133,9 +70,7 @@ class KeepTextsInTabs {

protected process(config: KeepTextsInTabsConfig | undefined) {

if (this.paperTabsEditionResolver) {
this.paperTabsEditionResolver.disconnect();
}
this.paperTabsEditionResolver?.disconnect();

if (!config) return;

Expand Down Expand Up @@ -243,31 +178,10 @@ class KeepTextsInTabs {
}
}

protected dashboardChanged(mutations: MutationRecord[]) {
mutations.forEach(({ addedNodes }): void => {
addedNodes.forEach((node: Element): void => {
if (node.localName === ELEMENT.HA_PANEL_LOVELACE) {
this.lovelace = node as Lovelace;
this.run();
}
});
});
}

protected lovelaceChanged(mutations: MutationRecord[]) {
mutations.forEach(({ addedNodes }): void => {
addedNodes.forEach((node: Element): void => {
if (node.localName === ELEMENT.HUI_ROOT) {
this.run();
}
});
});
}

protected resizeWindow() {
window.clearTimeout(this.resizeDelay);
this.resizeDelay = window.setTimeout(() => {
this.start();
this.run();
}, WINDOW_RESIZE_DELAY);
}

Expand Down
36 changes: 4 additions & 32 deletions src/utilities/index.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,7 @@
import { Position } from '@types';
import {
NAMESPACE,
MAX_ATTEMPTS,
RETRY_DELAY
} from '@constants';
import { NAMESPACE } from '@constants';
import { version } from '../../package.json';

export const getPromisableElement = <T>(
getElement: () => T,
check: (element: T) => boolean,
elementName: string
): Promise<T> => {
return new Promise<T>((resolve, reject) => {
let attempts = 0;
const select = () => {
const element: T = getElement();
if (element && check(element)) {
resolve(element);
} else {
attempts++;
if (attempts < MAX_ATTEMPTS) {
setTimeout(select, RETRY_DELAY);
} else {
reject(new Error(`${NAMESPACE}: Cannot select ${elementName} after ${MAX_ATTEMPTS} attempts. Giving up!`));
}
}
};
select();
});
};

export const getSpan = (text: string, position: Position): HTMLSpanElement => {
const textNode = document.createTextNode(text);
const span = document.createElement('span');
Expand All @@ -52,11 +24,11 @@ const buildStyles = (): string => {
`;
};

const styleExists = (elem: HTMLElement): HTMLStyleElement => {
const styleExists = (elem: Element): HTMLStyleElement => {
return elem.querySelector<HTMLStyleElement>(`#${NAMESPACE}`);
};

export const addStyle = (elem: HTMLElement): void => {
export const addStyle = (elem: Element): void => {
let style = styleExists(elem);
if (!style) {
style = document.createElement('style');
Expand All @@ -66,7 +38,7 @@ export const addStyle = (elem: HTMLElement): void => {
style.innerHTML = buildStyles();
};

export const removeStyle = (element: HTMLElement): void => {
export const removeStyle = (element: Element): void => {
if (styleExists(element)) {
element.querySelector(`#${NAMESPACE}`).remove();
}
Expand Down
Loading

0 comments on commit f84b7ad

Please sign in to comment.