@@ -1,3 +1,4 @@
|
||||
import { ConditionState } from '../../conditions/types';
|
||||
import { FolderConfig, FolderType, folderTypeSchema } from '../../config/schema/folders';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
import { Endpoint } from '../../types';
|
||||
@@ -23,12 +24,14 @@ export class FoldersExecutor {
|
||||
public async expandFolder(
|
||||
hass: HomeAssistant,
|
||||
query: FolderQuery,
|
||||
conditionState?: ConditionState,
|
||||
engineOptions?: EngineOptions,
|
||||
): Promise<ViewItem[] | null> {
|
||||
const results =
|
||||
(await this._getFolderEngine(query.folder.type)?.expandFolder(
|
||||
hass,
|
||||
query,
|
||||
conditionState,
|
||||
engineOptions,
|
||||
)) ?? null;
|
||||
return results ? sortItems(results) : null;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NonEmptyTuple } from 'type-fest';
|
||||
import { ConditionState } from '../../../conditions/types';
|
||||
import {
|
||||
FolderConfig,
|
||||
folderTypeSchema,
|
||||
@@ -112,6 +113,7 @@ export class HAFoldersEngine implements FoldersEngine {
|
||||
public async expandFolder(
|
||||
hass: HomeAssistant,
|
||||
query: FolderQuery,
|
||||
conditionState?: ConditionState,
|
||||
engineOptions?: EngineOptions,
|
||||
): Promise<ViewItem[] | null> {
|
||||
if (query.folder.type !== folderTypeSchema.enum.ha) {
|
||||
@@ -163,13 +165,13 @@ export class HAFoldersEngine implements FoldersEngine {
|
||||
|
||||
...(nextComponent && {
|
||||
matcher: (media: BrowseMedia) =>
|
||||
this._mediaMatcher.match(
|
||||
media,
|
||||
nextComponent.ha?.matchers,
|
||||
this._mediaMatcher.match(hass, media, {
|
||||
matchers: nextComponent.ha?.matchers,
|
||||
// Set foldersOnly to true if there are more stages in the path,
|
||||
// as by definition only folders can be matched at this point.
|
||||
pathComponents.length > 0,
|
||||
),
|
||||
foldersOnly: pathComponents.length > 0,
|
||||
conditionState,
|
||||
}),
|
||||
advance: (targets) => (pathComponents.length ? generateStep(targets) : []),
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,29 +1,79 @@
|
||||
import { Matcher } from '../../../config/schema/folders';
|
||||
import { ConditionState } from '../../../conditions/types';
|
||||
import { Matcher, TemplateMatcher, TitleMatcher } from '../../../config/schema/folders';
|
||||
import { BrowseMedia } from '../../../ha/browse-media/types';
|
||||
import { HomeAssistant } from '../../../ha/types';
|
||||
import { regexpExtract } from '../../../utils/regexp-extract';
|
||||
import { TemplateRenderer } from '../../templates';
|
||||
import { REGEXP_GROUP_VALUE_KEY } from './types';
|
||||
|
||||
export class MediaMatcher {
|
||||
public match(media: BrowseMedia, matchers?: Matcher[], foldersOnly = false): boolean {
|
||||
if (foldersOnly && !media.can_expand) {
|
||||
private _templateRenderer = new TemplateRenderer();
|
||||
|
||||
public match(
|
||||
hass: HomeAssistant,
|
||||
media: BrowseMedia,
|
||||
options?: {
|
||||
foldersOnly?: boolean;
|
||||
matchers?: Matcher[];
|
||||
conditionState?: ConditionState;
|
||||
},
|
||||
): boolean {
|
||||
if (options?.foldersOnly && !media.can_expand) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const matcher of matchers ?? []) {
|
||||
if (matcher.type === 'title') {
|
||||
if (!this._matchTitle(matcher, media.title)) {
|
||||
return false;
|
||||
}
|
||||
for (const matcher of options?.matchers ?? []) {
|
||||
switch (matcher.type) {
|
||||
case 'template':
|
||||
if (!this._matchTemplate(hass, matcher, media, options?.conditionState)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'title':
|
||||
if (!this._matchTitle(matcher, media)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'or':
|
||||
if (
|
||||
!matcher.matchers.some((subMatcher) =>
|
||||
this.match(hass, media, {
|
||||
foldersOnly: options?.foldersOnly,
|
||||
matchers: [subMatcher],
|
||||
conditionState: options?.conditionState,
|
||||
}),
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _matchTitle(matcher: Matcher, src: string): boolean {
|
||||
private _matchTemplate(
|
||||
hass: HomeAssistant,
|
||||
matcher: TemplateMatcher,
|
||||
media: BrowseMedia,
|
||||
conditionState?: ConditionState,
|
||||
): boolean {
|
||||
return (
|
||||
this._templateRenderer.renderRecursively(hass, matcher.value_template, {
|
||||
conditionState,
|
||||
mediaData: {
|
||||
title: media.title,
|
||||
is_folder: media.can_expand,
|
||||
},
|
||||
}) === true
|
||||
);
|
||||
}
|
||||
|
||||
private _matchTitle(matcher: TitleMatcher, media: BrowseMedia): boolean {
|
||||
const valueToMatch = matcher.regexp
|
||||
? regexpExtract(matcher.regexp, src, { groupName: REGEXP_GROUP_VALUE_KEY })
|
||||
: src;
|
||||
? regexpExtract(matcher.regexp, media.title, { groupName: REGEXP_GROUP_VALUE_KEY })
|
||||
: media.title;
|
||||
|
||||
if (!valueToMatch) {
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { ConditionState } from '../../conditions/types';
|
||||
import { FolderConfig } from '../../config/schema/folders';
|
||||
import { localize } from '../../localize/localize';
|
||||
import { Endpoint } from '../../types';
|
||||
@@ -63,10 +64,13 @@ export class FoldersManager {
|
||||
|
||||
public async expandFolder(
|
||||
query: FolderQuery,
|
||||
conditionState?: ConditionState,
|
||||
engineOptions?: EngineOptions,
|
||||
): Promise<ViewItem[] | null> {
|
||||
const hass = this._api.getHASSManager().getHASS();
|
||||
return hass ? this._executor.expandFolder(hass, query, engineOptions) : null;
|
||||
return hass
|
||||
? this._executor.expandFolder(hass, query, conditionState, engineOptions)
|
||||
: null;
|
||||
}
|
||||
|
||||
public getItemCapabilities(item: ViewItem): ViewItemCapabilities | null {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { NonEmptyTuple } from 'type-fest';
|
||||
import { ConditionState } from '../../conditions/types';
|
||||
import { FolderConfig, HAFolderPathComponent } from '../../config/schema/folders';
|
||||
import { ResolvedMediaCache } from '../../ha/resolved-media';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
@@ -47,6 +48,7 @@ export interface FoldersEngine {
|
||||
expandFolder(
|
||||
hass: HomeAssistant,
|
||||
query: FolderQuery,
|
||||
conditionState?: ConditionState,
|
||||
engineOptions?: EngineOptions,
|
||||
): Promise<ViewItem[] | null>;
|
||||
|
||||
|
||||
@@ -2,10 +2,15 @@ import { HASS, renderTemplate } from 'ha-nunjucks/dist';
|
||||
import { ConditionState, ConditionsTriggerData } from '../../conditions/types';
|
||||
import { HomeAssistant } from '../../ha/types';
|
||||
|
||||
interface TemplateMediaData {
|
||||
title: string;
|
||||
is_folder: boolean;
|
||||
}
|
||||
interface TemplateContextInternal {
|
||||
camera?: string;
|
||||
view?: string;
|
||||
trigger?: ConditionsTriggerData;
|
||||
media?: TemplateMediaData;
|
||||
}
|
||||
|
||||
interface TemplateContext {
|
||||
@@ -22,30 +27,35 @@ export class TemplateRenderer {
|
||||
options?: {
|
||||
conditionState?: ConditionState;
|
||||
triggerData?: ConditionsTriggerData;
|
||||
mediaData?: TemplateMediaData;
|
||||
},
|
||||
): unknown => {
|
||||
return this._renderTemplateRecursively(
|
||||
hass,
|
||||
data,
|
||||
this._conditionStateToTemplateContext(
|
||||
options?.conditionState,
|
||||
options?.triggerData,
|
||||
),
|
||||
this._generateTemplateContext(options),
|
||||
);
|
||||
};
|
||||
|
||||
protected _conditionStateToTemplateContext(
|
||||
conditionState?: ConditionState,
|
||||
triggerData?: ConditionsTriggerData,
|
||||
): TemplateContext | undefined {
|
||||
if (!conditionState?.camera && !conditionState?.view && !triggerData) {
|
||||
protected _generateTemplateContext(options?: {
|
||||
conditionState?: ConditionState;
|
||||
triggerData?: ConditionsTriggerData;
|
||||
mediaData?: TemplateMediaData;
|
||||
}): TemplateContext | undefined {
|
||||
if (
|
||||
!options?.conditionState?.camera &&
|
||||
!options?.conditionState?.view &&
|
||||
!options?.triggerData &&
|
||||
!options?.mediaData
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const advancedCameraCardContext: TemplateContextInternal = {
|
||||
...(conditionState?.camera && { camera: conditionState.camera }),
|
||||
...(conditionState?.view && { view: conditionState.view }),
|
||||
...(triggerData && { trigger: triggerData }),
|
||||
...(options?.conditionState?.camera && { camera: options.conditionState.camera }),
|
||||
...(options?.conditionState?.view && { view: options.conditionState.view }),
|
||||
...(options?.triggerData && { trigger: options.triggerData }),
|
||||
...(options?.mediaData && { media: options.mediaData }),
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@@ -159,7 +159,9 @@ export class QueryExecutor {
|
||||
}
|
||||
const itemArray = await this._api
|
||||
.getFoldersManager()
|
||||
.expandFolder(rawQuery, { useCache: executorOptions?.useCache });
|
||||
.expandFolder(rawQuery, this._api.getConditionStateManager().getState(), {
|
||||
useCache: executorOptions?.useCache,
|
||||
});
|
||||
|
||||
const queryResults = itemArray
|
||||
? this._generateQueriesResults(itemArray, executorOptions)
|
||||
|
||||
Reference in New Issue
Block a user