Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/common/string/sequence_matching.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
*
* return true if word contains sequence. Otherwise false.
*/
export const fuzzySequentialMatch = (filter: string, word: string) => {
export const fuzzySequentialMatch = (filter: string, words: string[]) => {
for (const word of words) {
if (_fuzzySequentialMatch(filter, word)) {
return true;
}
}
return false;
};

const _fuzzySequentialMatch = (filter: string, word: string) => {
if (filter === "") {
return true;
}
Expand All @@ -22,7 +31,7 @@ export const fuzzySequentialMatch = (filter: string, word: string) => {
const newWord = word.substring(pos + 1);
const newFilter = filter.substring(1);

return fuzzySequentialMatch(newFilter, newWord);
return _fuzzySequentialMatch(newFilter, newWord);
}

return true;
Expand Down
63 changes: 42 additions & 21 deletions src/dialogs/quick-bar/ha-quick-bar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export class QuickBar extends LitElement {

@internalProperty() private _commandItems: CommandItem[] = [];

@internalProperty() private _entities: HassEntity[] = [];

@internalProperty() private _itemFilter = "";

@internalProperty() private _opened = false;
Expand All @@ -46,6 +48,9 @@ export class QuickBar extends LitElement {
this._commandMode = params.commandMode || false;
this._opened = true;
this._commandItems = this._generateCommandItems();
this._entities = Object.keys(this.hass.states).map<HassEntity>(
(entity_id) => this.hass.states[entity_id]
);
}

public closeDialog() {
Expand Down Expand Up @@ -101,19 +106,31 @@ export class QuickBar extends LitElement {
});

protected renderEntityList = memoizeOne((filter) => {
const entities = this._filterEntityItems(
Object.keys(this.hass.states),
filter
);
const entities = this._filterEntityItems(filter);

return html`
<mwc-list activatable @selected=${this._entityMoreInfo}>
${entities.map((entityId) => {
const domain = computeDomain(entityId);
${entities.map((entity) => {
const domain = computeDomain(entity.entity_id);
return html`
<mwc-list-item graphic="icon" .entityId=${entityId}>
<mwc-list-item
twoline
.entityId=${entity.entity_id}
graphic="avatar"
>
<ha-icon .icon=${domainIcon(domain)} slot="graphic"></ha-icon>
${entityId}
${entity.attributes?.friendly_name
? html`
<span>
${entity.attributes?.friendly_name}
</span>
<span slot="secondary">${entity.entity_id}</span>
`
: html`
<span>
${entity.entity_id}
</span>
`}
</mwc-list-item>
`;
})}
Expand Down Expand Up @@ -155,36 +172,40 @@ export class QuickBar extends LitElement {
): CommandItem[] {
return items
.filter(({ text }) =>
fuzzySequentialMatch(filter.toLowerCase(), text.toLowerCase())
fuzzySequentialMatch(filter.toLowerCase(), [text.toLowerCase()])
)
.sort((itemA, itemB) => compare(itemA.text, itemB.text));
}

private _filterEntityItems(
entityIds: HassEntity["entity_id"][],
filter: string
): HassEntity["entity_id"][] {
return entityIds
.filter((entityId) =>
fuzzySequentialMatch(filter.toLowerCase(), entityId)
private _filterEntityItems(filter: string): HassEntity[] {
return this._entities
.filter(({ entity_id, attributes: { friendly_name } }) => {
const values = [entity_id];
if (friendly_name) {
values.push(friendly_name);
}
return fuzzySequentialMatch(filter.toLowerCase(), values);}
)
.sort();
.sort((entityA, entityB) =>
compare(entityA.entity_id, entityB.entity_id)
);
}

private async _processItemAndCloseDialog(ev: SingleSelectedEvent) {
const index = ev.detail.index;
const item = (ev.target as any).items[index].item;
const _index = ev.detail.index;
const item = (ev.target as any).items[_index].item;

await this.hass.callService(item.domain, item.service, item.serviceData);

this.closeDialog();
}

private _entityMoreInfo(ev: SingleSelectedEvent) {
const index = ev.detail.index;
const entityId = (ev.target as any).items[index].entityId;
const _index = ev.detail.index;
const entityId = (ev.target as any).items[_index].entityId;

fireEvent(this, "hass-more-info", { entityId });

this.closeDialog();
}

Expand Down
19 changes: 15 additions & 4 deletions test-mocha/common/string/sequence_matching.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { assert } from "chai";
import { fuzzySequentialMatch } from "../../../src/common/string/sequence_matching";

describe("fuzzySequentialMatch", () => {
const entityId = "automation.ticker";
const entity = { entity_id: "automation.ticker", friendly_name: "Stocks" };

const shouldMatchEntity = [
"",
Expand All @@ -23,6 +23,9 @@ describe("fuzzySequentialMatch", () => {
"uoaintce",
"au.tce",
"tomaontkr",
"s",
"stocks",
"sks",
];

const shouldNotMatchEntity = [
Expand All @@ -32,19 +35,27 @@ describe("fuzzySequentialMatch", () => {
"automation. ticke",
"1",
"noitamotua",
"autostocks",
"stox",
];

describe(`Entity '${entityId}'`, () => {
describe(`Entity '${entity.entity_id}'`, () => {
for (const goodFilter of shouldMatchEntity) {
it(`matches with '${goodFilter}'`, () => {
const res = fuzzySequentialMatch(goodFilter, entityId);
const res = fuzzySequentialMatch(goodFilter, [
entity.entity_id,
entity.friendly_name.toLowerCase(),
]);
assert.equal(res, true);
});
}

for (const badFilter of shouldNotMatchEntity) {
it(`fails to match with '${badFilter}'`, () => {
const res = fuzzySequentialMatch(badFilter, entityId);
const res = fuzzySequentialMatch(badFilter, [
entity.entity_id,
entity.friendly_name,
]);
assert.equal(res, false);
});
}
Expand Down