diff --git a/.gitignore b/.gitignore index 68327c31..cb2e0455 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.rpt2_cache/ package-lock.json /dist +/.yarn/ .env .envrc diff --git a/README.md b/README.md index adf07ca8..6898a9ac 100644 --- a/README.md +++ b/README.md @@ -392,7 +392,7 @@ live: | `show_details` | `false` | :white_check_mark: | Whether to show event details (e.g. duration, start time, object detected, etc) alongside the thumbnail.| | `show_favorite_control` | `true` | :white_check_mark: | Whether to show the favorite ('star') control on each thumbnail.| | `show_timeline_control` | `true` | :white_check_mark: | Whether to show the timeline ('target') control on each thumbnail.| -| `media` | `clips` | :white_check_mark: | Whether to show `clips` or `snapshots` in the thumbnail carousel in the `live` view.| +| `media` | `all` | :white_check_mark: | Whether to show `clips`, `snapshots` or `all` in the thumbnail carousel in the `live` view.| #### Live Controls: Next / Previous @@ -548,11 +548,39 @@ See the [fully expanded event gallery configuration example](#config-expanded-ev | Option | Default | Overridable | Description | | - | - | - | - | -| `size` | 100 | :heavy_multiplication_x: | The size of the thumbnails in the event gallery in pixels. Must be >= `75` and <= `175`.| -| `show_details` | `false` | :heavy_multiplication_x: | Whether to show event details (e.g. duration, start time, object detected, etc) alongside the thumbnail.| +| `controls` | | :heavy_multiplication_x: | Configuration for the Media viewer controls. See below. | +| `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `event_gallery` (e.g. `clips`, `snapshots`, `recordings`). See [actions](#actions) below.| + +#### Event Gallery Controls: Filter + +All configuration is under: + +```yaml +event_gallery: + controls: + filter: +``` + +| Option | Default | Overridable | Description | +| - | - | - | - | +| `mode` | `right` | :heavy_multiplication_x: | Whether to show the gallery media filter to the `left`, to the `right` or `none` for no media filter. | + +#### Event Gallery Controls: Thumbnails + +All configuration is under: + +```yaml +event_gallery: + controls: + thumbnails: +``` + +| Option | Default | Overridable | Description | +| - | - | - | - | +| `size` | 100 | :heavy_multiplication_x: | The size of the thumbnails in the gallery. Must be >= `75` and <= `175`.| +| `show_details` | `false` | :heavy_multiplication_x: | Whether to show media details (e.g. duration, start time, object detected, etc) alongside the thumbnail.| | `show_favorite_control` | `true` | :heavy_multiplication_x: | Whether to show the favorite ('star') control on each thumbnail.| | `show_timeline_control` | `true` | :heavy_multiplication_x: | Whether to show the timeline ('target') control on each thumbnail.| -| `actions` | | :heavy_multiplication_x: | Actions to use for all views that use the `event_gallery` (e.g. `clips`, `snapshots`, `recordings`). See [actions](#actions) below.| ### Image Options @@ -1575,6 +1603,8 @@ Reference: [Event Gallery Options](#event-gallery-options). ```yaml event_gallery: controls: + filter: + mode: 'right' thumbnails: size: 100 show_details: false diff --git a/package.json b/package.json index 69f28695..d7731871 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,8 @@ "dependencies": { "@cycjimmy/jsmpeg-player": "^6.0.4", "@egjs/hammerjs": "^2.0.17", + "@graphiteds/core": "^1.9.6", + "@lit-labs/scoped-registry-mixin": "^1.0.1", "@lit-labs/task": "^1.1.3", "@types/bluebird": "^3.5.36", "component-emitter": "^1.3.0", @@ -37,11 +39,11 @@ "side-drawer": "^3.1.0", "ts-toolbelt": "^9.6.0", "uuid": "^8.3.2", - "vis-data": "^7.1.3", + "vis-data": "^7.1.4", "vis-timeline": "^7.7.0", "vis-util": "^5.0.2", "xss": "^1.0.14", - "zod": "^3.19.0" + "zod": "^3.20.2" }, "devDependencies": { "@babel/core": "^7.19.0", @@ -70,12 +72,14 @@ "rollup-plugin-typescript2": "^0.33.0", "rollup-plugin-visualizer": "^5.8.2", "sass": "^1.54.9", + "ts-prune": "^0.10.3", "typescript": "^4.8.3" }, "scripts": { "start": "rollup -c --watch", "build": "yarn run lint && yarn run rollup", "lint": "eslint 'src/**/*.ts'", - "rollup": "rollup -c" + "rollup": "rollup -c", + "prune": "ts-prune" } } diff --git a/src/action-handler-directive.ts b/src/action-handler-directive.ts index b1e52948..d62eb71e 100644 --- a/src/action-handler-directive.ts +++ b/src/action-handler-directive.ts @@ -165,7 +165,7 @@ const getActionHandler = (): ActionHandler => { return actionhandler as ActionHandler; }; -export const actionHandlerBind = ( +const actionHandlerBind = ( element: ActionHandlerElement, options?: FrigateCardActionHandlerOptions, ): void => { diff --git a/src/camera-manager/cache.ts b/src/camera-manager/cache.ts new file mode 100644 index 00000000..291810da --- /dev/null +++ b/src/camera-manager/cache.ts @@ -0,0 +1,159 @@ +import isEqual from 'lodash-es/isEqual'; +import orderBy from 'lodash-es/orderBy'; +import sortedUniqBy from 'lodash-es/sortedUniqBy'; +import { DateRange, MemoryRangeSet } from './range'; +import { DataQuery, QueryResults, RecordingSegment } from './types'; + +interface RequestCacheItem { + request: Request; + response: Response; + expires?: Date; +} + +interface CameraManagerCache { + get(request: Request): Response | null; + has(request: Request): boolean; + set(request: Request, response: Response, expiry?: Date): void; +} + +class MemoryRequestCache + implements CameraManagerCache +{ + protected _data: RequestCacheItem[] = []; + + public get(request: Request): Response | null { + const now = new Date(); + for (const item of this._data) { + if ( + (!item.expires || now <= item.expires) && + this._contains(request, item.request) + ) { + return item.response; + } + } + return null; + } + + public has(request: Request): boolean { + return !!this.get(request); + } + + public set(request: Request, response: Response, expiry?: Date): void { + this._data.push({ + request: request, + response: response, + expires: expiry, + }); + + // Clean up old requests on set. + this._expireOldRequests(); + } + + protected _contains(a: Request, b: Request): boolean { + return isEqual(a, b); + } + + protected _expireOldRequests(): void { + const now = new Date(); + this._data = this._data.filter((item) => !item.expires || now < item.expires); + } +} + +export class RequestCache extends MemoryRequestCache {} + +class MemoryRangedCache { + protected _ranges: MemoryRangeSet = new MemoryRangeSet(); + protected _data: Data[] = []; + protected _timeFunc: (data: Data) => number; + protected _idFunc: (data: Data) => string; + + constructor(timeFunc: (data: Data) => number, idFunc: (data: Data) => string) { + this._timeFunc = timeFunc; + this._idFunc = idFunc; + } + + public add(range: DateRange, data: Data[]) { + this._ranges.add(range); + this._data = sortedUniqBy( + orderBy(this._data.concat(data), this._timeFunc, 'asc'), + this._idFunc, + ); + } + + public hasCoverage(range: DateRange): boolean { + return this._ranges.hasCoverage(range); + } + + public get(range: DateRange): Data[] | null { + if (!this.hasCoverage(range)) { + return null; + } + + const output: Data[] = []; + for (const data of this._data) { + const start = this._timeFunc(data); + if (start > range.start.getTime()) { + if (start > range.end.getTime()) { + // Data is kept in order. + break; + } + output.push(data); + } + } + return output; + } + + public size(): number { + return this._data.length; + } + + /** + * Remove old data that matches a given predicate. No change to the covered + * ranges is made, i.e. this is asserting authoritiatively that this data does + * not exist in the current ranges. + * @param predicate A predicate to run on each data element. + */ + public expireMatches(predicate: (data: Data) => boolean): void { + this._data = this._data.filter(predicate); + } +} + +export class RecordingSegmentsCache { + protected _segments: Map> = new Map(); + + public add(cameraID: string, range: DateRange, segments: RecordingSegment[]) { + let cameraSegmentCache: MemoryRangedCache | undefined = + this._segments.get(cameraID); + if (!cameraSegmentCache) { + cameraSegmentCache = new MemoryRangedCache( + (segment: RecordingSegment) => segment.start_time * 1000, + (segment: RecordingSegment) => segment.id, + ); + this._segments.set(cameraID, cameraSegmentCache); + } + cameraSegmentCache.add(range, segments); + } + + public hasCoverage(cameraID: string, range: DateRange): boolean { + return !!this._segments.get(cameraID)?.hasCoverage(range); + } + + public get(cameraID: string, range: DateRange): RecordingSegment[] | null { + return this._segments.get(cameraID)?.get(range) ?? null; + } + + public getCache(cameraID: string): MemoryRangedCache | null { + return this._segments.get(cameraID) ?? null; + } + + public getCameraIDs(): string[] { + return [...this._segments.keys()]; + } + + public expireMatches( + cameraID: string, + func: (segment: RecordingSegment) => boolean, + ): void { + this._segments.get(cameraID)?.expireMatches(func); + } +} diff --git a/src/camera-manager/engine-factory.ts b/src/camera-manager/engine-factory.ts new file mode 100644 index 00000000..07295bb8 --- /dev/null +++ b/src/camera-manager/engine-factory.ts @@ -0,0 +1,93 @@ +import { CameraConfig, CardWideConfig } from '../types'; +import { ViewMedia } from '../view/media'; +import { RecordingSegmentsCache, RequestCache } from './cache'; +import { CameraManagerEngine } from './engine'; +import { FrigateCameraManagerEngine } from './frigate/engine-frigate'; +import { Engine } from './types'; + +type CameraManagerEngineCameraIDMap = Map>; + +export class CameraManagerEngineFactory { + protected _engines: Map = new Map(); + protected _cardWideConfig: CardWideConfig; + + constructor(cardWideConfig: CardWideConfig) { + this._cardWideConfig = cardWideConfig; + } + + public getEngine(engine: Engine): CameraManagerEngine | null { + const cachedEngine = this._engines.get(engine); + if (cachedEngine) { + return cachedEngine; + } + let cameraManagerEngine: CameraManagerEngine | null = null; + switch (engine) { + case Engine.Frigate: + cameraManagerEngine = new FrigateCameraManagerEngine( + this._cardWideConfig, + new RecordingSegmentsCache(), + new RequestCache(), + ); + break; + } + if (cameraManagerEngine) { + this._engines.set(engine, cameraManagerEngine); + } + return cameraManagerEngine; + } + + public getEngineForCamera(cameraConfig?: CameraConfig): CameraManagerEngine | null { + if (!cameraConfig) { + return null; + } + + let engine: Engine | null = null; + if (cameraConfig.frigate.camera_name) { + engine = Engine.Frigate; + } + return engine ? this.getEngine(engine) : null; + } + + public getEnginesForCameraIDs( + cameras: Map, + cameraIDs: Set, + ): CameraManagerEngineCameraIDMap | null { + const output: CameraManagerEngineCameraIDMap = new Map(); + + for (const cameraID of cameraIDs) { + const cameraConfig = cameras.get(cameraID); + if (!cameraConfig) { + continue; + } + + const engine = this.getEngineForCamera(cameraConfig); + if (!engine) { + continue; + } + if (!output.has(engine)) { + output.set(engine, new Set()); + } + output.get(engine)?.add(cameraID); + } + return output.size ? output : null; + } + + public getEngineForMedia( + cameras: Map, + media: ViewMedia, + ): CameraManagerEngine | null { + const cameraID = media.getCameraID(); + if (!cameraID) { + return null; + } + const engines = this.getEnginesForCameraIDs(cameras, new Set([cameraID])); + return engines ? ([...engines.keys()][0] ?? null) : null; + } + + public getAllEngines( + cameras: Map, + ): CameraManagerEngine[] | null { + const engines = this.getEnginesForCameraIDs(cameras, new Set(cameras.keys())); + return engines ? [...engines.keys()] : null; + } +} diff --git a/src/camera-manager/engine.ts b/src/camera-manager/engine.ts new file mode 100644 index 00000000..22fc836f --- /dev/null +++ b/src/camera-manager/engine.ts @@ -0,0 +1,105 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import { CameraConfig } from '../types'; +import { ViewMedia } from '../view/media'; +import { + DataQuery, + EventQuery, + EventQueryResultsMap, + MediaMetadata, + PartialEventQuery, + PartialRecordingQuery, + PartialRecordingSegmentsQuery, + QueryReturnType, + RecordingQuery, + RecordingQueryResultsMap, + RecordingSegmentsQuery, + RecordingSegmentsQueryResultsMap, + CameraManagerEngineCapabilities, + CameraManagerMediaCapabilities, + CameraManagerCameraMetadata, +} from './types'; + +export const CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT = 10000; + +export interface CameraManagerEngine { + generateDefaultEventQuery( + cameras: Map, + cameraIDs: Set, + query: PartialEventQuery, + ): EventQuery[] | null; + + generateDefaultRecordingQuery( + cameras: Map, + cameraIDs: Set, + query: PartialRecordingQuery, + ): RecordingQuery[] | null; + + generateDefaultRecordingSegmentsQuery( + cameras: Map, + cameraIDs: Set, + query: PartialRecordingSegmentsQuery, + ): RecordingSegmentsQuery[] | null; + + getEvents( + hass: HomeAssistant, + cameras: Map, + query: EventQuery, + ): Promise; + + getRecordings( + hass: HomeAssistant, + cameras: Map, + query: RecordingQuery, + ): Promise; + + getRecordingSegments( + hass: HomeAssistant, + cameras: Map, + query: RecordingSegmentsQuery, + ): Promise; + + generateMediaFromEvents( + hass: HomeAssistant, + cameras: Map, + query: EventQuery, + results: QueryReturnType, + ): ViewMedia[] | null; + + generateMediaFromRecordings( + hass: HomeAssistant, + cameras: Map, + query: RecordingQuery, + results: QueryReturnType, + ): ViewMedia[] | null; + + getMediaDownloadPath(cameraConfig: CameraConfig, media: ViewMedia): string | null; + + favoriteMedia( + hass: HomeAssistant, + cameraConfig: CameraConfig, + media: ViewMedia, + favorite: boolean, + ): Promise; + + getQueryResultMaxAge(query: DataQuery): number | null; + + getMediaSeekTime( + hass: HomeAssistant, + cameras: Map, + media: ViewMedia, + target: Date, + ): Promise; + + getMediaMetadata( + hass: HomeAssistant, + cameras: Map, + ): Promise; + + getCameraMetadata( + hass: HomeAssistant, + cameraConfig: CameraConfig, + ): CameraManagerCameraMetadata; + + getCapabilities(): CameraManagerEngineCapabilities | null; + getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null; +} diff --git a/src/camera-manager/frigate/engine-frigate.ts b/src/camera-manager/frigate/engine-frigate.ts new file mode 100644 index 00000000..98fc83f0 --- /dev/null +++ b/src/camera-manager/frigate/engine-frigate.ts @@ -0,0 +1,839 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import add from 'date-fns/add'; +import endOfHour from 'date-fns/endOfHour'; +import startOfHour from 'date-fns/startOfHour'; +import { CAMERA_BIRDSEYE } from '../../const'; +import { CameraConfig, CardWideConfig } from '../../types'; +import { ViewMedia } from '../../view/media'; +import { RequestCache, RecordingSegmentsCache } from '../cache'; +import { + CameraManagerEngine, + CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT, +} from '../engine'; +import { DateRange } from '../range'; +import { + CameraManagerCameraMetadata, + CameraManagerEngineCapabilities, + CameraManagerMediaCapabilities, + DataQuery, + Engine, + EventQuery, + EventQueryResults, + EventQueryResultsMap, + FrigateEventQueryResults, + FrigateRecordingQueryResults, + FrigateRecordingSegmentsQueryResults, + MediaMetadata, + PartialEventQuery, + PartialRecordingQuery, + PartialRecordingSegmentsQuery, + QueryResults, + QueryResultsType, + QueryReturnType, + QueryType, + RecordingQuery, + RecordingQueryResults, + RecordingQueryResultsMap, + RecordingSegment, + RecordingSegmentsQuery, + RecordingSegmentsQueryResultsMap, +} from '../types'; +import { FrigateRecording } from './types'; +import { + getEvents, + getEventSummary, + getRecordingSegments, + getRecordingsSummary, + NativeFrigateEventQuery, + NativeFrigateRecordingSegmentsQuery, + retainEvent, +} from './requests'; +import orderBy from 'lodash-es/orderBy'; +import throttle from 'lodash-es/throttle'; +import { + allPromises, + formatDate, + prettifyTitle, + runWhenIdleIfSupported, +} from '../../utils/basic'; +import fromUnixTime from 'date-fns/fromUnixTime'; +import sum from 'lodash-es/sum'; +import { FrigateViewMediaClassifier } from './media-classifier'; +import { ViewMediaClassifier } from '../../view/media-classifier'; +import { FrigateViewMediaFactory } from './media'; +import { log } from '../../utils/debug'; +import { getEntityIcon, getEntityTitle } from '../../utils/ha'; + +const EVENT_REQUEST_CACHE_MAX_AGE_SECONDS = 60; +const RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS = 60; + +class FrigateQueryResultsClassifier { + public static isFrigateEventQueryResults( + results: QueryResults, + ): results is FrigateEventQueryResults { + return results.engine === Engine.Frigate && results.type === QueryResultsType.Event; + } + + public static isFrigateRecordingQueryResults( + results: QueryResults, + ): results is FrigateRecordingQueryResults { + return ( + results.engine === Engine.Frigate && results.type === QueryResultsType.Recording + ); + } + + public static isFrigateRecordingSegmentsResults( + results: QueryResults, + ): results is FrigateRecordingSegmentsQueryResults { + return ( + results.engine === Engine.Frigate && + results.type === QueryResultsType.RecordingSegments + ); + } +} + +export class FrigateCameraManagerEngine implements CameraManagerEngine { + protected _recordingSegmentsCache: RecordingSegmentsCache; + protected _requestCache: RequestCache; + protected _cardWideConfig: CardWideConfig; + + // Garbage collect segments at most once an hour. + protected _throttledSegmentGarbageCollector = throttle( + this._garbageCollectSegments.bind(this), + 60 * 60 * 1000, + { leading: false, trailing: true }, + ); + + constructor( + cardWideConfig: CardWideConfig, + recordingSegmentsCache: RecordingSegmentsCache, + requestCache: RequestCache, + ) { + this._cardWideConfig = cardWideConfig; + this._recordingSegmentsCache = recordingSegmentsCache; + this._requestCache = requestCache; + } + + public getMediaDownloadPath( + cameraConfig: CameraConfig, + media: ViewMedia, + ): string | null { + let path: string | null = null; + if (FrigateViewMediaClassifier.isFrigateEvent(media)) { + path = + `/api/frigate/${cameraConfig.frigate.client_id}` + + `/notifications/${media.getID()}/` + + `${ViewMediaClassifier.isClip(media) ? 'clip.mp4' : 'snapshot.jpg'}` + + `?download=true`; + } else if (FrigateViewMediaClassifier.isFrigateRecording(media)) { + path = + `/api/frigate/${cameraConfig.frigate.client_id}` + + `/recording/${cameraConfig.frigate.camera_name}` + + `/start/${Math.floor(media.getStartTime().getTime() / 1000)}` + + `/end/${Math.floor(media.getEndTime().getTime() / 1000)}}` + + `?download=true`; + } + return path; + } + + public generateDefaultEventQuery( + cameras: Map, + cameraIDs: Set, + query?: PartialEventQuery, + ): EventQuery[] | null { + const relevantCameraConfigs = Array.from(cameraIDs).map((cameraID) => + cameras.get(cameraID), + ); + + // If there isn't a label or zone specified, we can come up with a single + // batch query for Frigate that will match across all cameras. + const canDoBatchQuery = relevantCameraConfigs.every( + (cameraConfig) => !cameraConfig?.frigate.label && !cameraConfig?.frigate.zone, + ); + + if (canDoBatchQuery) { + return [ + { + type: QueryType.Event, + cameraIDs: cameraIDs, + ...query, + }, + ]; + } + + const output: EventQuery[] = []; + for (const cameraID of cameraIDs) { + const cameraConfig = cameras.get(cameraID); + if (cameraConfig) { + output.push({ + type: QueryType.Event, + cameraIDs: new Set([cameraID]), + ...(cameraConfig.frigate.label && { + what: new Set([cameraConfig.frigate.label]), + }), + ...(cameraConfig.frigate.zone && { + where: new Set([cameraConfig.frigate.zone]), + }), + ...query, + }); + } + } + return output.length ? output : null; + } + + public generateDefaultRecordingQuery( + _cameras: Map, + cameraIDs: Set, + query?: PartialRecordingQuery, + ): RecordingQuery[] { + return [ + { + type: QueryType.Recording, + cameraIDs: cameraIDs, + ...query, + }, + ]; + } + + public generateDefaultRecordingSegmentsQuery( + _cameras: Map, + cameraIDs: Set, + query: PartialRecordingSegmentsQuery, + ): RecordingSegmentsQuery[] | null { + if (!query.start || !query.end) { + return null; + } + return [ + { + type: QueryType.RecordingSegments, + cameraIDs: cameraIDs, + start: query.start, + end: query.end, + ...query, + }, + ]; + } + + public async favoriteMedia( + hass: HomeAssistant, + cameraConfig: CameraConfig, + media: ViewMedia, + favorite: boolean, + ): Promise { + if (!FrigateViewMediaClassifier.isFrigateEvent(media)) { + return; + } + + await retainEvent(hass, cameraConfig.frigate.client_id, media.getID(), favorite); + media.setFavorite(favorite); + } + + protected _buildInstanceToCameraIDMapFromQuery( + cameras: Map, + cameraIDs: Set, + ): Map> { + const output: Map> = new Map(); + for (const cameraID of cameraIDs) { + const cameraConfig = this._getQueryableCameraConfig(cameras, cameraID); + const clientID = cameraConfig?.frigate.client_id; + if (clientID) { + if (!output.has(clientID)) { + output.set(clientID, new Set()); + } + output.get(clientID)?.add(cameraID); + } + } + return output; + } + + protected _getFrigateCameraNamesForCameraIDs( + cameras: Map, + cameraIDs: Set, + ): Set { + const output = new Set(); + for (const cameraID of cameraIDs) { + const cameraConfig = this._getQueryableCameraConfig(cameras, cameraID); + if (cameraConfig?.frigate.camera_name) { + output.add(cameraConfig.frigate.camera_name); + } + } + return output; + } + + public async getEvents( + hass: HomeAssistant, + cameras: Map, + query: EventQuery, + ): Promise { + const output: EventQueryResultsMap = new Map(); + + const processInstanceQuery = async ( + instanceID: string, + cameraIDs?: Set, + ): Promise => { + if (!cameraIDs || !cameraIDs.size) { + return; + } + const instanceQuery = { ...query, cameraIDs: cameraIDs }; + const cachedResult = this._requestCache.get(instanceQuery); + if (cachedResult) { + output.set(query, cachedResult as EventQueryResults); + return; + } + + const nativeQuery: NativeFrigateEventQuery = { + instance_id: instanceID, + cameras: Array.from(this._getFrigateCameraNamesForCameraIDs(cameras, cameraIDs)), + ...(query.what && { labels: Array.from(query.what) }), + ...(query.where && { zones: Array.from(query.where) }), + ...(query.end && { before: Math.floor(query.end.getTime() / 1000) }), + ...(query.start && { after: Math.floor(query.start.getTime() / 1000) }), + ...(query.limit && { limit: query.limit }), + ...(query.hasClip && { has_clip: query.hasClip }), + ...(query.hasSnapshot && { has_snapshot: query.hasSnapshot }), + ...(query.favorite && { favorites: query.favorite }), + limit: query?.limit ?? CAMERA_MANAGER_ENGINE_EVENT_LIMIT_DEFAULT, + }; + + const result: FrigateEventQueryResults = { + type: QueryResultsType.Event, + engine: Engine.Frigate, + instanceID: instanceID, + events: await getEvents(hass, nativeQuery), + expiry: add(new Date(), { seconds: EVENT_REQUEST_CACHE_MAX_AGE_SECONDS }), + cached: false, + }; + + this._requestCache.set(query, { ...result, cached: true }, result.expiry); + output.set(instanceQuery, result); + }; + + // Frigate allows multiple cameras to be searched for events in a single + // query. Break them down into groups of cameras per Frigate instance, then + // query once per instance for all cameras in that instance. + const instances = this._buildInstanceToCameraIDMapFromQuery( + cameras, + query.cameraIDs, + ); + + await Promise.all( + Array.from(instances.keys()).map((instanceID) => + processInstanceQuery(instanceID, instances.get(instanceID)), + ), + ); + return output.size ? output : null; + } + + public async getRecordings( + hass: HomeAssistant, + cameras: Map, + query: RecordingQuery, + ): Promise { + const output: RecordingQueryResultsMap = new Map(); + + const processQuery = async ( + baseQuery: RecordingQuery, + cameraID: string, + ): Promise => { + const query = { ...baseQuery, cameraIDs: new Set([cameraID]) }; + const cachedResult = this._requestCache.get(query); + if (cachedResult) { + output.set(query, cachedResult as RecordingQueryResults); + return; + } + + const cameraConfig = this._getQueryableCameraConfig(cameras, cameraID); + if (!cameraConfig || !cameraConfig.frigate.camera_name) { + return; + } + + const recordingSummary = await getRecordingsSummary( + hass, + cameraConfig.frigate.client_id, + cameraConfig.frigate.camera_name, + ); + + let recordings: FrigateRecording[] = []; + + for (const dayData of recordingSummary ?? []) { + for (const hourData of dayData.hours) { + const hour = add(dayData.day, { hours: hourData.hour }); + const startHour = startOfHour(hour); + const endHour = endOfHour(hour); + if ( + (!query.start || startHour >= query.start) && + (!query.end || endHour <= query.end) + ) { + recordings.push({ + cameraID: cameraID, + startTime: startHour, + endTime: endHour, + events: hourData.events, + }); + } + } + } + + if (query.limit !== undefined) { + // Frigate does not natively support a way to limit recording searches so + // this simulates it. + recordings = orderBy( + recordings, + (recording: FrigateRecording) => recording.startTime, + 'desc', + ).slice(0, query.limit); + } + + const result: FrigateRecordingQueryResults = { + type: QueryResultsType.Recording, + engine: Engine.Frigate, + instanceID: cameraConfig.frigate.client_id, + recordings: recordings, + expiry: add(new Date(), { + seconds: RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS, + }), + cached: false, + }; + this._requestCache.set(query, { ...result, cached: true }, result.expiry); + output.set(query, result); + }; + + // Frigate recordings can only be queried for a single camera, so fan out + // the inbound query into multiple outbound queries. + await Promise.all( + Array.from(query.cameraIDs).map((cameraID) => processQuery(query, cameraID)), + ); + return output.size ? output : null; + } + + public async getRecordingSegments( + hass: HomeAssistant, + cameras: Map, + query: RecordingSegmentsQuery, + ): Promise { + const output: RecordingSegmentsQueryResultsMap = new Map(); + + const processQuery = async ( + baseQuery: RecordingSegmentsQuery, + cameraID: string, + ): Promise => { + const query = { ...baseQuery, cameraIDs: new Set([cameraID]) }; + const cameraConfig = this._getQueryableCameraConfig(cameras, cameraID); + if (!cameraConfig || !cameraConfig.frigate.camera_name) { + return; + } + + const range: DateRange = { start: query.start, end: query.end }; + + // A note on Frigate Recording Segments: + // - There is an internal cache at the engine level for segments to allow + // caching "within an existing query" (e.g. if we already cached hour + // 1-8, we will avoid a fetch if we request hours 2-3 even though the + // query is different -- the segments won't be). This is since the + // volume of data in segment transfers can be high, and the segments can + // be used in high frequency situations (e.g. video seeking). + const cachedSegments = this._recordingSegmentsCache.get(cameraID, range); + if (cachedSegments) { + output.set(query, { + type: QueryResultsType.RecordingSegments, + engine: Engine.Frigate, + instanceID: cameraConfig.frigate.client_id, + segments: cachedSegments, + cached: true, + }); + return; + } + + const request: NativeFrigateRecordingSegmentsQuery = { + instance_id: cameraConfig.frigate.client_id, + camera: cameraConfig.frigate.camera_name, + after: Math.floor(query.start.getTime() / 1000), + before: Math.floor(query.end.getTime() / 1000), + }; + + const segments = await getRecordingSegments(hass, request); + this._recordingSegmentsCache.add(cameraID, range, segments); + + output.set(query, { + type: QueryResultsType.RecordingSegments, + engine: Engine.Frigate, + instanceID: cameraConfig.frigate.client_id, + segments: segments, + cached: false, + }); + }; + + // Frigate recording segments can only be queried for a single camera, so + // fan out the inbound query into multiple outbound queries. + await Promise.all( + Array.from(query.cameraIDs).map((cameraID) => processQuery(query, cameraID)), + ); + + runWhenIdleIfSupported(() => this._throttledSegmentGarbageCollector(hass, cameras)); + return output.size ? output : null; + } + + protected _getCameraIDMatch( + cameras: Map, + query: DataQuery, + instanceID: string, + cameraName: string, + ): string | null { + // If the query is only for a single cameraID, all results are assumed to + // belong to it for performance reasons. Otherwise, we need to map the + // instanceID and camera name for the known cameras, and get the precise + // cameraID that matches the expected instance ID / camera name. + if (query.cameraIDs.size === 1) { + return [...query.cameraIDs][0]; + } + for (const [cameraID, cameraConfig] of cameras.entries()) { + if ( + cameraConfig.frigate.client_id === instanceID && + cameraConfig.frigate.camera_name === cameraName + ) { + return cameraID; + } + } + return null; + } + + public generateMediaFromEvents( + _hass: HomeAssistant, + cameras: Map, + query: EventQuery, + results: QueryReturnType, + ): ViewMedia[] | null { + if (!FrigateQueryResultsClassifier.isFrigateEventQueryResults(results)) { + return null; + } + + const output: ViewMedia[] = []; + for (const event of results.events) { + const cameraID = this._getCameraIDMatch( + cameras, + query, + results.instanceID, + event.camera, + ); + if (!cameraID) { + continue; + } + const cameraConfig = this._getQueryableCameraConfig(cameras, cameraID); + if (!cameraConfig) { + continue; + } + let mediaType: 'clip' | 'snapshot' | null = null; + if ( + !query.hasClip && + !query.hasSnapshot && + (event.has_clip || event.has_snapshot) + ) { + mediaType = event.has_clip ? 'clip' : 'snapshot'; + } else if (query.hasSnapshot && event.has_snapshot) { + mediaType = 'snapshot'; + } else if (query.hasClip && event.has_clip) { + mediaType = 'clip'; + } + if (!mediaType) { + continue; + } + const media = FrigateViewMediaFactory.createEventViewMedia( + mediaType, + cameraID, + cameraConfig, + event, + ); + if (media) { + output.push(media); + } + } + return output; + } + + public generateMediaFromRecordings( + hass: HomeAssistant, + cameras: Map, + _query: RecordingQuery, + results: QueryReturnType, + ): ViewMedia[] | null { + if (!FrigateQueryResultsClassifier.isFrigateRecordingQueryResults(results)) { + return null; + } + + const output: ViewMedia[] = []; + for (const recording of results.recordings) { + const cameraConfig = this._getQueryableCameraConfig(cameras, recording.cameraID); + if (!cameraConfig) { + continue; + } + const media = FrigateViewMediaFactory.createRecordingViewMedia( + recording.cameraID, + recording, + cameraConfig, + this.getCameraMetadata(hass, cameraConfig).title, + ); + if (media) { + output.push(media); + } + } + return output; + } + + public getQueryResultMaxAge(query: DataQuery): number | null { + if (query.type === QueryType.Event) { + return EVENT_REQUEST_CACHE_MAX_AGE_SECONDS; + } else if (query.type === QueryType.Recording) { + return RECORDING_SUMMARY_REQUEST_CACHE_MAX_AGE_SECONDS; + } + return null; + } + + public async getMediaSeekTime( + hass: HomeAssistant, + cameras: Map, + media: ViewMedia, + target: Date, + ): Promise { + const start = media.getStartTime(); + const end = media.getEndTime(); + if (!start || !end || target < start || target > end) { + return null; + } + + const cameraID = media.getCameraID(); + const query: RecordingSegmentsQuery = { + cameraIDs: new Set([cameraID]), + start: start, + end: end, + type: QueryType.RecordingSegments, + }; + + const results = await this.getRecordingSegments(hass, cameras, query); + + if (results) { + return this._getSeekTimeInSegments( + start, + target, + // There will only be a single result since Frigate recording segments + // searches are per camera which is specified singularly above. + Array.from(results.values())[0].segments, + ); + } + return null; + } + + protected _getQueryableCameraConfig( + cameras: Map, + cameraID: string, + ): CameraConfig | null { + const cameraConfig = cameras.get(cameraID); + if (!cameraConfig || cameraConfig.frigate.camera_name == CAMERA_BIRDSEYE) { + return null; + } + return cameraConfig; + } + + public async getMediaMetadata( + hass: HomeAssistant, + cameras: Map, + ): Promise { + const what: Set = new Set(); + const where: Set = new Set(); + const days: Set = new Set(); + + const instances = this._buildInstanceToCameraIDMapFromQuery( + cameras, + new Set(cameras.keys()), + ); + + const processEventSummary = async ( + instanceID: string, + cameraIDs: Set, + ): Promise => { + const cameraNames = this._getFrigateCameraNamesForCameraIDs(cameras, cameraIDs); + for (const entry of await getEventSummary(hass, instanceID)) { + if (!cameraNames.has(entry.camera)) { + // If this entry applies to a camera that *is* in this Frigate + // instance, but is *not* a configured camera in the card, skip it. + continue; + } + if (entry.label) { + what.add(entry.label); + } + if (entry.zones.length) { + entry.zones.forEach(where.add, where); + } + if (entry.day) { + days.add(entry.day); + } + } + }; + + const processRecordings = async (cameraIDs: Set): Promise => { + const recordings = await this.getRecordings(hass, cameras, { + type: QueryType.Recording, + cameraIDs: cameraIDs, + }); + if (!recordings) { + return; + } + + for (const result of recordings.values()) { + if (!FrigateQueryResultsClassifier.isFrigateRecordingQueryResults(result)) { + continue; + } + + for (const recording of result.recordings) { + // Frigate recordings are always 1 hour long, i.e. never span a day. + days.add(formatDate(recording.startTime)); + } + } + }; + + await allPromises([...instances.entries()], ([instanceID, cameraIDs]) => + (async () => { + await Promise.all([ + processEventSummary(instanceID, cameraIDs), + processRecordings(cameraIDs), + ]); + })(), + ); + + if (!what.size && !where.size && !days.size) { + return null; + } + return { + ...(what.size && { what: what }), + ...(where.size && { where: where }), + ...(days.size && { days: days }), + }; + } + + /** + * Garbage collect recording segments that no longer feature in the recordings + * returned by the Frigate backend. + */ + protected async _garbageCollectSegments( + hass: HomeAssistant, + cameras: Map, + ): Promise { + const cameraIDs = this._recordingSegmentsCache.getCameraIDs(); + const recordingQuery: RecordingQuery = { + cameraIDs: new Set(cameraIDs), + type: QueryType.Recording, + }; + + const countSegments = () => + sum( + cameraIDs.map( + (cameraID) => this._recordingSegmentsCache.getCache(cameraID)?.size() ?? 0, + ), + ); + const segmentsStart = countSegments(); + + // Performance: _recordingSegments is potentially very large (e.g. 10K - 1M + // items) and each item must be examined, so care required here to stick to + // nothing worse than O(n) performance. + const getHourID = (cameraID: string, startTime: Date): string => { + return `${cameraID}/${startTime.getDate()}/${startTime.getHours()}`; + }; + + const results = await this.getRecordings(hass, cameras, recordingQuery); + if (!results) { + return; + } + + for (const [query, result] of results) { + if (!FrigateQueryResultsClassifier.isFrigateRecordingQueryResults(result)) { + continue; + } + + const goodHours: Set = new Set(); + for (const recording of result.recordings) { + goodHours.add(getHourID(recording.cameraID, recording.startTime)); + } + + // Frigate recordings are always executed individually, so there'll only + // be a single results. + const cameraID = Array.from(query.cameraIDs)[0]; + this._recordingSegmentsCache.expireMatches( + cameraID, + (segment: RecordingSegment) => { + const hourID = getHourID(cameraID, fromUnixTime(segment.start_time)); + // ~O(1) lookup time for a JS set. + return goodHours.has(hourID); + }, + ); + } + + log( + this._cardWideConfig, + 'Frigate Card recording segment garbage collection: ' + + `Released ${segmentsStart - countSegments()} segment(s)`, + ); + } + + /** + * Get the number of seconds to seek into a video stream consisting of the + * provided segments to reach the target time provided. + * @param startTime The earliest allowable time to seek from. + * @param targetTime Target time. + * @param segments An array of segments dataset items. Must be sorted from oldest to youngest. + * @returns + */ + protected _getSeekTimeInSegments( + startTime: Date, + targetTime: Date, + segments: RecordingSegment[], + ): number | null { + if (!segments.length) { + return null; + } + let seekMilliseconds = 0; + + // Inspired by: https://github.com/blakeblackshear/frigate/blob/release-0.11.0/web/src/routes/Recording.jsx#L27 + for (const segment of segments) { + const segmentStart = fromUnixTime(segment.start_time); + if (segmentStart > targetTime) { + break; + } + const segmentEnd = fromUnixTime(segment.end_time); + const start = segmentStart < startTime ? startTime : segmentStart; + const end = segmentEnd > targetTime ? targetTime : segmentEnd; + seekMilliseconds += end.getTime() - start.getTime(); + } + return seekMilliseconds / 1000; + } + + public getCapabilities(): CameraManagerEngineCapabilities { + return { + canFavoriteEvents: true, + canFavoriteRecordings: false, + }; + } + + public getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities { + return { + canFavorite: ViewMediaClassifier.isEvent(media), + }; + } + + public getCameraMetadata( + hass: HomeAssistant, + cameraConfig: CameraConfig, + ): CameraManagerCameraMetadata { + return { + title: + cameraConfig.title ?? + getEntityTitle(hass, cameraConfig.camera_entity) ?? + getEntityTitle(hass, cameraConfig.webrtc_card?.entity) ?? + prettifyTitle(cameraConfig.frigate?.camera_name) ?? + cameraConfig.id ?? + '', + icon: + cameraConfig?.icon ?? + getEntityIcon(hass, cameraConfig.camera_entity) ?? + 'mdi:video', + }; + } +} diff --git a/src/camera-manager/frigate/icon.ts b/src/camera-manager/frigate/icon.ts new file mode 100644 index 00000000..3e075f2e --- /dev/null +++ b/src/camera-manager/frigate/icon.ts @@ -0,0 +1,20 @@ +export const FRIGATE_ICON_SVG_PATH = + 'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' + + '-0.43297,0.10824 -2.32718,-3.43665 -2.7601492,-4.95202 -0.4329602,-1.51538 ' + + '-0.6764993,-3.22017 -0.5682593,-4.19434 0.1082301,-0.97417 5.7097085,-2.48955 ' + + '5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' + + '1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' + + '-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' + + '-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' + + '-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' + + '-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' + + '-4.97909,2.05658 -4.97909,2.43542 0,0.37885 0.16236,0.67651 0.0541,1.54244 -0.10824,' + + '0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' + + '0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' + + '0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' + + '0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' + + '-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' + + 'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' + + '-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' + + '-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' + + '-0.21649,2.59778 -0.21649,3.43665 0,0.75379 -0.10824,2.43542 0,3.30135 z'; diff --git a/src/camera-manager/frigate/media-classifier.ts b/src/camera-manager/frigate/media-classifier.ts new file mode 100644 index 00000000..442d6b0d --- /dev/null +++ b/src/camera-manager/frigate/media-classifier.ts @@ -0,0 +1,18 @@ +import { ViewMedia } from '../../view/media'; +import { FrigateEventViewMedia, FrigateRecordingViewMedia } from './media'; + +export class FrigateViewMediaClassifier { + public static isFrigateMedia( + media: ViewMedia, + ): media is FrigateEventViewMedia | FrigateRecordingViewMedia { + return this.isFrigateEvent(media) || this.isFrigateRecording(media); + } + public static isFrigateEvent(media: ViewMedia): media is FrigateEventViewMedia { + return media instanceof FrigateEventViewMedia; + } + public static isFrigateRecording( + media: ViewMedia, + ): media is FrigateRecordingViewMedia { + return media instanceof FrigateRecordingViewMedia; + } +} diff --git a/src/camera-manager/frigate/media.ts b/src/camera-manager/frigate/media.ts new file mode 100644 index 00000000..0b5f5a3e --- /dev/null +++ b/src/camera-manager/frigate/media.ts @@ -0,0 +1,181 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import fromUnixTime from 'date-fns/fromUnixTime'; +import isEqual from 'lodash-es/isEqual'; +import { CameraConfig } from '../../types'; +import { + ViewMedia, + EventViewMedia, + RecordingViewMedia, + ViewMediaType, +} from '../../view/media'; +import { FrigateEvent, FrigateRecording } from './types'; +import { + getEventMediaContentID, + getEventThumbnailURL, + getEventTitle, + getRecordingID, + getRecordingMediaContentID, + getRecordingTitle, +} from './util'; + +export class FrigateEventViewMedia extends ViewMedia implements EventViewMedia { + protected _event: FrigateEvent; + protected _contentID: string; + protected _thumbnail: string; + + constructor( + mediaType: ViewMediaType, + cameraID: string, + event: FrigateEvent, + contentID: string, + thumbnail: string, + ) { + super(mediaType, cameraID); + this._event = event; + this._contentID = contentID; + this._thumbnail = thumbnail; + } + + public hasClip(): boolean { + return !!this._event.has_clip; + } + public getStartTime(): Date { + return fromUnixTime(this._event.start_time); + } + public getEndTime(): Date | null { + return this._event.end_time ? fromUnixTime(this._event.end_time) : null; + } + public getID(): string { + return this._event.id; + } + public getContentID(): string { + return this._contentID; + } + public getTitle(): string | null { + return getEventTitle(this._event); + } + public getThumbnail(): string | null { + return this._thumbnail; + } + public isFavorite(): boolean | null { + return this._event.retain_indefinitely ?? null; + } + public setFavorite(favorite: boolean): void { + this._event.retain_indefinitely = favorite; + } + public getWhat(): string[] | null { + return [this._event.label]; + } + public getWhere(): string[] | null { + const zones = this._event.zones; + return zones.length ? zones : null; + } + public getScore(): number | null { + return this._event.top_score; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public isGroupableWith(that: EventViewMedia): boolean { + return ( + this.getMediaType() === that.getMediaType() && + isEqual(this.getWhere(), that.getWhere()) && + isEqual(this.getWhat(), that.getWhat()) + ); + } +} + +export class FrigateRecordingViewMedia extends ViewMedia implements RecordingViewMedia { + protected _recording: FrigateRecording; + protected _id: string; + protected _contentID: string; + protected _title: string; + + constructor( + mediaType: ViewMediaType, + cameraID: string, + recording: FrigateRecording, + id: string, + contentID: string, + title: string, + ) { + super(mediaType, cameraID); + this._recording = recording; + this._id = id; + this._contentID = contentID; + this._title = title; + } + + public getID(): string { + return this._id; + } + public getStartTime(): Date { + return this._recording.startTime; + } + public getEndTime(): Date { + return this._recording.endTime; + } + public getContentID(): string | null { + return this._contentID; + } + public getTitle(): string | null { + return this._title; + } + public getEventCount(): number { + return this._recording.events; + } +} + +export class FrigateViewMediaFactory { + static createEventViewMedia( + mediaType: 'clip' | 'snapshot', + cameraID: string, + cameraConfig: CameraConfig, + event: FrigateEvent, + ): FrigateEventViewMedia | null { + if ( + (mediaType === 'clip' && !event.has_clip) || + (mediaType === 'snapshot' && !event.has_snapshot) || + !cameraConfig.frigate.client_id || + !cameraConfig.frigate.camera_name + ) { + return null; + } + + return new FrigateEventViewMedia( + mediaType, + cameraID, + event, + getEventMediaContentID( + cameraConfig.frigate.client_id, + cameraConfig.frigate.camera_name, + event, + mediaType === 'clip' ? 'clips' : 'snapshots', + ), + getEventThumbnailURL(cameraConfig.frigate.client_id, event), + ); + } + + static createRecordingViewMedia( + cameraID: string, + recording: FrigateRecording, + cameraConfig: CameraConfig, + cameraTitle: string, + ): FrigateRecordingViewMedia | null { + if (!cameraConfig.frigate.client_id || !cameraConfig.frigate.camera_name) { + return null; + } + + return new FrigateRecordingViewMedia( + 'recording', + cameraID, + recording, + getRecordingID(cameraConfig, recording), + getRecordingMediaContentID( + cameraConfig.frigate.client_id, + cameraConfig.frigate.camera_name, + recording, + ), + getRecordingTitle(cameraTitle, recording), + ); + } +} diff --git a/src/camera-manager/frigate/requests.ts b/src/camera-manager/frigate/requests.ts new file mode 100644 index 00000000..3cf751ba --- /dev/null +++ b/src/camera-manager/frigate/requests.ts @@ -0,0 +1,153 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import { localize } from '../../localize/localize'; +import { FrigateCardError } from '../../types'; +import { homeAssistantWSRequest } from '../../utils/ha'; +import { RecordingSegment } from '../types'; +import { + EventSummary, + eventSummarySchema, + FrigateEvent, + frigateEventsSchema, + recordingSegmentsSchema, + RecordingSummary, + recordingSummarySchema, + RetainResult, + retainResultSchema, +} from './types'; + +/** + * Get the recordings summary. May throw. + * @param hass The Home Assistant object. + * @param clientID The Frigate clientID. + * @param camera_name The Frigate camera name. + * @returns A RecordingSummary object. + */ +export const getRecordingsSummary = async ( + hass: HomeAssistant, + clientID: string, + camera_name: string, +): Promise => { + return (await homeAssistantWSRequest( + hass, + recordingSummarySchema, + { + type: 'frigate/recordings/summary', + instance_id: clientID, + camera: camera_name, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }, + true, + // See: https://github.com/colinhacks/zod/pull/1752 + )) as RecordingSummary; +}; + +export interface NativeFrigateRecordingSegmentsQuery { + instance_id: string; + camera: string; + after: number; + before: number; +} + +/** + * Get the recording segments. May throw. + * @param hass The Home Assistant object. + * @param params The recording segment query parameters. + * @returns A RecordingSegments object. + */ +export const getRecordingSegments = async ( + hass: HomeAssistant, + params: NativeFrigateRecordingSegmentsQuery, +): Promise => { + return await homeAssistantWSRequest( + hass, + recordingSegmentsSchema, + { + type: 'frigate/recordings/get', + ...params, + }, + true, + ); +}; + +/** + * Request that Frigate retain an event. May throw. + * @param hass The HomeAssistant object. + * @param clientID The Frigate clientID. + * @param eventID The event ID to retain. + * @param retain `true` to retain or `false` to unretain. + */ +export async function retainEvent( + hass: HomeAssistant, + clientID: string, + eventID: string, + retain: boolean, +): Promise { + const retainRequest = { + type: 'frigate/event/retain', + instance_id: clientID, + event_id: eventID, + retain: retain, + }; + const response = await homeAssistantWSRequest( + hass, + retainResultSchema, + retainRequest, + true, + ); + if (!response.success) { + throw new FrigateCardError(localize('error.failed_retain'), { + request: retainRequest, + response: response, + }); + } +} + +export interface NativeFrigateEventQuery { + instance_id?: string; + cameras?: string[]; + labels?: string[]; + zones?: string[]; + after?: number; + before?: number; + limit?: number; + has_clip?: boolean; + has_snapshot?: boolean; + favorites?: boolean; +} + +/** + * Get events over websocket. May throw. + * @param hass The Home Assistant object. + * @param params The events search parameters. + * @returns An array of 'FrigateEvent's. + */ +export const getEvents = async ( + hass: HomeAssistant, + params?: NativeFrigateEventQuery, +): Promise => { + return await homeAssistantWSRequest( + hass, + frigateEventsSchema, + { + type: 'frigate/events/get', + ...params, + }, + true, + ); +}; + +export const getEventSummary = async ( + hass: HomeAssistant, + clientID: string, +): Promise => { + return await homeAssistantWSRequest( + hass, + eventSummarySchema, + { + type: 'frigate/events/summary', + instance_id: clientID, + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }, + true, + ); +}; diff --git a/src/camera-manager/frigate/types.ts b/src/camera-manager/frigate/types.ts new file mode 100644 index 00000000..a8184d3d --- /dev/null +++ b/src/camera-manager/frigate/types.ts @@ -0,0 +1,68 @@ +import { z } from 'zod'; +import { dayToDate } from '../../utils/basic'; + +const dayStringToDate = (arg: unknown): Date | unknown => { + return typeof arg === 'string' ? dayToDate(arg) : arg; +}; + +const eventSchema = z.object({ + camera: z.string(), + end_time: z.number().nullable(), + false_positive: z.boolean().nullable(), + has_clip: z.boolean(), + has_snapshot: z.boolean(), + id: z.string(), + label: z.string(), + start_time: z.number(), + top_score: z.number(), + zones: z.string().array(), + retain_indefinitely: z.boolean().optional(), +}); +export const frigateEventsSchema = eventSchema.array(); + +export type FrigateEvent = z.infer; + +const recordingSummaryHourSchema = z.object({ + hour: z.preprocess((arg) => Number(arg), z.number().min(0).max(23)), + duration: z.number().min(0), + events: z.number().min(0), +}); + +export const recordingSummarySchema = z + .object({ + day: z.preprocess(dayStringToDate, z.date()), + events: z.number(), + hours: recordingSummaryHourSchema.array(), + }) + .array(); +export type RecordingSummary = z.infer; + +const recordingSegmentSchema = z.object({ + start_time: z.number(), + end_time: z.number(), + id: z.string(), +}); +export const recordingSegmentsSchema = recordingSegmentSchema.array(); + +export const retainResultSchema = z.object({ + success: z.boolean(), + message: z.string(), +}); +export type RetainResult = z.infer; + +export interface FrigateRecording { + cameraID: string; + startTime: Date; + endTime: Date; + events: number; +} + +export const eventSummarySchema = z + .object({ + camera: z.string(), + day: z.string(), + label: z.string(), + zones: z.string().array(), + }) + .array(); +export type EventSummary = z.infer; diff --git a/src/camera-manager/frigate/util.ts b/src/camera-manager/frigate/util.ts new file mode 100644 index 00000000..0be47429 --- /dev/null +++ b/src/camera-manager/frigate/util.ts @@ -0,0 +1,97 @@ +import utcToZonedTime from 'date-fns-tz/utcToZonedTime'; +import { CameraConfig, ClipsOrSnapshots } from '../../types'; +import { formatDateAndTime, prettifyTitle } from '../../utils/basic'; +import { FrigateEvent, FrigateRecording } from './types'; + +/** + * Given an event generate a title. + * @param event + */ +export const getEventTitle = (event: FrigateEvent): string => { + const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const durationSeconds = Math.round( + event.end_time + ? event.end_time - event.start_time + : Date.now() / 1000 - event.start_time, + ); + return `${formatDateAndTime( + utcToZonedTime(event.start_time * 1000, localTimezone), + )} [${durationSeconds}s, ${prettifyTitle(event.label)} ${Math.round( + event.top_score * 100, + )}%]`; +}; + +export const getRecordingTitle = ( + cameraTitle: string, + recording: FrigateRecording, +): string => { + return `${cameraTitle} ${formatDateAndTime(recording.startTime)}`; +}; + +/** + * Get a thumbnail URL for an event. + * @param clientId The Frigate client id. + * @param event The event. + * @returns A string URL. + */ +export const getEventThumbnailURL = (clientId: string, event: FrigateEvent): string => { + return `/api/frigate/${clientId}/thumbnail/${event.id}`; +}; + +/** + * Get a media content ID for an event. + * @param clientId The Frigate client id. + * @param cameraName The Frigate camera name. + * @param event The Frigate event. + * @param mediaType The media type required. + * @returns A string media content id. + */ +export const getEventMediaContentID = ( + clientId: string, + cameraName: string, + event: FrigateEvent, + mediaType: ClipsOrSnapshots, +): string => { + return `media-source://frigate/${clientId}/event/${mediaType}/${cameraName}/${event.id}`; +}; + +/** + * Generate a recording identifier. + * @param clientId The Frigate client id. + * @param cameraName The Frigate camera name. + * @param recording The Frigate recording. + * @returns A recording identifier. + */ +export const getRecordingMediaContentID = ( + clientId: string, + cameraName: string, + recording: FrigateRecording, +): string => { + return [ + 'media-source://frigate', + clientId, + 'recordings', + cameraName, + `${recording.startTime.getFullYear()}-${String( + recording.startTime.getMonth() + 1, + ).padStart(2, '0')}-${String( + String(recording.startTime.getDate()).padStart(2, '0'), + )}`, + String(recording.startTime.getHours()).padStart(2, '0'), + ].join('/'); +}; + +/** + * Get a recording ID for internal de-duping. + */ +export const getRecordingID = ( + cameraConfig: CameraConfig, + recording: FrigateRecording, +): string => { + // ID name is derived from the real camera name (not CameraID) since the + // recordings for the same camera across multiple zones will be the same and + // can be dedup'd from this id. + return `${cameraConfig.frigate?.client_id ?? ''}/${ + cameraConfig.frigate.camera_name ?? '' + }/${recording.startTime.getTime()}/${recording.endTime.getTime()}}`; +}; diff --git a/src/camera-manager/manager.ts b/src/camera-manager/manager.ts new file mode 100644 index 00000000..c3e0a11c --- /dev/null +++ b/src/camera-manager/manager.ts @@ -0,0 +1,544 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import { CameraConfig, CardWideConfig } from '../types.js'; +import { allPromises, arrayify, setify } from '../utils/basic.js'; +import { + CameraManagerCameraMetadata, + CameraManagerCapabilities, + CameraManagerMediaCapabilities, + DataQuery, + EventQuery, + EventQueryResults, + EventQueryResultsMap, + MediaMetadata, + MediaQuery, + PartialDataQuery, + PartialEventQuery, + PartialQueryConcreteType, + PartialRecordingQuery, + PartialRecordingSegmentsQuery, + QueryResults, + QueryResultsType, + QueryReturnType, + QueryType, + RecordingQuery, + RecordingQueryResults, + RecordingQueryResultsMap, + RecordingSegmentsQuery, + RecordingSegmentsQueryResults, + RecordingSegmentsQueryResultsMap, + ResultsMap, +} from './types.js'; +import orderBy from 'lodash-es/orderBy'; +import { CameraManagerEngineFactory } from './engine-factory.js'; +import { ViewMedia } from '../view/media.js'; +import uniqBy from 'lodash-es/uniqBy'; +import { CameraManagerEngine } from './engine.js'; +import sum from 'lodash-es/sum'; +import add from 'date-fns/add'; +import { log } from '../utils/debug.js'; + +class QueryClassifier { + public static isEventQuery(query: DataQuery | PartialDataQuery): query is EventQuery { + return query.type === QueryType.Event; + } + public static isRecordingQuery( + query: DataQuery | PartialDataQuery, + ): query is RecordingQuery { + return query.type === QueryType.Recording; + } + public static isRecordingSegmentsQuery( + query: DataQuery | PartialDataQuery, + ): query is RecordingSegmentsQuery { + return query.type === QueryType.RecordingSegments; + } +} + +class QueryResultClassifier { + public static isEventQueryResult( + queryResults: QueryResults, + ): queryResults is EventQueryResults { + return queryResults.type === QueryResultsType.Event; + } + public static isRecordingQuery( + queryResults: QueryResults, + ): queryResults is RecordingQueryResults { + return queryResults.type === QueryResultsType.Recording; + } + public static isRecordingSegmentsQuery( + queryResults: QueryResults, + ): queryResults is RecordingSegmentsQueryResults { + return queryResults.type === QueryResultsType.RecordingSegments; + } +} + +export interface ExtendedMediaQueryResult { + queries: T[]; + results: ViewMedia[]; +} + +export class CameraManager { + protected _engineFactory: CameraManagerEngineFactory; + protected _cameras: Map; + protected _cardWideConfig?: CardWideConfig; + + constructor( + engineFactory: CameraManagerEngineFactory, + cameras: Map, + cardWideConfig?: CardWideConfig, + ) { + this._engineFactory = engineFactory; + this._cameras = cameras; + this._cardWideConfig = cardWideConfig; + } + + public generateDefaultEventQueries( + cameraIDs: string | Set, + partialQuery?: PartialEventQuery, + ): EventQuery[] | null { + return this._generateDefaultQueries(cameraIDs, { + type: QueryType.Event, + ...partialQuery, + }); + } + + public generateDefaultRecordingQueries( + cameraIDs: string | Set, + partialQuery?: PartialRecordingQuery, + ): RecordingQuery[] | null { + return this._generateDefaultQueries(cameraIDs, { + type: QueryType.Recording, + ...partialQuery, + }); + } + + public generateDefaultRecordingSegmentsQueries( + cameraIDs: string | Set, + partialQuery?: PartialRecordingSegmentsQuery, + ): RecordingSegmentsQuery[] | null { + return this._generateDefaultQueries(cameraIDs, { + type: QueryType.RecordingSegments, + ...partialQuery, + }); + } + + public async getMediaMetadata(hass: HomeAssistant): Promise { + const what: Set = new Set(); + const where: Set = new Set(); + const days: Set = new Set(); + + const engines = this._engineFactory.getAllEngines(this._cameras); + if (!engines) { + return null; + } + + const processMetadata = async (engine: CameraManagerEngine): Promise => { + const engineMetadata = await engine.getMediaMetadata(hass, this._cameras); + if (engineMetadata) { + if (engineMetadata.what) { + engineMetadata.what.forEach(what.add, what); + } + if (engineMetadata.where) { + engineMetadata.where.forEach(where.add, where); + } + if (engineMetadata.days) { + engineMetadata.days.forEach(days.add, days); + } + } + }; + + await allPromises(engines, (engine) => processMetadata(engine)); + + if (!what.size && !where.size && !days.size) { + return null; + } + return { + ...(what.size && { what: what }), + ...(where.size && { where: where }), + ...(days.size && { days: days }), + }; + } + + protected _generateDefaultQueries( + cameraIDs: string | Set, + partialQuery: PQT, + ): PartialQueryConcreteType[] | null { + const concreteQueries: PartialQueryConcreteType[] = []; + const _cameraIDs = setify(cameraIDs); + + const engines = this._engineFactory.getEnginesForCameraIDs( + this._cameras, + _cameraIDs, + ); + + if (!engines) { + return null; + } + + for (const [engine, cameraIDs] of engines) { + let queries: DataQuery[] | null = null; + if (QueryClassifier.isEventQuery(partialQuery)) { + queries = engine.generateDefaultEventQuery( + this._cameras, + cameraIDs, + partialQuery, + ); + } else if (QueryClassifier.isRecordingQuery(partialQuery)) { + queries = engine.generateDefaultRecordingQuery( + this._cameras, + cameraIDs, + partialQuery, + ); + } else if (QueryClassifier.isRecordingSegmentsQuery(partialQuery)) { + queries = engine.generateDefaultRecordingSegmentsQuery( + this._cameras, + cameraIDs, + partialQuery, + ); + } + + for (const query of queries ?? []) { + concreteQueries.push(query as PartialQueryConcreteType); + } + } + return concreteQueries; + } + + public async getEvents( + hass: HomeAssistant, + query: EventQuery | EventQuery[], + ): Promise { + return await this._handleQuery(hass, query); + } + + public async getRecordings( + hass: HomeAssistant, + query: RecordingQuery | RecordingQuery[], + ): Promise { + return await this._handleQuery(hass, query); + } + + public async getRecordingSegments( + hass: HomeAssistant, + query: RecordingSegmentsQuery | RecordingSegmentsQuery[], + ): Promise { + return await this._handleQuery(hass, query); + } + + public async executeMediaQueries( + hass: HomeAssistant, + queries: T[], + ): Promise { + return this._convertQueryResultsToMedia( + hass, + await this._handleQuery(hass, queries), + ); + } + + public async extendMediaQueries( + hass: HomeAssistant, + queries: T[], + results: ViewMedia[], + direction: 'earlier' | 'later', + chunkSize: number, + ): Promise | null> { + const getTimeFromResults = (want: 'earliest' | 'latest'): Date | null => { + let output: Date | null = null; + for (const result of results) { + const startTime = result.getStartTime(); + if ( + startTime && + (!output || + (want === 'earliest' && startTime < output) || + (want === 'latest' && startTime > output)) + ) { + output = startTime; + } + } + return output; + }; + + // The queries associated with the chunk to fetch. + const newChunkQueries: T[] = []; + + // The re-constituted combined query. + const extendedQueries: T[] = []; + + for (const query of queries) { + const newChunkQuery = { ...query }; + + if (direction === 'later') { + const latestResult = getTimeFromResults('latest'); + if (latestResult) { + newChunkQuery.start = latestResult; + } + } else if (direction === 'earlier') { + const earliestResult = getTimeFromResults('earliest'); + if (earliestResult) { + newChunkQuery.end = earliestResult; + } + } + newChunkQuery.limit = chunkSize; + + extendedQueries.push({ + ...query, + limit: (query.limit ?? 0) + chunkSize, + }); + newChunkQueries.push(newChunkQuery); + } + + const newChunkMedia = this._convertQueryResultsToMedia( + hass, + await this._handleQuery(hass, newChunkQueries), + ); + + if (!newChunkMedia.length) { + return null; + } + + return { + queries: extendedQueries, + results: this._sortMedia(results.concat(newChunkMedia)), + }; + } + + public getMediaDownloadPath(media: ViewMedia): string | null { + const cameraConfig = this._cameras.get(media.getCameraID()); + const engine = cameraConfig + ? this._engineFactory.getEngineForCamera(cameraConfig) + : null; + if (!cameraConfig || !engine) { + return null; + } + return engine.getMediaDownloadPath(cameraConfig, media); + } + + public getCapabilities(): CameraManagerCapabilities | null { + const engines = this._engineFactory.getAllEngines(this._cameras); + if (!engines) { + return null; + } + + return { + canFavoriteEvents: engines.some( + (engine) => engine.getCapabilities()?.canFavoriteEvents, + ), + canFavoriteRecordings: engines.some( + (engine) => engine.getCapabilities()?.canFavoriteRecordings, + ), + }; + } + + public getMediaCapabilities(media: ViewMedia): CameraManagerMediaCapabilities | null { + const engine = this._engineFactory.getEngineForMedia(this._cameras, media); + if (!engine) { + return null; + } + return engine.getMediaCapabilities(media); + } + + public async favoriteMedia( + hass: HomeAssistant, + media: ViewMedia, + favorite: boolean, + ): Promise { + const cameraConfig = this._cameras.get(media.getCameraID()); + if (!cameraConfig) { + return; + } + const engine = this._engineFactory.getEngineForCamera(cameraConfig); + if (engine) { + const queryStartTime = new Date(); + await engine.favoriteMedia(hass, cameraConfig, media, favorite); + + log( + this._cardWideConfig, + 'Frigate Card CameraManager favorite request (', + `Duration: ${(new Date().getTime() - queryStartTime.getTime()) / 1000}s,`, + 'Media:', + media.getID(), + ', Favorite:', + favorite, + ')', + ); + } + } + + public areMediaQueriesResultsFresh( + queries: T[], + resultsTimestamp: Date, + ): boolean { + const now = new Date(); + + for (const query of queries) { + const engines = this._engineFactory.getEnginesForCameraIDs( + this._cameras, + query.cameraIDs, + ); + for (const [engine, cameraIDs] of engines ?? []) { + const maxAgeSeconds = engine.getQueryResultMaxAge({ + ...query, + cameraIDs: cameraIDs, + }); + if ( + maxAgeSeconds !== null && + add(resultsTimestamp, { seconds: maxAgeSeconds }) < now + ) { + return false; + } + } + } + return true; + } + + public async getMediaSeekTime( + hass: HomeAssistant, + media: ViewMedia, + target: Date, + ): Promise { + const startTime = media.getStartTime(); + const endTime = media.getEndTime(); + const cameraConfig = this._cameras.get(media.getCameraID()); + if ( + !cameraConfig || + !startTime || + !endTime || + target < startTime || + target > endTime + ) { + return null; + } + + const engine = this._engineFactory.getEngineForCamera(cameraConfig); + return (await engine?.getMediaSeekTime(hass, this._cameras, media, target)) ?? null; + } + + protected async _handleQuery( + hass: HomeAssistant, + query: QT | QT[], + ): Promise>> { + const _queries = arrayify(query); + const results = new Map>(); + const queryStartTime = new Date(); + + const processEngineQuery = async ( + engine: CameraManagerEngine, + query?: QT, + ): Promise => { + if (!query) { + return; + } + + let engineResult: Map> | null = null; + if (QueryClassifier.isEventQuery(query)) { + engineResult = (await engine.getEvents(hass, this._cameras, query)) as Map< + QT, + QueryReturnType + > | null; + } else if (QueryClassifier.isRecordingQuery(query)) { + engineResult = (await engine.getRecordings(hass, this._cameras, query)) as Map< + QT, + QueryReturnType + > | null; + } else if (QueryClassifier.isRecordingSegmentsQuery(query)) { + engineResult = (await engine.getRecordingSegments( + hass, + this._cameras, + query, + )) as Map> | null; + } + + engineResult?.forEach((value, key) => results.set(key, value)); + }; + + const processQuery = async (query: QT): Promise => { + const engines = this._engineFactory.getEnginesForCameraIDs( + this._cameras, + query.cameraIDs, + ); + if (!engines) { + return; + } + await Promise.all( + Array.from(engines.keys()).map((engine) => + processEngineQuery(engine, { ...query, cameraIDs: engines.get(engine) }), + ), + ); + }; + + await Promise.all(_queries.map((query) => processQuery(query))); + + const cachedOutputQueries = sum( + Array.from(results.values()).map((result) => Number(result.cached)), + ); + + log( + this._cardWideConfig, + 'Frigate Card CameraManager request [Input queries:', + _queries.length, + ', Cached output queries:', + cachedOutputQueries, + ', Total output queries:', + results.size, + ', Duration:', + `${(new Date().getTime() - queryStartTime.getTime()) / 1000}s,`, + ', Queries:', + _queries, + ', Results:', + results, + ']', + ); + return results; + } + + protected _convertQueryResultsToMedia( + hass: HomeAssistant, + results: ResultsMap, + ): ViewMedia[] { + const mediaArray: ViewMedia[] = []; + for (const [query, result] of results.entries()) { + const engine = this._engineFactory.getEngine(result.engine); + + if (engine) { + let media: ViewMedia[] | null = null; + if ( + QueryClassifier.isEventQuery(query) && + QueryResultClassifier.isEventQueryResult(result) + ) { + media = engine.generateMediaFromEvents(hass, this._cameras, query, result); + } else if ( + QueryClassifier.isRecordingQuery(query) && + QueryResultClassifier.isRecordingQuery(result) + ) { + media = engine.generateMediaFromRecordings(hass, this._cameras, query, result); + } + if (media) { + mediaArray.push(...media); + } + } + } + return this._sortMedia(mediaArray); + } + + protected _sortMedia(mediaArray: ViewMedia[]): ViewMedia[] { + return orderBy( + // Ensure uniqueness by the ID (if specified), otherwise all elements + // are assumed to be unique. + uniqBy(mediaArray, (media) => media.getID() ?? media), + + // Sort all items leading with the most recent. + (media) => media.getStartTime(), + 'desc', + ); + } + + public getCameraMetadata( + hass: HomeAssistant, + cameraConfig?: CameraConfig, + ): CameraManagerCameraMetadata | null { + const engine = this._engineFactory.getEngineForCamera(cameraConfig); + if (!engine || !cameraConfig) { + return null; + } + return engine.getCameraMetadata(hass, cameraConfig); + } +} diff --git a/src/camera-manager/range.ts b/src/camera-manager/range.ts new file mode 100644 index 00000000..6c3b7666 --- /dev/null +++ b/src/camera-manager/range.ts @@ -0,0 +1,125 @@ +import orderBy from 'lodash-es/orderBy'; + +interface Range { + start: T; + end: T; +} + +export type DateRange = Range; + +interface MemoryRangeSetInterface { + hasCoverage(range: T): boolean; + add(range: T): void; + clear(): void; +} + +export class MemoryRangeSet implements MemoryRangeSetInterface { + protected _ranges: DateRange[]; + + constructor(ranges?: DateRange[]) { + this._ranges = ranges ?? []; + } + + public hasCoverage(range: DateRange): boolean { + return this._ranges.some((cachedRange) => + rangeIsEntirelyContained(cachedRange, range), + ); + } + + public add(range: DateRange): void { + this._ranges.push(range); + this._ranges = compressRanges(this._ranges); + } + + public clear(): void { + this._ranges = []; + } +} + +interface ExpiringRange extends Range { + expires: Date; +} + +export class ExpiringMemoryRangeSet + implements MemoryRangeSetInterface> +{ + protected _ranges: ExpiringRange[]; + + constructor(ranges?: ExpiringRange[]) { + this._ranges = ranges ?? []; + } + + public hasCoverage(range: DateRange): boolean { + const now = new Date(); + return this._ranges.some( + (cachedRange) => + now < cachedRange.expires && rangeIsEntirelyContained(cachedRange, range), + ); + } + + public add(range: ExpiringRange): void { + this._expireOldRanges(); + this._ranges.push(range); + } + + protected _expireOldRanges(): void { + const now = new Date(); + this._ranges = this._ranges.filter((range) => now < range.expires); + } + + public clear(): void { + this._ranges = []; + } +} + +const rangeIsEntirelyContained = (bigger: DateRange, smaller: DateRange): boolean => { + return smaller.start >= bigger.start && smaller.end <= bigger.end; +}; + +export const rangesOverlap = (a: DateRange, b: DateRange): boolean => { + return ( + // a starts within the range of b. + (a.start >= b.start && a.start <= b.end) || + // a events within the range of b. + (a.end >= b.start && a.end <= b.end) || + // a encompasses the entire range of b. + (a.start <= b.start && a.end >= b.end) + ); +}; + +export const compressRanges = ( + ranges: Range[], + toleranceSeconds = 0, +): Range[] => { + const compressedRanges: Range[] = []; + ranges = orderBy(ranges, (range) => range.start, 'asc'); + + let current: Range | null = null; + for (let i = 0; i < ranges.length; ++i) { + const item = ranges[i]; + const itemStartSeconds = + item.start instanceof Date ? item.start.getTime() : item.start; + + if (!current) { + current = { ...item }; + continue; + } + + const currentEndSeconds = + current.end instanceof Date ? current.end.getTime() : (current.end as number); + + if (currentEndSeconds + toleranceSeconds * 1000 >= itemStartSeconds) { + if (item.end > current.end) { + current.end = item.end; + } + } else { + compressedRanges.push(current); + current = { ...item }; + } + } + if (current) { + compressedRanges.push(current); + } + + return compressedRanges; +}; diff --git a/src/camera-manager/types.ts b/src/camera-manager/types.ts new file mode 100644 index 00000000..2e85a6d0 --- /dev/null +++ b/src/camera-manager/types.ts @@ -0,0 +1,173 @@ +import { FrigateEvent, FrigateRecording } from './frigate/types'; + +// ==== +// Base +// ==== + +export enum QueryType { + Event = 'event-query', + Recording = 'recording-query', + RecordingSegments = 'recording-segments-query', +} + +export enum QueryResultsType { + Event = 'event-results', + Recording = 'recording-results', + RecordingSegments = 'recording-segments-results', +} + +export enum Engine { + Frigate = 'frigate', +} + +export interface DataQuery { + type: QueryType; + cameraIDs: Set; +} +export type PartialDataQuery = Partial; + +interface TimeBasedDataQuery { + start: Date; + end: Date; +} + +interface LimitedDataQuery { + limit: number; +} + +export interface MediaQuery + extends DataQuery, + Partial, + Partial { + favorite?: boolean; +} + +export interface QueryResults { + type: QueryResultsType; + engine: Engine; + expiry?: Date; + cached?: boolean; +} + +// Generic recording segment type (inspired by Frigate recording segments). +export interface RecordingSegment { + start_time: number; + end_time: number; + id: string; +} + +export type QueryReturnType = QT extends EventQuery + ? EventQueryResults + : QT extends RecordingQuery + ? RecordingQueryResults + : QT extends RecordingSegmentsQuery + ? RecordingSegmentsQueryResults + : never; +export type PartialQueryConcreteType = PQT extends PartialEventQuery + ? EventQuery + : PQT extends PartialRecordingQuery + ? RecordingQuery + : PQT extends PartialRecordingSegmentsQuery + ? RecordingSegmentsQuery + : never; + +export type ResultsMap = Map>; +export type EventQueryResultsMap = ResultsMap; +export type RecordingQueryResultsMap = ResultsMap; +export type RecordingSegmentsQueryResultsMap = ResultsMap; + +export interface MediaMetadata { + where?: Set; + what?: Set; + days?: Set; +} + +interface BaseCapabilities { + canFavoriteEvents: boolean; + canFavoriteRecordings: boolean; +} + +export type CameraManagerCapabilities = BaseCapabilities; +export type CameraManagerEngineCapabilities = BaseCapabilities; +export interface CameraManagerMediaCapabilities { + canFavorite: boolean; +} + +export interface CameraManagerCameraMetadata { + title: string; + icon: string; +} + +// =========== +// Event Query +// =========== + +export interface EventQuery extends MediaQuery { + type: QueryType.Event; + + // Frigate equivalent: has_snapshot + hasSnapshot?: boolean; + + // Frigate equivalent: has_clip + hasClip?: boolean; + + // Frigate equivalent: label + what?: Set; + + // Frigate equivalent: zone + where?: Set; +} +export type PartialEventQuery = Partial; + +export interface EventQueryResults extends QueryResults { + type: QueryResultsType.Event; +} + +// =============== +// Recording Query +// =============== + +export interface RecordingQuery extends MediaQuery { + type: QueryType.Recording; +} +export type PartialRecordingQuery = Partial; + +export interface RecordingQueryResults extends QueryResults { + type: QueryResultsType.Recording; +} + +// ======================== +// Recording Segments Query +// ======================== + +export interface RecordingSegmentsQuery extends DataQuery, TimeBasedDataQuery { + type: QueryType.RecordingSegments; +} +export type PartialRecordingSegmentsQuery = Partial; + +export interface RecordingSegmentsQueryResults extends QueryResults { + type: QueryResultsType.RecordingSegments; + segments: RecordingSegment[]; +} + +// ======================== +// Frigate concrete results +// ======================== + +export interface FrigateEventQueryResults extends EventQueryResults { + engine: Engine.Frigate; + instanceID: string; + events: FrigateEvent[]; +} + +export interface FrigateRecordingQueryResults extends RecordingQueryResults { + engine: Engine.Frigate; + instanceID: string; + recordings: FrigateRecording[]; +} + +export interface FrigateRecordingSegmentsQueryResults + extends RecordingSegmentsQueryResults { + engine: Engine.Frigate; + instanceID: string; +} diff --git a/src/camera-manager/util.ts b/src/camera-manager/util.ts new file mode 100644 index 00000000..3f4693cd --- /dev/null +++ b/src/camera-manager/util.ts @@ -0,0 +1,39 @@ +import startOfHour from 'date-fns/startOfHour'; +import endOfHour from 'date-fns/endOfHour'; +import startOfDay from 'date-fns/startOfDay'; +import endOfDay from 'date-fns/endOfDay'; +import endOfMinute from 'date-fns/endOfMinute'; +import { DateRange } from './range'; + +export const convertRangeToCacheFriendlyTimes = ( + range: DateRange, + options?: { + endCap?: boolean; + }, +): DateRange => { + const widthSeconds = (range.end.getTime() - range.start.getTime()) / 1000; + let cacheableStart: Date; + let cacheableEnd: Date; + + if (widthSeconds <= 60 * 60) { + cacheableStart = startOfHour(range.start); + cacheableEnd = endOfHour(range.end); + } else { + cacheableStart = startOfDay(range.start); + cacheableEnd = endOfDay(range.end); + } + + if (options?.endCap) { + cacheableEnd = endOfMinute(capEndDate(cacheableEnd)); + } + + return { + start: cacheableStart, + end: cacheableEnd, + }; +}; + +export const capEndDate = (end: Date): Date => { + const now = new Date(); + return end > now ? now : end; +}; diff --git a/src/card-condition.ts b/src/card-condition.ts index 36ecf49a..cdbd6113 100644 --- a/src/card-condition.ts +++ b/src/card-condition.ts @@ -21,7 +21,7 @@ class ConditionStateRequestEvent extends Event { public conditionState?: ConditionState; } -export function evaluateCondition( +function evaluateCondition( condition?: Readonly, state?: Readonly, ): boolean { @@ -180,7 +180,8 @@ export class CardConditionManager { * Trigger the callback. * @param _ Ignored parameter. */ - protected _triggerChange(_): void { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected _triggerChange(_: MediaQueryListEvent): void { this._callback(); } diff --git a/src/card.ts b/src/card.ts index 52377462..20458682 100644 --- a/src/card.ts +++ b/src/card.ts @@ -53,8 +53,6 @@ import { FrigateCardView, FRIGATE_CARD_VIEWS_USER_SPECIFIED, MediaLoadedInfo, - MEDIA_TYPE_IMAGE, - MEDIA_TYPE_VIDEO, MESSAGE_TYPE_PRIORITIES, MenuButton, Message, @@ -69,7 +67,7 @@ import { getActionConfigGivenAction, } from './utils/action.js'; import { contentsChanged, errorToConsole } from './utils/basic.js'; -import { getCameraIcon, getCameraID, getCameraTitle } from './utils/camera.js'; +import { getCameraID } from './utils/camera.js'; import { getEntityIcon, getEntityTitle, @@ -80,7 +78,6 @@ import { isTriggeredState, sideLoadHomeAssistantElements, } from './utils/ha'; -import { getEventID } from './utils/ha/browse-media.js'; import { DeviceList, getAllDevices } from './utils/ha/device-registry.js'; import { ExtendedEntityCache, @@ -91,11 +88,13 @@ import { import { ResolvedMediaCache } from './utils/ha/resolved-media.js'; import { supportsFeature } from './utils/ha/update.js'; import { isValidMediaLoadedInfo } from './utils/media-info.js'; -import { View } from './view.js'; +import { View } from './view/view.js'; import pkg from '../package.json'; import { ViewContext } from 'view'; -import { DataManager } from './utils/data-manager.js'; +import { CameraManager } from './camera-manager/manager.js'; import { setLowPerformanceProfile, setPerformanceCSSStyles } from './performance.js'; +import { CameraManagerEngineFactory } from './camera-manager/engine-factory.js'; +import { log } from './utils/debug.js'; /** A note on media callbacks: * @@ -144,11 +143,13 @@ console.info( documentationURL: REPO_URL, }); +type InitializedType = 'initialized' | 'initializing'; + /** * Main FrigateCard class. */ @customElement('frigate-card') -export class FrigateCard extends LitElement { +class FrigateCard extends LitElement { @state() protected _hass?: ExtendedHomeAssistant; @@ -204,16 +205,14 @@ export class FrigateCard extends LitElement { // A cache of resolved media URLs/mimetypes for use in the whole card. protected _resolvedMediaCache = new ResolvedMediaCache(); - // Shared timeline data manager (for main timeline view and mini-timelines). - protected _dataManager?: DataManager; + protected _cameraManager?: CameraManager; // The mouse handler may be called continually, throttle it to at most once // per second for performance reasons. protected _boundMouseHandler = throttle(this._mouseHandler.bind(this), 1 * 1000); // Whether the card has been successfully initialized. - protected _loadedHAElements = false; - protected _loadedLanguages = false; + protected _initialized?: InitializedType; protected _triggers: Map = new Map(); protected _untriggerTimerID: number | null = null; @@ -404,12 +403,16 @@ export class FrigateCard extends LitElement { const action = createFrigateCardCustomAction('camera_select', { camera: camera, }); + const metadata = this._hass + ? this._cameraManager?.getCameraMetadata(this._hass, config) ?? undefined + : undefined; + return { enabled: true, - icon: getCameraIcon(this._hass, config), + icon: metadata?.icon, entity: config.camera_entity, state_color: true, - title: getCameraTitle(this._hass, config), + title: metadata?.title, selected: this._view?.camera === camera, ...(action && { tap_action: action }), }; @@ -530,7 +533,8 @@ export class FrigateCard extends LitElement { if ( !this._isBeingCasted() && - (this._view?.isViewerView() || (this._view?.is('timeline') && !!this._view?.media)) + (this._view?.isViewerView() || + (this._view?.is('timeline') && !!this._view?.queryResults?.hasSelectedResult())) ) { buttons.push({ icon: 'mdi:download', @@ -989,6 +993,7 @@ export class FrigateCard extends LitElement { this._config = config; this._cardWideConfig = { performance: config.performance, + debug: config.debug, }; this._overriddenConfig = undefined; @@ -1016,13 +1021,16 @@ export class FrigateCard extends LitElement { } protected _changeView(args?: { view?: View; resetMessage?: boolean }): void { + log(this._cardWideConfig, `Frigate Card view change: `, args?.view ?? '[default]'); const changeView = (view: View): void => { if (View.isMediaChange(this._view, view)) { this._currentMediaLoadedInfo = null; } + if (this._view?.view !== view.view) { + this._resetMainScroll(); + } this._view = view; this._generateConditionState(); - this._resetMainScroll(); }; if (args?.resetMessage ?? true) { @@ -1099,18 +1107,18 @@ export class FrigateCard extends LitElement { * Called before each update. */ protected willUpdate(changedProps: PropertyValues): void { - // Side load the necessary elements if not already initialized (do not need - // to block for the loading to complete). - if (!this._loadedHAElements) { - sideLoadHomeAssistantElements().then((success) => { - if (success) { - this._loadedHAElements = true; - } - }); - } - - if (this._cameras && (changedProps.has('_config') || changedProps.has('_cameras'))) { - this._dataManager = new DataManager(this._cameras); + if ( + this._cameras && + this._cardWideConfig && + (changedProps.has('_config') || + changedProps.has('_cameras') || + changedProps.has('_cardWideConfig')) + ) { + this._cameraManager = new CameraManager( + new CameraManagerEngineFactory(this._cardWideConfig), + this._cameras, + this._cardWideConfig, + ); } if (changedProps.has('_cardWideConfig')) { @@ -1237,6 +1245,12 @@ export class FrigateCard extends LitElement { } } + /** + * Initialize the card. + */ + protected async _initialize(): Promise { + await Promise.all([sideLoadHomeAssistantElements(), loadLanguages()]); + } /** * Determine whether the element should be updated. * @param changedProps The changed properties if any. @@ -1244,11 +1258,13 @@ export class FrigateCard extends LitElement { */ protected shouldUpdate(changedProps: PropertyValues): boolean { // Load the relevant languages. Cannot do anything until then. - if (!this._loadedLanguages) { - loadLanguages().then(() => { - this._loadedLanguages = true; - this.requestUpdate(); - }); + if (this._initialized !== 'initialized') { + if (this._initialized !== 'initializing') { + this._initialize().then(() => { + this._initialized = 'initialized'; + this.requestUpdate(); + }); + } return false; } @@ -1318,12 +1334,9 @@ export class FrigateCard extends LitElement { // Should not occur. return; } + const media = this._view.queryResults?.getSelectedResult(); - if ( - !this._view.media || - (this._view.media.media_content_type !== MEDIA_TYPE_VIDEO && - this._view.media.media_content_type !== MEDIA_TYPE_IMAGE) - ) { + if (!media) { this._setMessageAndUpdate({ message: localize('error.download_no_media'), type: 'error', @@ -1336,35 +1349,8 @@ export class FrigateCard extends LitElement { return; } - let path: string; - if (this._view.media.frigate?.event) { - const event_id = getEventID(this._view.media); - if (!event_id) { - this._setMessageAndUpdate({ - message: localize('error.download_no_event_id'), - type: 'error', - }); - return; - } - - path = - `/api/frigate/${cameraConfig.frigate.client_id}` + - `/notifications/${event_id}/` + - `${ - this._view.media.media_content_type === MEDIA_TYPE_VIDEO - ? 'clip.mp4' - : 'snapshot.jpg' - }` + - `?download=true`; - } else if (this._view.media.frigate?.recording) { - const recording = this._view.media.frigate.recording; - path = - `/api/frigate/${cameraConfig.frigate.client_id}` + - `/recording/${cameraConfig.frigate.camera_name}` + - `/start/${recording.start_time}` + - `/end/${recording.end_time}` + - `?download=true`; - } else { + const path = this._cameraManager?.getMediaDownloadPath(media); + if (!path) { return; } @@ -1412,30 +1398,40 @@ export class FrigateCard extends LitElement { * @returns */ protected _mediaPlayerAction(mediaPlayer: string, action: 'play' | 'stop'): void { - if (!['play', 'stop'].includes(action)) { + if ( + !['play', 'stop'].includes(action) || + !this._view || + !this._hass || + !this._cameraManager + ) { return; } - let media_content_id: string; - let media_content_type: string; - const extra = {}; - const cameraConfig = this._getSelectedCameraConfig(); - const cameraEntity = cameraConfig?.camera_entity ?? null; + let media_content_id: string | null = null; + let media_content_type: string | null = null; + let title: string | null = null; + let thumbnail: string | null = null; - if (this._view?.isViewerView() && this._view.media) { - media_content_id = this._view.media.media_content_id; - media_content_type = this._view.media.media_content_type; - extra['thumb'] = this._view.media.thumbnail; - extra['title'] = this._view.media.title; + const cameraConfig = this._getSelectedCameraConfig(); + if (!cameraConfig) { + return; + } + const cameraEntity = cameraConfig.camera_entity ?? null; + const media = this._view.queryResults?.getSelectedResult(); + + if (this._view.isViewerView() && media && this._cameras) { + media_content_id = media.getContentID(); + media_content_type = media.getContentType(); + title = media.getTitle(); + thumbnail = media.getThumbnail(); } else if (this._view?.is('live') && cameraEntity) { - if (this._hass?.states && cameraEntity in this._hass.states) { - extra['thumb'] = - this._hass.states[cameraEntity].attributes.entity_picture ?? null; - } - extra['title'] = getCameraTitle(this._hass, cameraConfig); media_content_id = `media-source://camera/${cameraEntity}`; media_content_type = 'application/vnd.apple.mpegurl'; - } else { + title = this._cameraManager.getCameraMetadata(this._hass, cameraConfig)?.title ?? null; + thumbnail = this._hass?.states[cameraEntity]?.attributes?.entity_picture ?? null; + } + + if (!media_content_id || !media_content_type) { return; } @@ -1444,7 +1440,10 @@ export class FrigateCard extends LitElement { entity_id: mediaPlayer, media_content_id: media_content_id, media_content_type: media_content_type, - extra: extra, + extra: { + ...(title && { title: title }), + ...(thumbnail && { thumb: thumbnail }), + }, }); } else if (action === 'stop') { this._hass?.callService('media_player', 'media_stop', { @@ -1576,6 +1575,7 @@ export class FrigateCard extends LitElement { date: new Date(), frigate_version: Object.fromEntries(frigateVersionMap), lang: getLanguage(), + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, git: { ...(pkg['gitVersion'] && { build_version: pkg['gitVersion'] }), ...(pkg['buildDate'] && { build_date: pkg['buildDate'] }), @@ -2037,7 +2037,7 @@ export class FrigateCard extends LitElement { .view=${this._view} .cameras=${this._cameras} .galleryConfig=${this._getConfig().event_gallery} - .dataManager=${this._dataManager} + .cameraManager=${this._cameraManager} .cardWideConfig=${this._cardWideConfig} > ` @@ -2049,7 +2049,7 @@ export class FrigateCard extends LitElement { .cameras=${this._cameras} .viewerConfig=${this._getConfig().media_viewer} .resolvedMediaCache=${this._resolvedMediaCache} - .dataManager=${this._dataManager} + .cameraManager=${this._cameraManager} .cardWideConfig=${this._cardWideConfig} > ` @@ -2060,7 +2060,7 @@ export class FrigateCard extends LitElement { .view=${this._view} .cameras=${this._cameras} .timelineConfig=${this._getConfig().timeline} - .dataManager=${this._dataManager} + .cameraManager=${this._cameraManager} > ` : ``} @@ -2081,7 +2081,7 @@ export class FrigateCard extends LitElement { .conditionState=${this._conditionState} .liveOverrides=${getOverridesByKey(this._getConfig().overrides, 'live')} .cameras=${this._cameras} - .dataManager=${this._dataManager} + .cameraManager=${this._cameraManager} .cardWideConfig=${this._cardWideConfig} class="${classMap(liveClasses)}" > diff --git a/src/components/carousel.ts b/src/components/carousel.ts index 8199eb7c..e1ab24bf 100644 --- a/src/components/carousel.ts +++ b/src/components/carousel.ts @@ -41,15 +41,12 @@ export class FrigateCardCarousel extends LitElement { @property({ attribute: false }) public carouselPlugins?: EmblaCarouselPlugins; + @property({ attribute: false }) + public selected = 0; + @property({ attribute: true }) public transitionEffect?: TransitionEffect; - // An override to the startIndex, used to preserve the current carousel - // position after the carousel is destroyed (so it can be restored if - // recreated). - // See: https://github.com/dermotduffy/frigate-hass-card/issues/775 - protected _savedStartIndex: number | null = null; - protected _refSlot: Ref = createRef(); protected _carousel?: EmblaCarouselType; @@ -81,7 +78,7 @@ export class FrigateCardCarousel extends LitElement { // Destroy the carousel when the component is disconnected, which forces the // plugins (which may have registered event handlers) to also be destroyed. // The carousel will automatically reconstruct if the component is re-rendered. - this._destroyCarousel({ savePosition: true }); + this._destroyCarousel(); super.disconnectedCallback(); } @@ -96,42 +93,10 @@ export class FrigateCardCarousel extends LitElement { 'carouselPlugins', ] as const; if (destroyProperties.some((prop) => changedProps.has(prop))) { - this._destroyCarousel({ savePosition: true }); + this._destroyCarousel(); } } - /** - * Scroll to a particular slide. - * @param index Slide number. - */ - public carouselScrollTo(index: number): void { - const scroll = () => - this._carousel?.scrollTo(index, this.transitionEffect === 'none'); - // This ensures scrolling can work on initial render when the carousel may - // not yet exist. - if (this._carousel) { - scroll(); - } else { - this.updateComplete.then(() => { - scroll(); - }); - } - } - - /** - * Scroll to the previous slide. - */ - public carouselScrollPrevious(): void { - this._carousel?.scrollPrev(this.transitionEffect === 'none'); - } - - /** - * Scroll to the next slide. - */ - public carouselScrollNext(): void { - this._carousel?.scrollNext(this.transitionEffect === 'none'); - } - /** * Get the selected slide. * @returns A CarouselSelect object (index & element). @@ -174,11 +139,10 @@ export class FrigateCardCarousel extends LitElement { window.requestAnimationFrame(() => { this._carousel?.reInit({ ...options }); }); - } - const selected = this.getCarouselSelected(); + }; carouselReInit({ - ...(selected && { startIndex: selected.index }), + startIndex: this.selected, }); } @@ -211,6 +175,10 @@ export class FrigateCardCarousel extends LitElement { if (!this._carousel) { this._initCarousel(); } + + if (changedProperties.has('selected')) { + this._carousel?.scrollTo(this.selected, this.transitionEffect === 'none'); + } } /** @@ -218,9 +186,7 @@ export class FrigateCardCarousel extends LitElement { * @param options If `savePosition` is set the existing carousel position * will be saved so it can be restored if the carousel is recreated. */ - protected _destroyCarousel(options?: { savePosition: boolean }): void { - this._savedStartIndex = - (options?.savePosition ? this._carousel?.selectedScrollSnap() : null) ?? null; + protected _destroyCarousel(): void { if (this._carousel) { this._carousel.destroy(); } @@ -248,8 +214,8 @@ export class FrigateCardCarousel extends LitElement { { axis: this.direction == 'horizontal' ? 'x' : 'y', speed: 20, + startIndex: this.selected, ...this.carouselOptions, - ...(this._savedStartIndex !== null && { startIndex: this._savedStartIndex }), }, this.carouselPlugins, ); @@ -262,7 +228,7 @@ export class FrigateCardCarousel extends LitElement { // Make sure every select causes a refresh to allow for re-paint of the // next/previous controls. this.requestUpdate(); - } + }; this._carousel.on('init', selectSlide); this._carousel.on('select', selectSlide); @@ -294,18 +260,14 @@ export class FrigateCardCarousel extends LitElement { protected _slotChanged(): void { // Cannot just re-init, because the slide elements themselves may have // changed, and only a carousel init can pass in new (slotted) children. If - // the slides themselves change, any position the user has set is assumed to - // be abandoned and so the startIndex is reset to whatever the carousel was - // originally configured with. - this._destroyCarousel({ savePosition: false }); + this._destroyCarousel(); this.requestUpdate(); } protected render(): TemplateResult | void { const slides = this._refSlot.value?.assignedElements({ flatten: true }) || []; - const currentSlide = (this._carousel?.selectedScrollSnap() ?? this.carouselOptions?.startIndex) ?? 0; - const showPrevious = this.carouselOptions?.loop || currentSlide > 0; - const showNext = this.carouselOptions?.loop || currentSlide + 1 < slides.length; + const showPrevious = this.carouselOptions?.loop || this.selected > 0; + const showNext = this.carouselOptions?.loop || this.selected + 1 < slides.length; return html`
${showPrevious ? html`` : ``} diff --git a/src/components/drawer.ts b/src/components/drawer.ts index dfff296a..0639c707 100644 --- a/src/components/drawer.ts +++ b/src/components/drawer.ts @@ -15,6 +15,11 @@ import drawerStyle from '../scss/drawer.scss'; import { stopEventFromActivatingCardWideActions } from '../utils/action'; import { isHoverableDevice } from '../utils/basic'; +export interface DrawerIcons { + open?: string; + closed?: string; +} + @customElement('frigate-card-drawer') export class FrigateCardDrawer extends LitElement { @property({ attribute: true, reflect: true }) @@ -26,6 +31,9 @@ export class FrigateCardDrawer extends LitElement { @property({ type: Boolean, reflect: true, attribute: true }) public open = false; + @property({ attribute: false }) + public icons?: DrawerIcons; + // The 'empty' attribute is used in the styling to change the drawer // visibility and that of all descendants if there is no content. Styling is // used rather than display or hidden in order to ensure the contents continue @@ -111,7 +119,9 @@ export class FrigateCardDrawer extends LitElement { > { // Only open the drawer on mousenter when the device // supports hover (otherwise iOS may end up passing on @@ -126,7 +136,7 @@ export class FrigateCardDrawer extends LitElement {
` : ''} - + this._slotChanged()}> `; } diff --git a/src/components/embla-plugins/automedia.ts b/src/components/embla-plugins/automedia.ts index 078eac90..a4f3dbd6 100644 --- a/src/components/embla-plugins/automedia.ts +++ b/src/components/embla-plugins/automedia.ts @@ -27,7 +27,7 @@ const defaultOptions: OptionsType = { breakpoints: {}, }; -export type AutoMediaOptionsType = Partial +type AutoMediaOptionsType = Partial export type AutoMediaType = CreatePluginType< { diff --git a/src/components/embla-plugins/lazyload.ts b/src/components/embla-plugins/lazyload.ts index 3fbdb9f4..311ee6cc 100644 --- a/src/components/embla-plugins/lazyload.ts +++ b/src/components/embla-plugins/lazyload.ts @@ -3,7 +3,7 @@ import { CreatePluginType } from 'embla-carousel/components/Plugins'; import EmblaCarousel, { EmblaCarouselType, EmblaEventType } from 'embla-carousel'; import { LazyUnloadCondition } from '../../types'; -export type OptionsType = CreateOptionsType<{ +type OptionsType = CreateOptionsType<{ // Number of slides to lazyload left/right of selected (0 == only selected // slide). lazyLoadCount?: number; @@ -13,15 +13,15 @@ export type OptionsType = CreateOptionsType<{ lazyUnloadCallback?: (index: number, slide: HTMLElement) => void; }>; -export const defaultOptions: OptionsType = { +const defaultOptions: OptionsType = { active: true, breakpoints: {}, lazyLoadCount: 0, }; -export type LazyloadOptionsType = Partial; +type LazyloadOptionsType = Partial; -export type LazyloadType = CreatePluginType< +type LazyloadType = CreatePluginType< { hasLazyloaded(index: number): boolean; }, diff --git a/src/components/gallery.ts b/src/components/gallery.ts index 9a4c9816..2959e4ca 100644 --- a/src/components/gallery.ts +++ b/src/components/gallery.ts @@ -7,7 +7,7 @@ import { TemplateResult, unsafeCSS, } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; +import { customElement, property, state } from 'lit/decorators.js'; import galleryStyle from '../scss/gallery.scss'; import { CameraConfig, @@ -19,27 +19,29 @@ import { } from '../types.js'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; import { - fetchChildMediaAndDispatchViewChange, - fetchLatestMediaAndDispatchViewChange, - getFullDependentBrowseMediaQueryParametersOrDispatchError, -} from '../utils/ha/browse-media'; -import { changeViewToRecentRecordingForCameraAndDependents } from '../utils/media-to-view.js'; -import { DataManager } from '../utils/data-manager.js'; -import { View } from '../view.js'; + changeViewToRecentEventsForCameraAndDependents, + changeViewToRecentRecordingForCameraAndDependents, +} from '../utils/media-to-view.js'; +import { CameraManager, ExtendedMediaQueryResult } from '../camera-manager/manager.js'; +import { View } from '../view/view.js'; import { renderProgressIndicator } from './message.js'; import './thumbnail.js'; import { THUMBNAIL_DETAILS_WIDTH_MIN } from './thumbnail.js'; +import { createRef, Ref } from 'lit/directives/ref.js'; +import { MediaQueriesClassifier } from '../view/media-queries-classifier'; +import { EventMediaQueries, RecordingMediaQueries } from '../view/media-queries'; +import { EventQuery, MediaQuery, RecordingQuery } from '../camera-manager/types'; +import { MediaQueriesResults } from '../view/media-queries-results'; +import { errorToConsole } from '../utils/basic'; +import './media-filter'; +import "./surround-basic"; -interface GalleryViewContext { - // Keep track of the previous view to allow returning to a higher-level folder. - previous?: View; -} +const GALLERY_MEDIA_CHUNK_SIZE = 100; -declare module 'view' { - interface ViewContext { - gallery?: GalleryViewContext; - } -} +const GALLERY_MEDIA_FILTER_MENU_ICONS = { + closed: 'mdi:filter-cog-outline', + open: 'mdi:filter-cog', +}; @customElement('frigate-card-gallery') export class FrigateCardGallery extends LitElement { @@ -56,7 +58,7 @@ export class FrigateCardGallery extends LitElement { public cameras?: Map; @property({ attribute: false }) - public dataManager?: DataManager; + public cameraManager?: CameraManager; @property({ attribute: false }) public cardWideConfig?: CardWideConfig; @@ -66,62 +68,75 @@ export class FrigateCardGallery extends LitElement { * @returns A rendered template. */ protected render(): TemplateResult | void { - const mediaType = this.view?.getMediaType(); if ( !this.hass || !this.view || !this.cameras || !this.view.isGalleryView() || - !mediaType || - !this.dataManager + !this.cameraManager ) { return; } - if (!this.view.target) { - if (mediaType === 'recordings') { + if (!this.view.query) { + if (this.view.is('recordings')) { changeViewToRecentRecordingForCameraAndDependents( this, this.hass, - this.dataManager, + this.cameraManager, + this.cameras, + this.view, + ); + } else { + const mediaType = this.view.is('snapshots') + ? 'snapshots' + : this.view.is('clips') + ? 'clips' + : null; + changeViewToRecentEventsForCameraAndDependents( + this, + this.hass, + this.cameraManager, this.cameras, this.view, { - targetView: 'recordings', + ...(mediaType && { mediaType: mediaType }), }, ); - } else { - const browseMediaQueryParameters = - getFullDependentBrowseMediaQueryParametersOrDispatchError( - this, - this.hass, - this.cameras, - this.view.camera, - mediaType, - ); - - if (!browseMediaQueryParameters) { - return; - } - - fetchLatestMediaAndDispatchViewChange( - this, - this.hass, - this.view, - browseMediaQueryParameters, - ); } return renderProgressIndicator({ cardWideConfig: this.cardWideConfig }); } return html` - - + ${this.galleryConfig && this.galleryConfig.controls.filter.mode !== 'none' + ? html` + ` + : ''} + + + `; } @@ -153,11 +168,25 @@ export class FrigateCardGalleryCore extends LitElement { @property({ attribute: false }) public cameras?: Map; + @property({ attribute: false }) + public cameraManager?: CameraManager; + + @property({ attribute: false }) + public cardWideConfig?: CardWideConfig; + + protected _intersectionObserver: IntersectionObserver; protected _resizeObserver: ResizeObserver; + protected _refLoader: Ref = createRef(); + + @state() + protected _showExtensionLoader = true; constructor() { super(); this._resizeObserver = new ResizeObserver(this._resizeHandler.bind(this)); + this._intersectionObserver = new IntersectionObserver( + this._intersectionHandler.bind(this), + ); } /** @@ -166,6 +195,10 @@ export class FrigateCardGalleryCore extends LitElement { connectedCallback(): void { super.connectedCallback(); this._resizeObserver.observe(this); + + // Request update in order to ensure the intersection observer reconnects + // with the loader sentinel. + this.requestUpdate(); } /** @@ -173,6 +206,7 @@ export class FrigateCardGalleryCore extends LitElement { */ disconnectedCallback(): void { this._resizeObserver.disconnect(); + this._intersectionObserver.disconnect(); super.disconnectedCallback(); } @@ -201,16 +235,55 @@ export class FrigateCardGalleryCore extends LitElement { this._setColumnCount(); } - /** - * Determine whether the back arrow should be displayed. - * @returns `true` if the back arrow should be displayed, `false` otherwise. - */ - protected _showBackArrow(): boolean { - return ( - !!this.view?.context?.gallery?.previous && - !!this.view.context.gallery.previous.target && - this.view.context.gallery.previous.view === this.view.view - ); + protected async _intersectionHandler( + entries: IntersectionObserverEntry[], + ): Promise { + if (!this.cameraManager || !this.hass || !this.view) { + return; + } + if (entries.every((entry) => !entry.isIntersecting)) { + return; + } + + this._showExtensionLoader = false; + + const query = this.view?.query; + const rawQueries = query?.getQueries() ?? null; + const existingMedia = this.view.queryResults?.getResults(); + if (!query || !rawQueries || !existingMedia) { + return; + } + + let extension: ExtendedMediaQueryResult | null; + try { + extension = await this.cameraManager.extendMediaQueries( + this.hass, + rawQueries, + existingMedia, + 'earlier', + GALLERY_MEDIA_CHUNK_SIZE, + ); + } catch (e) { + errorToConsole(e as Error); + return; + } + + if (extension) { + const newMediaQueries = MediaQueriesClassifier.areEventQueries(query) + ? new EventMediaQueries(extension.queries as EventQuery[]) + : MediaQueriesClassifier.areRecordingQueries(query) + ? new RecordingMediaQueries(extension.queries as RecordingQuery[]) + : null; + + if (newMediaQueries) { + this.view + ?.evolve({ + query: newMediaQueries, + queryResults: new MediaQueriesResults(extension.results), + }) + .dispatchChangeEvent(this); + } + } } /** @@ -232,6 +305,9 @@ export class FrigateCardGalleryCore extends LitElement { ); } } + if (changedProps.has('view')) { + this._showExtensionLoader = true; + } } /** @@ -239,11 +315,12 @@ export class FrigateCardGalleryCore extends LitElement { * @returns A rendered template. */ protected render(): TemplateResult | void { + const results = this.view?.queryResults?.getResults(); + if ( + !results || !this.hass || !this.view || - !this.view.target || - !this.view.target.children || !this.view.isGalleryView() || !this.cameras ) { @@ -251,80 +328,49 @@ export class FrigateCardGalleryCore extends LitElement { } return html` - ${this._showBackArrow() - ? html` { - if (this.view && this.view.context?.gallery?.previous) { - this.view.context.gallery.previous.dispatchChangeEvent(this); + ${results.map( + (media, index) => + html` { + if (this.view) { + this.view + .evolve({ + view: 'media', + queryResults: this.view.queryResults?.clone().selectResult(index), + }) + .dispatchChangeEvent(this); } stopEventFromActivatingCardWideActions(ev); }} - outlined="" > - - ` - : ''} - ${this.view.target.children.map( - (child, index) => - html` - ${child.can_expand - ? html` - { - if (this.hass && this.view) { - fetchChildMediaAndDispatchViewChange( - this, - this.hass, - this.view, - child, - { - gallery: { - previous: this.view, - }, - }, - ); - } - stopEventFromActivatingCardWideActions(ev); - }} - outlined="" - > -
${child.title}
-
- ` - : html` { - if (this.view) { - const targetView = this.view.getViewerViewForGalleryView(); - if (targetView) { - this.view - .evolve({ - view: targetView, - childIndex: index, - }) - .dispatchChangeEvent(this); - } - } - stopEventFromActivatingCardWideActions(ev); - }} - > - `} - `, + `, )} + ${this._showExtensionLoader + ? html`${renderProgressIndicator({ + cardWideConfig: this.cardWideConfig, + componentRef: this._refLoader, + })}` + : ''} `; } + public updated(): void { + if (this._refLoader.value) { + this._intersectionObserver.disconnect(); + this._intersectionObserver.observe(this._refLoader.value); + } + } + /** * Get styles. */ diff --git a/src/components/image.ts b/src/components/image.ts index 2975f33e..7f9144c9 100644 --- a/src/components/image.ts +++ b/src/components/image.ts @@ -22,7 +22,7 @@ import { createMediaLoadedInfo, dispatchExistingMediaLoadedInfoAsEvent, } from '../utils/media-info.js'; -import { View } from '../view.js'; +import { View } from '../view/view.js'; import { dispatchErrorMessageEvent } from './message.js'; import { contentsChanged } from '../utils/basic.js'; import isEqual from 'lodash-es/isEqual'; diff --git a/src/components/live/live.ts b/src/components/live/live.ts index 67a7c823..e7f68134 100644 --- a/src/components/live/live.ts +++ b/src/components/live/live.ts @@ -32,13 +32,11 @@ import { } from '../../types.js'; import { stopEventFromActivatingCardWideActions } from '../../utils/action.js'; import { contentsChanged } from '../../utils/basic.js'; -import { getCameraIcon, getCameraTitle } from '../../utils/camera.js'; -import { getFullDependentBrowseMediaQueryParameters } from '../../utils/ha/browse-media.js'; import { dispatchExistingMediaLoadedInfoAsEvent, dispatchMediaUnloadedEvent, } from '../../utils/media-info.js'; -import { dispatchViewContextChangeEvent, View } from '../../view.js'; +import { dispatchViewContextChangeEvent, View } from '../../view/view.js'; import { AutoMediaPlugin } from './../embla-plugins/automedia.js'; import { Lazyload } from './../embla-plugins/lazyload.js'; import { @@ -49,10 +47,10 @@ import { import '../next-prev-control.js'; import '../title-control.js'; import '../surround.js'; -import { EmblaCarouselPlugins } from '../carousel.js'; +import { CarouselSelect, EmblaCarouselPlugins } from '../carousel.js'; import { classMap } from 'lit/directives/class-map.js'; import { updateElementStyleFromMediaLayoutConfig } from '../../utils/media-layout.js'; -import { DataManager } from '../../utils/data-manager.js'; +import { CameraManager } from '../../camera-manager/manager.js'; import { HomeAssistant } from 'custom-card-helpers'; import { dispatchMessageEvent, dispatchErrorMessageEvent } from '../message.js'; import { HassEntity } from 'home-assistant-js-websocket'; @@ -88,7 +86,7 @@ export const getStateObjOrDispatchError = ( if (stateObj.state === 'unavailable') { dispatchMessageEvent(element, localize('error.live_camera_unavailable'), 'info', { icon: 'mdi:connection', - context: getCameraTitle(hass, cameraConfig), + context: cameraConfig, }); return null; } @@ -116,7 +114,7 @@ export class FrigateCardLive extends LitElement { public liveOverrides?: LiveOverrides; @property({ attribute: false }) - public dataManager?: DataManager; + public cameraManager?: CameraManager; @property({ attribute: false }) public cardWideConfig?: CardWideConfig; @@ -212,16 +210,6 @@ export class FrigateCardLive extends LitElement { this.conditionState, ) as LiveConfig; - // Does not use getFullDependentBrowseMediaQueryParametersOrDispatchError to - // ensure that non-Frigate cameras will work in live view (they will not - // have a Frigate camera name). - const browseMediaParams = getFullDependentBrowseMediaQueryParameters( - this.hass, - this.cameras, - this.view.camera, - config.controls.thumbnails.media, - ); - // Notes: // - See use of liveConfig and not config below -- the carousel will // independently override the liveConfig to reflect the camera in the @@ -238,12 +226,11 @@ export class FrigateCardLive extends LitElement { html`) => { this._renderKey++; @@ -273,6 +260,7 @@ export class FrigateCardLive extends LitElement { .conditionState=${this.conditionState} .liveOverrides=${this.liveOverrides} .cardWideConfig=${this.cardWideConfig} + .cameraManager=${this.cameraManager} > `, @@ -316,6 +304,9 @@ export class FrigateCardLiveCarousel extends LitElement { @property({ attribute: false }) public cardWideConfig?: CardWideConfig; + @property({ attribute: false }) + public cameraManager?: CameraManager; + // Index between camera name and slide number. protected _cameraToSlide: Record = {}; protected _refMediaCarousel: Ref = createRef(); @@ -328,30 +319,8 @@ export class FrigateCardLiveCarousel extends LitElement { super.updated(changedProperties); const frigateCardMediaCarousel = this._refMediaCarousel.value; - const frigateCardCarousel = frigateCardMediaCarousel?.frigateCardCarousel(); - if (changedProperties.has('view')) { - const oldView = changedProperties.get('view') as View | undefined; - if ( - frigateCardCarousel && - this.view?.camera && - (!oldView || this.view?.camera !== oldView.camera) - ) { - const slide: number | undefined = this._cameraToSlide[this.view.camera]; - if ( - slide !== undefined && - slide !== frigateCardCarousel.getCarouselSelected()?.index - ) { - frigateCardCarousel.carouselScrollTo(slide); - } - } - } - - if ( - frigateCardMediaCarousel && - frigateCardCarousel && - changedProperties.has('inBackground') - ) { + if (frigateCardMediaCarousel && changedProperties.has('inBackground')) { // If this has changed to be in the background (i.e. preloaded but not // visible) take the appropriate play/pause/mute/unmute actions. if (this.inBackground) { @@ -375,16 +344,19 @@ export class FrigateCardLiveCarousel extends LitElement { ); } + protected _getSelectedCameraIndex(): number { + if (!this.cameras || !this.view) { + return 0; + } + return Math.max(0, Array.from(this.cameras.keys()).indexOf(this.view.camera)); + } + /** * Get the Embla options to use. * @returns An EmblaOptionsType object or undefined for no options. */ protected _getOptions(): EmblaOptionsType { return { - startIndex: - this.cameras && this.view - ? Math.max(0, Array.from(this.cameras.keys()).indexOf(this.view.camera)) - : 0, draggable: this.liveConfig?.draggable, loop: true, }; @@ -472,26 +444,26 @@ export class FrigateCardLiveCarousel extends LitElement { /** * Handle the user selecting a new slide in the carousel. */ - protected _setViewHandler(): void { - const selectedCameraIndex = this._refMediaCarousel.value - ?.frigateCardCarousel() - ?.getCarouselSelected()?.index; - if (selectedCameraIndex === undefined || !this.view || !this.cameras) { - return; + protected _setViewHandler(ev: CustomEvent): void { + if (this.cameras && ev.detail.index !== this._getSelectedCameraIndex()) { + this._setViewCameraID(Array.from(this.cameras.keys())[ev.detail.index]); } + } - this.view - .evolve({ - camera: Array.from(this.cameras.keys())[selectedCameraIndex], - - // Reset the target. - target: null, - childIndex: null, - }) - // Don't yet fetch thumbnails (they will be fetched when the carousel - // settles). - .mergeInContext({ thumbnails: { fetch: false } }) - .dispatchChangeEvent(this); + protected _setViewCameraID(cameraID?: string | null): void { + if (cameraID) { + this.view + ?.evolve({ + camera: cameraID, + // Reset the query and query results. + query: null, + queryResults: null, + }) + // Don't yet fetch thumbnails (they will be fetched when the carousel + // settles). + .mergeInContext({ thumbnails: { fetch: false } }) + .dispatchChangeEvent(this); + } } /** @@ -521,7 +493,7 @@ export class FrigateCardLiveCarousel extends LitElement { cameraConfig: CameraConfig, slideIndex: number, ): TemplateResult | void { - if (!this.liveConfig) { + if (!this.liveConfig || !this.hass || !this.cameraManager) { return; } // The conditionState object contains the currently live camera, which (in @@ -538,12 +510,14 @@ export class FrigateCardLiveCarousel extends LitElement { conditionState, ) as LiveConfig; + const cameraMetadata = this.cameraManager.getCameraMetadata(this.hass, cameraConfig); + return html`
0 ? currentIndex - 1 : this.cameras.size - 1], - ) ?? null; - const next = - this.cameras.get( - keys[currentIndex + 1 < this.cameras.size ? currentIndex + 1 : 0], - ) ?? null; - return [prev, next]; + return [ + keys[currentIndex > 0 ? currentIndex - 1 : this.cameras.size - 1], + keys[currentIndex + 1 < this.cameras.size ? currentIndex + 1 : 0], + ]; } /** @@ -588,7 +557,14 @@ export class FrigateCardLiveCarousel extends LitElement { protected render(): TemplateResult | void { const [slides, cameraToSlide] = this._getSlides(); this._cameraToSlide = cameraToSlide; - if (!slides.length || !this.liveConfig || !this.cameras || !this.view) { + if ( + !slides.length || + !this.liveConfig || + !this.cameras || + !this.view || + !this.hass || + !this.cameraManager + ) { return; } @@ -598,8 +574,20 @@ export class FrigateCardLiveCarousel extends LitElement { this.conditionState, ) as LiveConfig; - const [prev, next] = this._getCameraNeighbors(); - const title = getCameraTitle(this.hass, this.cameras.get(this.view.camera)); + const [prevID, nextID] = this._getCameraIDsOfNeighbors(); + + const cameraMetadataPrevious = prevID ? this.cameraManager.getCameraMetadata( + this.hass, + this.cameras.get(prevID), + ) : null; + const cameraMetadataCurrent = this.cameraManager.getCameraMetadata( + this.hass, + this.cameras.get(this.view.camera), + ); + const cameraMetadataNext = nextID ? this.cameraManager.getCameraMetadata( + this.hass, + this.cameras.get(nextID), + ) : null; // Notes on the below: // - guard() is used to avoid reseting the carousel unless the @@ -622,8 +610,9 @@ export class FrigateCardLiveCarousel extends LitElement { [this.cameras, this.liveConfig], this._getPlugins.bind(this), ) as EmblaCarouselPlugins} - .label="${title ? `${localize('common.live')}: ${title}` : ''}" + .label="${cameraMetadataCurrent ? `${localize('common.live')}: ${cameraMetadataCurrent.title}` : ''}" .titlePopupConfig=${config.controls.title} + .selected=${this._getSelectedCameraIndex()} transitionEffect=${this._getTransitionEffect()} @frigate-card:media-carousel:select=${this._setViewHandler.bind(this)} @frigate-card:carousel:settle=${() => { @@ -636,13 +625,11 @@ export class FrigateCardLiveCarousel extends LitElement { .hass=${this.hass} .direction=${'previous'} .controlConfig=${config.controls.next_previous} - .label=${getCameraTitle(this.hass, prev)} - .icon=${getCameraIcon(this.hass, prev)} - ?disabled=${prev == null} + .label=${cameraMetadataPrevious?.title ?? ''} + .icon=${cameraMetadataPrevious?.icon} + ?disabled=${prevID === null} @click=${(ev) => { - this._refMediaCarousel.value - ?.frigateCardCarousel() - ?.carouselScrollPrevious(); + this._setViewCameraID(prevID); stopEventFromActivatingCardWideActions(ev); }} > @@ -653,11 +640,11 @@ export class FrigateCardLiveCarousel extends LitElement { .hass=${this.hass} .direction=${'next'} .controlConfig=${config.controls.next_previous} - .label=${getCameraTitle(this.hass, next)} - .icon=${getCameraIcon(this.hass, next)} - ?disabled=${next == null} + .label=${cameraMetadataNext?.title ?? ''} + .icon=${cameraMetadataNext?.icon} + ?disabled=${nextID === null} @click=${(ev) => { - this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollNext(); + this._setViewCameraID(nextID); stopEventFromActivatingCardWideActions(ev); }} > diff --git a/src/components/media-carousel.ts b/src/components/media-carousel.ts index 5679ab52..253b97f7 100644 --- a/src/components/media-carousel.ts +++ b/src/components/media-carousel.ts @@ -28,12 +28,12 @@ const getEmptyImageSrc = (width: number, height: number) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`; export const IMG_EMPTY = getEmptyImageSrc(16, 9); -export interface CarouselMediaLoadedInfo { +interface CarouselMediaLoadedInfo { slide: number; mediaLoadedInfo: MediaLoadedInfo; } -export interface CarouselMediaUnloadedInfo { +interface CarouselMediaUnloadedInfo { slide: number; } @@ -126,6 +126,9 @@ export class FrigateCardMediaCarousel extends LitElement { @property({ attribute: false }) public carouselPlugins?: EmblaCarouselPlugins; + @property({ attribute: false, type: Number }) + public selected = 0; + @property({ attribute: true }) public transitionEffect?: TransitionEffect; @@ -418,6 +421,7 @@ export class FrigateCardMediaCarousel extends LitElement { return html` ; + + @property({ attribute: false }) + public cameraManager?: CameraManager; + + @property({ attribute: false }) + public view?: View; + + @property({ attribute: false }) + public mediaLimit?: number; + + static elementDefinitions = { + 'frigate-card-select': FrigateCardSelect, + }; + + protected _mediaMetadataController?: MediaMetadataController; + + protected _mediaTypeOptions: SelectOption[]; + protected _cameraOptions?: SelectOption[]; + protected _whenOptions?: SelectOption[]; + protected _favoriteOptions: SelectOption[]; + + protected _defaults: MediaFilterCoreDefaults | null = null; + + protected _refMediaType: Ref = createRef(); + protected _refCamera: Ref = createRef(); + protected _refWhen: Ref = createRef(); + protected _refWhat: Ref = createRef(); + protected _refWhere: Ref = createRef(); + protected _refFavorite: Ref = createRef(); + + constructor() { + super(); + this._favoriteOptions = [ + { + value: MediaFilterCoreFavoriteSelection.Favorite, + label: localize('media_filter.favorite'), + }, + { + value: MediaFilterCoreFavoriteSelection.NotFavorite, + label: localize('media_filter.not_favorite'), + }, + ]; + this._mediaTypeOptions = [ + { + value: MediaFilterMediaType.Clips, + label: localize('media_filter.media_types.clips'), + }, + { + value: MediaFilterMediaType.Snapshots, + label: localize('media_filter.media_types.snapshots'), + }, + { + value: MediaFilterMediaType.Recordings, + label: localize('media_filter.media_types.recordings'), + }, + ]; + } + + protected _stringToDateRange(input: string): DateRange { + const dates = input.split(','); + return { + start: parse(dates[0], 'yyyy-MM-dd', new Date()), + end: parse(dates[1], 'yyyy-MM-dd', new Date()), + }; + } + + protected _dateRangeToString(when: DateRange): string { + return `${formatDate(when.start)},${formatDate(when.end)}`; + } + + protected _getWhen(): DateRange | null { + const value = this._refWhen.value?.value; + if (!value || Array.isArray(value)) { + return null; + } + const now = new Date(); + switch (value) { + case MediaFilterCoreWhen.Today: + return { start: startOfToday(), end: endOfToday() }; + case MediaFilterCoreWhen.Yesterday: + return { start: startOfYesterday(), end: endOfYesterday() }; + case MediaFilterCoreWhen.PastWeek: + return { start: startOfDay(sub(now, { days: 7 })), end: endOfDay(now) }; + case MediaFilterCoreWhen.PastMonth: + return { start: startOfDay(sub(now, { months: 1 })), end: endOfDay(now) }; + default: + return this._stringToDateRange(value); + } + } + + protected async _valueChangedHandler( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _ev: CustomEvent<{ value: unknown }>, + ): Promise { + if (!this.hass || !this.cameras || !this.cameraManager || !this.view) { + return; + } + + const getArrayValueAsSet = (val?: SelectValues): Set | null => { + // The reported value may be '' if the field is clearable (i.e. the user + // can click 'x'). + if (val && Array.isArray(val) && val.length && !val.includes('')) { + return new Set([...val]); + } + return null; + }; + + const cameraIDs = + getArrayValueAsSet(this._refCamera.value?.value) ?? new Set(this.cameras.keys()); + const mediaType = this._refMediaType.value?.value as + | MediaFilterMediaType + | undefined; + const when = this._getWhen(); + const favorite = this._refFavorite.value?.value + ? this._refFavorite.value.value === MediaFilterCoreFavoriteSelection.Favorite + : null; + + // A note on views: + // - In the below, if the user selects a camera to view media for, the main + // view camera is also set to that value (e.g. a user browsing the + // gallery, chooses a different camera in the media filter, then + // subsequently chooses the live button -- they would expect the live view + // for that filtered camera not the prior camera). + // - Similarly, if the user chooses clips or snapshots, set the actual view + // to 'clips' or 'snapshots' in order to ensure the right icon is shown as + // selected in the menu. + if ( + mediaType === MediaFilterMediaType.Clips || + mediaType === MediaFilterMediaType.Snapshots + ) { + const where = getArrayValueAsSet(this._refWhere.value?.value); + const what = getArrayValueAsSet(this._refWhat.value?.value); + + const queries: EventQuery[] = [ + { + type: QueryType.Event, + cameraIDs: cameraIDs, + ...(what && { what: what }), + ...(where && { where: where }), + ...(favorite !== null && { favorite: favorite }), + ...(when && { start: when.start, end: when.end }), + ...(this.mediaLimit && { limit: this.mediaLimit }), + ...(mediaType === MediaFilterMediaType.Clips && { hasClip: true }), + ...(mediaType === MediaFilterMediaType.Snapshots && { + hasSnapshot: true, + }), + }, + ]; + + ( + await createViewForEvents( + this, + this.hass, + this.cameraManager, + this.cameras, + this.view, + { + query: new EventMediaQueries(queries), + + // See 'A note on views' above for these two arguments. + ...(cameraIDs.size === 1 && { targetCameraID: [...cameraIDs][0] }), + targetView: mediaType === MediaFilterMediaType.Clips ? 'clips' : 'snapshots', + }, + ) + )?.dispatchChangeEvent(this); + } else if (mediaType === MediaFilterMediaType.Recordings) { + const query: RecordingQuery = { + type: QueryType.Recording, + cameraIDs: cameraIDs, + ...(when && { start: when.start, end: when.end }), + }; + + ( + await createViewForRecordings( + this, + this.hass, + this.cameraManager, + this.cameras, + this.view, + { + query: new RecordingMediaQueries([query]), + + // See 'A note on views' above for these two arguments. + ...(cameraIDs.size === 1 && { targetCameraID: [...cameraIDs][0] }), + targetView: 'recordings', + }, + ) + )?.dispatchChangeEvent(this); + } + } + + protected willUpdate(changedProps: PropertyValues): void { + if (changedProps.has('cameras') && this.cameras) { + this._cameraOptions = Array.from(this.cameras.entries()).map( + ([cameraID, cameraConfig]) => ({ + value: cameraID, + label: this.hass + ? this.cameraManager?.getCameraMetadata(this.hass, cameraConfig)?.title ?? '' + : '', + }), + ); + } + + if (changedProps.has('cameraManager') && this.hass && this.cameraManager) { + this._mediaMetadataController = new MediaMetadataController( + this, + this.hass, + this.cameraManager, + ); + } + + // Relative time based options are not pre-computed here to ensure relative + // dates (e.g. 'today') are always calculated when activated not when + // rendered. + this._whenOptions = [ + { + value: MediaFilterCoreWhen.Today, + label: localize('media_filter.whens.today'), + }, + { + value: MediaFilterCoreWhen.Yesterday, + label: localize('media_filter.whens.yesterday'), + }, + { + value: MediaFilterCoreWhen.PastWeek, + label: localize('media_filter.whens.past_week'), + }, + { + value: MediaFilterCoreWhen.PastMonth, + label: localize('media_filter.whens.past_month'), + }, + ...(this._mediaMetadataController?.whenOptions ?? []), + ]; + + if (changedProps.has('view')) { + const newDefaults = this._getDefaultsFromView(); + if (!isEqual(newDefaults, this._defaults)) { + this._defaults = newDefaults; + } + } + } + + protected _getDefaultsFromView(): MediaFilterCoreDefaults | null { + const queries = this.view?.query?.getQueries(); + if (!this.view || !queries) { + return null; + } + + let mediaType: MediaFilterMediaType | undefined; + let cameraIDs: string[] | undefined; + let what: string[] | undefined; + let where: string[] | undefined; + let favorite: MediaFilterCoreFavoriteSelection | undefined; + + const cameraIDSets = uniqWith( + queries.map((query) => query.cameraIDs), + isEqual, + ); + // Special note: If all cameras are selected, this is the same as no + // selector at all. + if (cameraIDSets.length === 1 && queries[0].cameraIDs.size !== this.cameras?.size) { + cameraIDs = [...queries[0].cameraIDs]; + } + + const favoriteValues = uniqWith( + queries.map((query) => query.favorite), + isEqual, + ); + if (favoriteValues.length === 1 && queries[0].favorite !== undefined) { + favorite = queries[0].favorite + ? MediaFilterCoreFavoriteSelection.Favorite + : MediaFilterCoreFavoriteSelection.NotFavorite; + } + + if (MediaQueriesClassifier.areEventQueries(this.view.query)) { + const queries = this.view.query.getQueries(); + if (!queries) { + return null; + } + + const hasClips = uniqWith( + queries.map((query) => query.hasClip), + isEqual, + ); + const hasSnapshots = uniqWith( + queries.map((query) => query.hasSnapshot), + isEqual, + ); + if (hasClips.length === 1 && hasSnapshots.length === 1) { + mediaType = !!hasClips[0] + ? MediaFilterMediaType.Clips + : !!hasSnapshots[0] + ? MediaFilterMediaType.Snapshots + : undefined; + } + + const whatSets = uniqWith( + queries.map((query) => query.what), + isEqual, + ); + if (whatSets.length === 1 && queries[0].what?.size) { + what = [...queries[0].what]; + } + const whereSets = uniqWith( + queries.map((query) => query.where), + isEqual, + ); + if (whereSets.length === 1 && queries[0].where?.size) { + where = [...queries[0].where]; + } + } else if (MediaQueriesClassifier.areRecordingQueries(this.view.query)) { + mediaType = MediaFilterMediaType.Recordings; + } + + return { + ...(mediaType && { mediaType: mediaType }), + ...(cameraIDs && { cameraIDs: cameraIDs }), + ...(what && { what: what }), + ...(where && { where: where }), + ...(favorite !== undefined && { favorite: favorite }), + }; + } + + protected render(): TemplateResult | void { + if (!this._mediaMetadataController) { + return; + } + + const areEvents = !!( + this.view?.query && MediaQueriesClassifier.areEventQueries(this.view.query) + ); + const areRecordings = !!( + this.view?.query && MediaQueriesClassifier.areRecordingQueries(this.view.query) + ); + const managerCapabilities = this.cameraManager?.getCapabilities(); + + // Which media controls are shown depends on the view. + const showFavoriteControl = areEvents + ? !!managerCapabilities?.canFavoriteEvents + : areRecordings + ? !!managerCapabilities?.canFavoriteRecordings + : false; + + return html` + + + + + + ${areEvents && this._mediaMetadataController.whatOptions.length + ? html` + ` + : ''} + ${areEvents && this._mediaMetadataController.whereOptions.length + ? html` + ` + : ''} + ${showFavoriteControl + ? html` + + + ` + : ''}`; + } + + static get styles(): CSSResultGroup { + return unsafeCSS(mediaFilterStyle); + } +} + +export class MediaMetadataController implements ReactiveController { + protected _host: ReactiveControllerHost; + protected _hass: HomeAssistant; + protected _cameraManager: CameraManager; + + public whenOptions: SelectOption[] = []; + public whatOptions: SelectOption[] = []; + public whereOptions: SelectOption[] = []; + + constructor( + host: ReactiveControllerHost, + hass: HomeAssistant, + cameraManager: CameraManager, + ) { + this._host = host; + this._hass = hass; + this._cameraManager = cameraManager; + host.addController(this); + } + + protected _dateRangeToString(when: DateRange): string { + return `${formatDate(when.start)},${formatDate(when.end)}`; + } + + async hostConnected() { + let metadata: MediaMetadata | null; + try { + metadata = await this._cameraManager.getMediaMetadata(this._hass); + } catch (e) { + errorToConsole(e as Error); + return; + } + if (!metadata) { + return; + } + + if (metadata.what) { + this.whatOptions = [...metadata.what] + .sort() + .map((what) => ({ value: what, label: prettifyTitle(what) })); + } + if (metadata.where) { + this.whereOptions = [...metadata.where] + .sort() + .map((where) => ({ value: where, label: prettifyTitle(where) })); + } + if (metadata.days) { + const yearMonths: Set = new Set(); + [...metadata.days].forEach((day) => { + // An efficient conversion: "2023-01-26" -> "2023-01" + yearMonths.add(day.substring(0, 7)); + }); + const monthStarts: Date[] = []; + yearMonths.forEach((yearMonth) => { + monthStarts.push(parse(yearMonth, 'yyyy-MM', new Date())); + }); + this.whenOptions = orderBy(monthStarts, (date) => date.getTime(), 'desc').map( + (monthStart) => ({ + label: format(monthStart, 'MMMM yyyy'), + value: this._dateRangeToString({ + start: monthStart, + end: endOfMonth(monthStart), + }), + }), + ); + } + this._host.requestUpdate(); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'frigate-card-media-filter': FrigateCardMediaFilter; + } +} diff --git a/src/components/menu.ts b/src/components/menu.ts index 6201f4a7..4c04b78d 100644 --- a/src/components/menu.ts +++ b/src/components/menu.ts @@ -27,7 +27,7 @@ import { frigateCardHasAction, getActionConfigGivenAction } from '../utils/action.js'; -import { FRIGATE_ICON_SVG_PATH } from '../utils/frigate.js'; +import { FRIGATE_ICON_SVG_PATH } from '../camera-manager/frigate/icon.js'; import { refreshDynamicStateParameters } from '../utils/ha'; import './submenu.js'; diff --git a/src/components/message.ts b/src/components/message.ts index 4d2688b1..66ffd686 100644 --- a/src/components/message.ts +++ b/src/components/message.ts @@ -1,6 +1,7 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; +import { ref, Ref } from 'lit/directives/ref.js'; import { TROUBLESHOOTING_URL } from '../const.js'; import { localize } from '../localize/localize.js'; import messageStyle from '../scss/message.scss'; @@ -118,9 +119,11 @@ export function renderMessage(message: Message): TemplateResult { export function renderProgressIndicator(options?: { message?: string; cardWideConfig?: CardWideConfig; + componentRef?: Ref, }): TemplateResult { return html` = createRef(); + + static elementDefinitions = { + ...grSelectElements, + }; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected _valueChangedHandler(_ev: CustomEvent<{ value: unknown }>): void { + const value: SelectValues | undefined = this._refSelect.value?.value; + // The underlying gr-select element is very sensitive and occasionally fires + // the change event even if the value has not actually changed. Prevent that + // from propagating upwards. + if (value !== undefined && !isEqual(this.value, value)) { + this.value = value; + dispatchFrigateCardEvent(this, 'select:change', value); + } + } + + protected render(): TemplateResult | void { + return html` + ${this.options?.map( + (option) => + html`${option.label}`, + )} + `; + } + + static get styles(): CSSResultGroup { + return unsafeCSS(selectStyle); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'frigate-card-select': FrigateCardSelect; + } +} diff --git a/src/components/surround-basic.ts b/src/components/surround-basic.ts index e8f760a0..a49c5f1d 100644 --- a/src/components/surround-basic.ts +++ b/src/components/surround-basic.ts @@ -1,8 +1,7 @@ import { CSSResultGroup, LitElement, TemplateResult, html, unsafeCSS } from 'lit'; import { createRef, ref, Ref } from 'lit/directives/ref.js'; -import { customElement } from 'lit/decorators.js'; - -import { FrigateCardDrawer } from './drawer.js'; +import { customElement, property } from 'lit/decorators.js'; +import { DrawerIcons, FrigateCardDrawer } from './drawer.js'; import './drawer.js'; @@ -14,6 +13,12 @@ interface FrigateCardDrawerOpen { @customElement('frigate-card-surround-basic') export class FrigateCardSurroundBasic extends LitElement { + @property({ attribute: false }) + public drawerIcons?: { + left?: DrawerIcons; + right?: DrawerIcons; + }; + protected _refDrawerLeft: Ref = createRef(); protected _refDrawerRight: Ref = createRef(); protected _boundDrawerHandler = this._drawerHandler.bind(this); @@ -53,10 +58,18 @@ export class FrigateCardSurroundBasic extends LitElement { protected render(): TemplateResult | void { return html` - + - + `; diff --git a/src/components/surround.ts b/src/components/surround.ts index 9b41f2cb..6affa9b4 100644 --- a/src/components/surround.ts +++ b/src/components/surround.ts @@ -7,29 +7,21 @@ import { unsafeCSS, } from 'lit'; import { customElement, property } from 'lit/decorators.js'; - import surroundStyle from '../scss/surround.scss'; import { - BrowseMediaQueryParameters, CameraConfig, + ClipsOrSnapshotsOrAll, ExtendedHomeAssistant, - FrigateBrowseMediaSource, - FrigateCardError, MiniTimelineControlConfig, ThumbnailsControlConfig, } from '../types.js'; import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js'; -import { - getFirstTrueMediaChildIndex, - multipleBrowseMediaQueryMerged, -} from '../utils/ha/browse-media'; -import { DataManager } from '../utils/data-manager'; -import { View } from '../view.js'; -import { dispatchFrigateCardErrorEvent } from './message.js'; +import { CameraManager } from '../camera-manager/manager.js'; +import { View } from '../view/view.js'; import { ThumbnailCarouselTap } from './thumbnail-carousel.js'; - import './surround-basic.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; +import { changeViewToRecentEventsForCameraAndDependents } from '../utils/media-to-view'; +import { getAllDependentCameras } from '../utils/camera.js'; interface ThumbnailViewContext { // Whether or not to fetch thumbnails. @@ -59,17 +51,17 @@ export class FrigateCardSurround extends LitElement { @property({ attribute: false }) public inBackground?: boolean; - @property({ attribute: false }) - public fetch = false; - + // If fetchMedia is not specified, no fetching is done. @property({ attribute: false, hasChanged: contentsChanged }) - public browseMediaParams?: BrowseMediaQueryParameters | BrowseMediaQueryParameters[]; + public fetchMedia?: ClipsOrSnapshotsOrAll; @property({ attribute: false }) public cameras?: Map; @property({ attribute: false }) - public dataManager?: DataManager; + public cameraManager?: CameraManager; + + protected _cameraIDsForTimeline?: Set; /** * Fetch thumbnail media when a target is not specified in the view (e.g. for @@ -79,32 +71,30 @@ export class FrigateCardSurround extends LitElement { */ protected async _fetchMedia(): Promise { if ( - !this.fetch || + !this.cameras || + !this.cameraManager || + !this.fetchMedia || this.inBackground || !this.hass || !this.view || - this.view.target || + this.view.query || !this.thumbnailConfig || this.thumbnailConfig.mode === 'none' || - !this.browseMediaParams || !(this.view.context?.thumbnails?.fetch ?? true) ) { return; } - let parent: FrigateBrowseMediaSource | null; - try { - parent = await multipleBrowseMediaQueryMerged(this.hass, this.browseMediaParams); - } catch (e) { - return dispatchFrigateCardErrorEvent(this, e as FrigateCardError); - } - if (getFirstTrueMediaChildIndex(parent) !== null) { - this.view - ?.evolve({ - target: parent, - childIndex: null, - }) - .dispatchChangeEvent(this); - } + await changeViewToRecentEventsForCameraAndDependents( + this, + this.hass, + this.cameraManager, + this.cameras, + this.view, + { + targetView: this.view.view, + mediaType: this.fetchMedia, + }, + ); } /** @@ -125,6 +115,16 @@ export class FrigateCardSurround extends LitElement { import('./timeline.js'); } + // Only reset the timeline cameraIDs when the media materially changes (and + // not on every view change, since the view will change frequently when the + // user is scrubbing video). + if ( + changedProperties.has('view') && + View.isMediaChange(changedProperties.get('view'), this.view) + ) { + this._cameraIDsForTimeline = this._getCameraIDsForTimeline() ?? undefined; + } + // Once the component will certainly update, dispatch a media request. Only // do so if properties relevant to the request have changed (as per their // hasChanged). @@ -137,12 +137,30 @@ export class FrigateCardSurround extends LitElement { } } + protected _getCameraIDsForTimeline(): Set | null { + if (!this.view || !this.cameras) { + return null; + } + if (this.view?.is('live')) { + return getAllDependentCameras(this.cameras, this.view.camera); + } + if (this.view.isViewerView()) { + return new Set( + this.view.query + ?.getQueries() + ?.map((query) => [...query.cameraIDs]) + .flat(), + ); + } + return null; + } + /** * Master render method. * @returns A rendered template. */ protected render(): TemplateResult | void { - if (!this.hass || !this.view || !this.thumbnailConfig) { + if (!this.hass || !this.view || !this.thumbnailConfig || !this.cameras) { return; } @@ -170,25 +188,21 @@ export class FrigateCardSurround extends LitElement { slot=${this.thumbnailConfig.mode} .hass=${this.hass} .config=${this.thumbnailConfig} + .cameraManager=${this.cameraManager} .view=${this.view} - .target=${this.view.target} .cameras=${this.cameras} - selected=${ifDefined(this.view.childIndex ?? undefined)} + .selected=${this.view.queryResults?.getSelectedIndex() ?? undefined} @frigate-card:view:change=${(ev: CustomEvent) => changeDrawer(ev, 'close')} @frigate-card:thumbnail-carousel:tap=${( ev: CustomEvent, ) => { - const child: FrigateBrowseMediaSource | null = - ev.detail.target?.children?.[ev.detail.childIndex] ?? null; - if (child) { + const media = ev.detail.queryResults.getSelectedResult(); + if (media) { this.view ?.evolve({ - view: this.view.is('recording') ? 'recording' : 'media', - target: ev.detail.target, - childIndex: ev.detail.childIndex, - ...(child.frigate?.cameraID && { - camera: child.frigate?.cameraID, - }), + view: 'media', + queryResults: ev.detail.queryResults, + ...(media.getCameraID() && { camera: media.getCameraID() }), }) .removeContext('timeline') // Send the view change from the source of the tap event, so @@ -200,17 +214,20 @@ export class FrigateCardSurround extends LitElement { > ` : ''} - ${this.timelineConfig?.mode && this.timelineConfig.mode !== 'none' && !this.inBackground + ${this.timelineConfig?.mode && + this.timelineConfig.mode !== 'none' && + !this.inBackground ? html` ` : ''} diff --git a/src/components/thumbnail-carousel.ts b/src/components/thumbnail-carousel.ts index 1dfc3f39..4e9625b7 100644 --- a/src/components/thumbnail-carousel.ts +++ b/src/components/thumbnail-carousel.ts @@ -15,22 +15,20 @@ import thumbnailCarouselStyle from '../scss/thumbnail-carousel.scss'; import { CameraConfig, ExtendedHomeAssistant, - FrigateBrowseMediaSource, ThumbnailsControlConfig, } from '../types.js'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; -import { contentsChanged, dispatchFrigateCardEvent } from '../utils/basic.js'; -import { isTrueMedia } from '../utils/ha/browse-media'; -import { View } from '../view.js'; +import { dispatchFrigateCardEvent } from '../utils/basic.js'; +import { View } from '../view/view.js'; +import { MediaQueriesResults } from "../view/media-queries-results"; import { FrigateCardCarousel } from './carousel.js'; import './thumbnail.js'; import './carousel.js'; import { ifDefined } from 'lit/directives/if-defined.js'; +import { CameraManager } from '../camera-manager/manager.js'; export interface ThumbnailCarouselTap { - slideIndex: number; - target: FrigateBrowseMediaSource; - childIndex: number; + queryResults: MediaQueriesResults; } @customElement('frigate-card-thumbnail-carousel') @@ -41,14 +39,12 @@ export class FrigateCardThumbnailCarousel extends LitElement { @property({ attribute: false }) public view?: Readonly; - // Use contentsChanged here to avoid the carousel rebuilding and resetting in - // front of the user, unless the contents have actually changed. - @property({ attribute: false, hasChanged: contentsChanged }) - public target?: FrigateBrowseMediaSource | null; - @property({ attribute: false }) public cameras?: Map; + @property({ attribute: false }) + public cameraManager?: CameraManager; + protected _refCarousel: Ref = createRef(); // Thumbnail carousels can expand (e.g. drawer-based carousels after the main @@ -59,10 +55,14 @@ export class FrigateCardThumbnailCarousel extends LitElement { @property({ attribute: false }) public config?: ThumbnailsControlConfig; - @property({ attribute: true, type: Number, reflect: true }) - public selected?: number; + @property({ attribute: false }) + public selected? = 0; + + protected _carouselOptions?: EmblaOptionsType = { + containScroll: 'keepSnaps', + dragFree: true, + }; - protected _carouselOptions?: EmblaOptionsType; protected _carouselPlugins: EmblaPluginType[] = [ WheelGesturesPlugin({ // Whether the carousel is vertical or horizontal, interpret y-axis wheel @@ -99,31 +99,20 @@ export class FrigateCardThumbnailCarousel extends LitElement { super.disconnectedCallback(); } - /** - * Get the Embla options to use. - * @returns An EmblaOptionsType object or undefined for no options. - */ - protected _getOptions(): EmblaOptionsType { - return { - containScroll: 'keepSnaps', - dragFree: true, - startIndex: this.selected ?? 0, - }; - } /** * Get slides to include in the render. * @returns The slides to include in the render. */ protected _getSlides(): TemplateResult[] { - if (!this.target || !this.target.children || !this.target.children.length) { + if (!this.view?.query || !this.view.queryResults?.hasResults()) { return []; } const slides: TemplateResult[] = []; - for (let i = 0; i < this.target.children.length; ++i) { - const thumbnail = this._renderThumbnail(this.target, i, slides.length); + for (let i = 0; i < this.view.queryResults.getResultsCount(); ++i) { + const thumbnail = this._renderThumbnail(i); if (thumbnail) { - slides.push(thumbnail); + slides[i] = thumbnail; } } return slides; @@ -152,30 +141,6 @@ export class FrigateCardThumbnailCarousel extends LitElement { this.selected === undefined ? '1.0' : '0.4', ); } - - if (!this._carouselOptions) { - // Want to set the initial carousel options just before the first render - // in order to get the startIndex correct in the options. It is not safe - // to rely on carouselScrollTo() post update, since the nested carousel - // may not yet be actual rendered/created. - this._carouselOptions = this._getOptions(); - } - } - - /** - * The updated lifecycle callback for this element. - * @param changedProperties The properties that were changed in this render. - */ - updated(changedProperties: PropertyValues): void { - super.updated(changedProperties); - - if (changedProperties.has('selected')) { - this.updateComplete.then(() => { - if (this.selected !== undefined) { - this._refCarousel.value?.carouselScrollTo(this.selected); - } - }); - } } /** @@ -183,45 +148,41 @@ export class FrigateCardThumbnailCarousel extends LitElement { * @param mediaToRender The media item to render. * @returns A template or void if the item could not be rendered. */ - protected _renderThumbnail( - parent: FrigateBrowseMediaSource, - childIndex: number, - slideIndex: number, - ): TemplateResult | void { - if ( - !parent.children || - !parent.children.length || - !isTrueMedia(parent.children[childIndex]) - ) { + protected _renderThumbnail(index: number): TemplateResult | void { + const media = this.view?.queryResults?.getResult(index) ?? null; + const cameraConfig = media ? this.cameras?.get(media.getCameraID()) : null; + if (!media || !cameraConfig || !this.view) { return; } const classes = { embla__slide: true, - 'slide-selected': this.selected === childIndex, + 'slide-selected': this.selected === index, }; - const cameraConfig = this.view?.camera ? this.cameras?.get(this.view.camera) : null; + const seekTarget = this.view?.context?.mediaViewer?.seek; return html` { - if (this._refCarousel.value?.carouselClickAllowed()) { + @click=${(ev: Event) => { + if ( + this.view && + this.view.queryResults && + this._refCarousel.value?.carouselClickAllowed() + ) { dispatchFrigateCardEvent( this, 'thumbnail-carousel:tap', { - slideIndex: slideIndex, - target: parent, - childIndex: childIndex, + queryResults: this.view.queryResults.clone().selectResult(index), }, ); } @@ -257,6 +218,7 @@ export class FrigateCardThumbnailCarousel extends LitElement { return html` diff --git a/src/components/thumbnail.ts b/src/components/thumbnail.ts index a65380de..84e95fc2 100644 --- a/src/components/thumbnail.ts +++ b/src/components/thumbnail.ts @@ -1,32 +1,31 @@ import format from 'date-fns/format'; -import fromUnixTime from 'date-fns/fromUnixTime'; -import { CSSResult, html, LitElement, TemplateResult, unsafeCSS } from 'lit'; +import { + CSSResult, + html, + LitElement, + PropertyValues, + TemplateResult, + unsafeCSS, +} from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; - import { localize } from '../localize/localize.js'; import thumbnailDetailsStyle from '../scss/thumbnail-details.scss'; import thumbnailFeatureEventStyle from '../scss/thumbnail-feature-event.scss'; import thumbnailFeatureRecordingStyle from '../scss/thumbnail-feature-recording.scss'; import thumbnailStyle from '../scss/thumbnail.scss'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; -import { errorToConsole, prettifyTitle } from '../utils/basic.js'; -import { getCameraTitle } from '../utils/camera.js'; -import { retainEvent } from '../utils/frigate.js'; -import { getEventDurationString } from '../utils/frigate.js'; +import { errorToConsole, getDurationString, prettifyTitle } from '../utils/basic.js'; import { renderTask } from '../utils/task.js'; -import { createFetchThumbnailTask } from '../utils/thumbnail.js'; -import { View } from '../view.js'; -import type { MediaSeek } from './viewer.js'; -import { TaskStatus } from '@lit-labs/task'; +import { createFetchThumbnailTask, FetchThumbnailTaskArgs } from '../utils/thumbnail.js'; +import { View } from '../view/view.js'; +import { Task, TaskStatus } from '@lit-labs/task'; + +import type { CameraConfig, ExtendedHomeAssistant } from '../types.js'; +import { EventViewMedia, RecordingViewMedia, ViewMedia } from '../view/media.js'; +import { CameraManager } from '../camera-manager/manager.js'; +import { ViewMediaClassifier } from '../view/media-classifier.js'; -import type { - CameraConfig, - ExtendedHomeAssistant, - FrigateBrowseMediaSource, - FrigateEvent, - FrigateRecording, -} from '../types.js'; // The minimum width of a thumbnail with details enabled. export const THUMBNAIL_DETAILS_WIDTH_MIN = 300; @@ -38,12 +37,7 @@ export class FrigateCardThumbnailFeatureEvent extends LitElement { @property({ attribute: false }) public hass?: ExtendedHomeAssistant; - protected _embedThumbnailTask = createFetchThumbnailTask( - this, - () => this.hass, - () => this.thumbnail, - false, - ); + protected _embedThumbnailTask?: Task; // Only load thumbnails on view in case there is a very large number of them. protected _intersectionObserver: IntersectionObserver; @@ -71,20 +65,38 @@ export class FrigateCardThumbnailFeatureEvent extends LitElement { this._intersectionObserver.disconnect(); } + protected willUpdate(changedProps: PropertyValues): void { + if (changedProps.has('thumbnail')) { + this._embedThumbnailTask = createFetchThumbnailTask( + this, + () => this.hass, + () => this.thumbnail, + false, + ); + // Reset the observer so the initial intersection handler call will set + // the visibility correctly. + this._intersectionObserver.unobserve(this); + this._intersectionObserver.observe(this); + } + } + /** * Called when the live view intersects with the viewport. * @param entries The IntersectionObserverEntry entries (should be only 1). */ protected _intersectionHandler(entries: IntersectionObserverEntry[]): void { if ( - this._embedThumbnailTask.status === TaskStatus.INITIAL && + this._embedThumbnailTask?.status === TaskStatus.INITIAL && entries.some((entry) => entry.isIntersecting) ) { - this._embedThumbnailTask.run(); + this._embedThumbnailTask?.run(); } } protected render(): TemplateResult | void { + if (!this._embedThumbnailTask) { + return; + } const imageOff = html` -
${prettifyTitle(this.event.label)}
-
- ${localize('event.start')}: - ${format(fromUnixTime(this.event.start_time), 'HH:mm:ss')} -
-
- ${localize('event.duration')}: - ${getEventDurationString(this.event)} -
- ${this.mediaSeek + const score = this.media.getScore(); + const startTime = this.media.getStartTime(); + const endTime = this.media.getEndTime(); + const what = this.media.getWhat(); + + return html`
+ ${what ? html`
${prettifyTitle(what.join(', '))}
` : ``} + ${startTime + ? html`
+ ${localize('event.start')}: + ${format(startTime, 'HH:mm:ss')} +
+
+ ${localize('event.duration')}: + ${endTime + ? getDurationString(startTime, endTime) + : localize('event.in_progress')} +
` + : ``} + ${this.seek ? html`
${localize('event.seek')} - ${format(fromUnixTime(this.mediaSeek.seekTime), 'HH:mm:ss')} + ${format(this.seek, 'HH:mm:ss')}
` : html``}
-
- ${score} -
`; + ${score + ? html`
+ ${(score * 100).toFixed(2) + '%'} +
` + : ``}`; } static get styles(): CSSResult { @@ -173,28 +197,34 @@ export class FrigateCardThumbnailDetailsEvent extends LitElement { @customElement('frigate-card-thumbnail-details-recording') export class FrigateCardThumbnailDetailsRecording extends LitElement { @property({ attribute: false }) - public recording?: FrigateRecording; + public media?: RecordingViewMedia; @property({ attribute: false }) - public mediaSeek?: MediaSeek; + public seek?: Date; + + @property({ attribute: false }) + public cameraTitle?: string; protected render(): TemplateResult | void { - if (!this.recording) { + if (!this.media) { return; } + const eventCount = this.media.getEventCount(); return html`
-
${prettifyTitle(this.recording.camera) || ''}
- ${this.mediaSeek +
${this.cameraTitle ?? ''}
+ ${this.seek ? html`
${localize('recording.seek')} - ${format(fromUnixTime(this.mediaSeek.seekTime), 'HH:mm:ss')} + ${format(this.seek, 'HH:mm:ss')}
` : html``}
-
- ${this.recording.events} - ${localize('recording.events')} -
`; + ${eventCount !== null + ? html`
+ ${eventCount} + ${localize('recording.events')} +
` + : ``}`; } static get styles(): CSSResult { @@ -204,6 +234,20 @@ export class FrigateCardThumbnailDetailsRecording extends LitElement { @customElement('frigate-card-thumbnail') export class FrigateCardThumbnail extends LitElement { + // HomeAssistant object may be required for thumbnail signing (for Frigate + // events). + @property({ attribute: false }) + public hass?: ExtendedHomeAssistant; + + @property({ attribute: false }) + public cameraManager?: CameraManager; + + @property({ attribute: true }) + public media?: ViewMedia; + + @property({ attribute: false }) + public cameraConfig?: CameraConfig; + @property({ attribute: true, type: Boolean }) public details = false; @@ -213,160 +257,133 @@ export class FrigateCardThumbnail extends LitElement { @property({ attribute: true, type: Boolean }) public show_timeline_control = false; - // ====================== - // Target-based interface - // ====================== @property({ attribute: false }) - public target?: FrigateBrowseMediaSource | null; + public seek?: Date; - @property({ attribute: false }) - public childIndex?: number; - - @property({ attribute: false }) - public mediaSeek?: MediaSeek; - - // =================================================== - // Raw interface (can override target-based interface) - // =================================================== - @property({ attribute: true }) - public thumbnail?: string; - - @property({ attribute: true }) - public label?: string; - - @property({ attribute: false }) - public event?: FrigateEvent; - - // ================================ - // Optional parameters for controls - // ================================ @property({ attribute: false }) public view?: Readonly; - @property({ attribute: false }) - public hass?: ExtendedHomeAssistant; - - @property({ attribute: false }) - public cameraConfig?: CameraConfig; - /** * Render the element. * @returns A template to display to the user. */ protected render(): TemplateResult | void { - let event: FrigateEvent | null = null; - let recording: FrigateRecording | null = null; - let thumbnail: string | null = null; - let label: string | null = null; - - // Take the event / thumbnail / label from the data-bound media (if specified). - if (this.target && this.target.children && this.childIndex !== undefined) { - const media = this.target.children[this.childIndex]; - event = media.frigate?.event ?? null; - recording = media.frigate?.recording ?? null; - thumbnail = media.thumbnail; - label = media.title; - } - - // Always give the overrides preference (if specified). - if (this.event) { - event = this.event; - } - thumbnail = this.thumbnail ? this.thumbnail : thumbnail; - label = this.label ? this.label : label; - - if (!event && !recording) { + if (!this.media || !this.cameraConfig || !this.cameraManager || !this.hass) { return; } + const thumbnail = this.media.getThumbnail(); + const title = this.media.getTitle() ?? ''; + const starClasses = { star: true, - starred: !!event?.retain_indefinitely, + starred: !!this.media?.isFavorite(), }; - const clientID = this.cameraConfig?.frigate.client_id; - return html` ${event + const shouldShowTimelineControl = + this.show_timeline_control && + this.view && + (!ViewMediaClassifier.isRecording(this.media) || + // Only show timeline control if the recording has a start & end time. + (this.media.getStartTime() && this.media.getEndTime())); + + const shouldShowFavoriteControl = + this.show_favorite_control && + this.media && + this.hass && + this.cameraManager?.getMediaCapabilities(this.media)?.canFavorite; + + const cameraTitle = this.cameraManager.getCameraMetadata(this.hass, this.cameraConfig)?.title; + + return html` ${ViewMediaClassifier.isEvent(this.media) ? html`` - : recording + : ViewMediaClassifier.isRecording(this.media) ? html`` : html``} - ${this.show_favorite_control && event && this.hass && clientID + ${shouldShowFavoriteControl ? html` { + @click=${async (ev: Event) => { stopEventFromActivatingCardWideActions(ev); - if (event && this.hass && clientID) { - retainEvent(this.hass, clientID, event.id, !event.retain_indefinitely) - .then(() => { - if (event) { - event.retain_indefinitely = !event.retain_indefinitely; - this.requestUpdate(); - } - }) - .catch((e) => { - errorToConsole(e); - }); + if (this.hass && this.media) { + try { + await this.cameraManager?.favoriteMedia( + this.hass, + this.media, + !this.media?.isFavorite(), + ); + } catch (e) { + errorToConsole(e as Error); + return; + } + this.requestUpdate(); } }} />` : ``} - ${this.details && event + ${this.details && ViewMediaClassifier.isEvent(this.media) ? html`` - : this.details && recording + : this.details && ViewMediaClassifier.isRecording(this.media) ? html`` : html``} - ${this.show_timeline_control + ${shouldShowTimelineControl ? html` { stopEventFromActivatingCardWideActions(ev); - if (event) { + if (!this.view || !this.media) { + return; + } + if (ViewMediaClassifier.isEvent(this.media)) { this.view - ?.evolve({ + .evolve({ view: 'timeline', - target: this.target, - childIndex: this.childIndex ?? null, + queryResults: this.view.queryResults + ?.clone() + .selectResultIfFound((media) => media === this.media), }) .removeContext('timeline') .dispatchChangeEvent(this); - } else if (recording) { + } else if (ViewMediaClassifier.isRecording(this.media)) { + const startTime = this.media.getStartTime(); + const endTime = this.media.getStartTime(); + if (!startTime || !endTime) { + return; + } // Specifically reset the media target/childIndex, as we cannot // 'select' an hour in the timeline rather we set the window to // matching values. this.view ?.evolve({ view: 'timeline', - target: null, - childIndex: null, + query: null, }) .mergeInContext({ timeline: { window: { - start: fromUnixTime(recording.start_time), - end: fromUnixTime(recording.end_time), + start: startTime, + end: endTime, }, }, }) diff --git a/src/components/timeline-core.ts b/src/components/timeline-core.ts index 0982637c..ed584496 100644 --- a/src/components/timeline-core.ts +++ b/src/components/timeline-core.ts @@ -1,6 +1,5 @@ import add from 'date-fns/add'; import endOfHour from 'date-fns/endOfHour'; -import fromUnixTime from 'date-fns/fromUnixTime'; import differenceInSeconds from 'date-fns/differenceInSeconds'; import startOfHour from 'date-fns/startOfHour'; import sub from 'date-fns/sub'; @@ -17,7 +16,7 @@ import { createRef, ref, Ref } from 'lit/directives/ref.js'; import isEqual from 'lodash-es/isEqual'; import throttle from 'lodash-es/throttle'; import { ViewContext } from 'view'; -import { DataView, DataSet } from 'vis-data/esnext'; +import { DataSet } from 'vis-data/esnext'; import type { DataGroupCollectionType, IdType } from 'vis-timeline/esnext'; import { Timeline, @@ -33,10 +32,8 @@ import timelineCoreStyle from '../scss/timeline-core.scss'; import { CameraConfig, ExtendedHomeAssistant, - FrigateBrowseMediaSource, frigateCardConfigDefaults, - FrigateEvent, - FrigateRecording, + FrigateCardView, TimelineCoreConfig, } from '../types'; import { stopEventFromActivatingCardWideActions } from '../utils/action'; @@ -45,27 +42,23 @@ import { dispatchFrigateCardEvent, isHoverableDevice, } from '../utils/basic'; -import { getAllDependentCameras, getCameraTitle } from '../utils/camera.js'; -import { - getEventMediaContentID, - getEventThumbnailURL, - getEventTitle, -} from '../utils/frigate'; -import { createEventParentForChildren, createChild } from '../utils/ha/browse-media'; import { - changeViewToRecording, - findChildIndex, - generateMediaViewerContextForChildren, + createViewForEvents, + createViewForRecordings, + findClosestMediaIndex, } from '../utils/media-to-view'; -import { - FrigateCardTimelineItem, - sortYoungestToOldest, - DataManager, -} from '../utils/data-manager'; -import { View } from '../view'; +import { CameraManager } from '../camera-manager/manager'; +import { EventMediaQueries, MediaQueries } from '../view/media-queries'; +import { MediaQueriesClassifier } from '../view/media-queries-classifier'; import { dispatchMessageEvent } from './message.js'; import './thumbnail.js'; +import { FrigateCardTimelineItem, TimelineDataSource } from '../utils/timeline-source'; +import { ViewMedia } from '../view/media'; +import { ViewMediaClassifier } from '../view/media-classifier'; +import { rangesOverlap } from '../camera-manager/range'; +import { View } from '../view/view'; +import { MediaQuery } from '../camera-manager/types'; interface FrigateCardGroupData { id: string; @@ -78,15 +71,7 @@ interface TimelineRangeChange extends TimelineWindow { } interface TimelineViewContext { - // Force a particular timeline window rather than taking the time from an - // event / recording. window?: TimelineWindow; - - // Whether or not to set the timeline window. - noSetWindow?: boolean; - - // Whether or not thumbnails were generated. - generatedThumbnails?: boolean; } declare module 'view' { @@ -95,11 +80,19 @@ declare module 'view' { } } -// An event used to fetch the HASS object. See "Special note" below. -class HASSRequestEvent extends Event { - public hass?: ExtendedHomeAssistant; +// An event used to fetch data required for thumbnail rendering. See special +// note below on why this is necessary. +interface ThumbnailDataRequest { + item: IdType; + hass?: ExtendedHomeAssistant; + cameraManager?: CameraManager; + cameraConfig?: CameraConfig; + media?: ViewMedia; + view?: View; } +class ThumbnailDataRequestEvent extends CustomEvent {} + const TIMELINE_TARGET_BAR_ID = 'target_bar'; /** @@ -109,49 +102,56 @@ const TIMELINE_TARGET_BAR_ID = 'target_bar'; @customElement('frigate-card-timeline-thumbnail') export class FrigateCardTimelineThumbnail extends LitElement { @property({ attribute: true }) - public thumbnail?: string; + public item?: IdType; @property({ attribute: true, type: Boolean }) public details = false; - @property({ attribute: true }) - public event?: string; - - @property({ attribute: true }) - public label?: string; - /** * Master render method. * @returns A rendered template. */ protected render(): TemplateResult | void { - // Don't display tooltips on touch devices, they just get in the way of - // the drawer. - if (!this.thumbnail || !this.event) { + if (!this.item) { return html``; } /* Special note on what's going on here: * - * This component does not have access to HASS, as there's no way to pass it - * in via the string-based tooltip that timeline supports. Instead dispatch - * an event to request HASS which the timeline adds to the event object - * before execution continues. + * This component does not have access to a variety of properties required + * to render a thumbnail component, as there's no way to pass them in via the + * string-based tooltip that timeline supports. Instead dispatch an event to + * request HASS which the timeline adds to the event object before execution + * continues. */ - const hassRequest = new HASSRequestEvent(`frigate-card:timeline:hass-request`, { - composed: true, - bubbles: true, - }); - this.dispatchEvent(hassRequest); - if (!hassRequest.hass) { + + const dataRequest: ThumbnailDataRequest = { + item: this.item, + }; + this.dispatchEvent( + new ThumbnailDataRequestEvent(`frigate-card:timeline:thumbnail-data-request`, { + composed: true, + bubbles: true, + detail: dataRequest, + }), + ); + + if ( + !dataRequest.hass || + !dataRequest.cameraManager || + !dataRequest.cameraConfig || + !dataRequest.media || + !dataRequest.view + ) { return html``; } return html` `; @@ -178,13 +178,18 @@ export class FrigateCardTimelineCore extends LitElement { @property({ attribute: false }) public thumbnailSize?: number; - // Whether or not this is a mini-timeline for a different view (e.g. media - // viewer). + // Whether or not this is a mini-timeline (in mini-mode the component takes a + // supportive role for other views). @property({ attribute: true, type: Boolean, reflect: true }) public mini = false; + // Which cameraIDs to include in the timeline. If not specified, all cameraIDs + // are shown. + @property({ attribute: false, hasChanged: contentsChanged }) + public cameraIDs?: Set; + @property({ attribute: false }) - public dataManager?: DataManager; + public cameraManager?: CameraManager; @state() protected _locked = false; @@ -192,7 +197,8 @@ export class FrigateCardTimelineCore extends LitElement { protected _targetBarVisible = false; protected _refTimeline: Ref = createRef(); protected _timeline?: Timeline; - protected _dataview?: DataView; + + protected _timelineSource: TimelineDataSource | null = null; // Need a way to separate when a user clicks (to pan the timeline) vs when a // user clicks (to choose a recording (non-event) to play). @@ -211,36 +217,40 @@ export class FrigateCardTimelineCore extends LitElement { /** * Get a tooltip for a given timeline event. - * @param source The FrigateBrowseMediaSource in question. + * @param item The TimelineItem in question. * @returns The tooltip as a string to render. */ protected _getTooltip(item: TimelineItem): string { - const event = (item).event; - const clientId = item.group - ? this.cameras?.get(String(item.group))?.frigate.client_id - : null; - if (!this._isHoverableDevice || !event || !clientId) { + if (!this._isHoverableDevice) { // Don't display tooltips on touch devices, they just get in the way of // the drawer. return ''; } - const eventAttr = `event='${JSON.stringify(event)}'`; - const detailsAttr = this.thumbnailDetails ? 'details' : ''; - // Cannot use Lit data-bindings as visjs requires a string for tooltips. // Note that changes to attributes here must be mirrored in the xss // whitelist in `_getOptions()` . return ` `; } + protected _handleThumbnailDataRequest(request: ThumbnailDataRequestEvent): void { + const item = request.detail.item; + const media = this._timelineSource?.dataset.get(item)?.media; + + request.detail.hass = this.hass; + request.detail.cameraConfig = media + ? this.cameras?.get(media.getCameraID()) + : undefined; + request.detail.cameraManager = this.cameraManager; + request.detail.media = media; + request.detail.view = this.view; + } + /** * Master render method. * @returns A rendered template. @@ -250,9 +260,9 @@ export class FrigateCardTimelineCore extends LitElement { return; } return html`
{ - request.hass = this.hass; - }} + @frigate-card:timeline:thumbnail-data-request=${this._handleThumbnailDataRequest.bind( + this, + )} class="timeline" ${ref(this._refTimeline)} > @@ -276,10 +286,7 @@ export class FrigateCardTimelineCore extends LitElement { * @returns A set of camera ids (may be empty). */ protected _getTimelineCameraIDs(): Set { - if (!this.mini || !this.cameras) { - return this._getAllCameraIDs(); - } - return getAllDependentCameras(this.cameras, this.view?.camera); + return this.cameraIDs ?? this._getAllCameraIDs(); } /** @@ -335,9 +342,9 @@ export class FrigateCardTimelineCore extends LitElement { const targetBarOn = !this._locked || - (!this.view?.is('timeline') && + (this.mini && this._timeline.getSelection().some((id) => { - const item = this._dataview?.get(id); + const item = this._timelineSource?.dataset?.get(id); return ( item && item.start && @@ -375,47 +382,56 @@ export class FrigateCardTimelineCore extends LitElement { * @param properties The range change properties. * @returns */ - protected _setViewDuringRangeChange( + protected async _setViewDuringRangeChange( targetTime: Date, properties: TimelineRangeChange, - ): void { + ): Promise { + const results = this.view?.queryResults; + const media = results?.getResults(); if ( + !media || + !results || !this._timeline || !this.view || - !this.view.target?.children?.length || - !this.dataManager + !this.hass || + !this.cameraManager || + !this.cameraManager || + // Skip range changes that do not have hammerjs pan directions associated + // with them, as these outliers cause media matching issues below. + !properties.event.additionalEvent ) { return; } const canSeek = !!this.view?.isViewerView(); - const context = canSeek - ? generateMediaViewerContextForChildren( - this.dataManager, - this.view.target.children, - targetTime, - ) - : null; - - const childIndex = this._locked + const newResults = this._locked ? null - : findChildIndex( - this.view.target.children, - targetTime, - this._getTimelineCameraIDs(), - properties.event.additionalEvent === 'panright' ? 'end' : 'start', - ); + : results + .clone() + .resetSelectedResult() + .selectBestResult((media) => + findClosestMediaIndex( + media, + targetTime, + this._getTimelineCameraIDs(), + properties.event.additionalEvent === 'panright' ? 'end' : 'start', + ), + ); - if (canSeek || (childIndex !== null && childIndex !== this.view.childIndex)) { + if ( + canSeek || + (newResults && + newResults.hasSelectedResult() && + newResults.getResult() !== results.getResult()) + ) { this.view .evolve({ - ...(childIndex !== null && { - childIndex: childIndex, - }), + ...(newResults && + newResults.hasSelectedResult() && { queryResults: newResults }), }) // Whether or not to set the timeline window. .mergeInContext({ - ...this._generateTimelineContext({ noSetWindow: true }), - ...context, + ...(canSeek && { mediaViewer: { seek: targetTime } }), + ...this._setWindowInContext(properties), }) .dispatchChangeEvent(this); } @@ -425,119 +441,113 @@ export class FrigateCardTimelineCore extends LitElement { * Called whenever the timeline is clicked. * @param properties The properties of the timeline click event. */ - protected _timelineClickHandler(properties: TimelineEventPropertiesResult): void { + protected async _timelineClickHandler( + properties: TimelineEventPropertiesResult, + ): Promise { // Calls to stopEventFromActivatingCardWideActions() are included for // completeness. Timeline does not support card-wide events and they are // disabled in card.ts in `_getMergedActions`. - if (properties.what === 'item' || this._ignoreClick) { + if ( + this._ignoreClick || + (properties.what && + ['item', 'background', 'group-label', 'axis'].includes(properties.what)) + ) { stopEventFromActivatingCardWideActions(properties.event); } if ( - !this._ignoreClick && - properties.what && - this.hass && - this.dataManager && - this.cameras && - this.view + this._ignoreClick || + !this.hass || + !this._timeline || + !this.cameras || + !this.view || + !this.cameraManager || + !properties.what ) { - if ( - this.timelineConfig?.show_recordings && - ['background', 'group-label', 'axis'].includes(properties.what) - ) { - stopEventFromActivatingCardWideActions(properties.event); + return; + } - if (['background', 'group-label'].includes(properties.what)) { - const window = this._timeline?.getWindow(); - if (window) { - if (properties.group) { - changeViewToRecording( - this, - this.hass, - this.dataManager, - this.cameras, - this.view, - { - cameraIDs: new Set([String(properties.group)]), - targetTime: - properties.what === 'background' ? properties.time : window.end, - }, - ); - } else if (this.mini && this.view?.camera) { - // In mini mode group may not be displayed / used, so just use the camera directly. - changeViewToRecording( - this, - this.hass, - this.dataManager, - this.cameras, - this.view, - { - targetTime: window.end, - }, - ); - } - } - } else { - changeViewToRecording( - this, - this.hass, - this.dataManager, - this.cameras, - this.view, - { - cameraIDs: this._getAllCameraIDs(), - start: startOfHour(properties.time), - end: endOfHour(properties.time), - targetTime: properties.time, - }, - ); - } - } else if ( - properties.what === 'item' && - properties.item && - this.view && - this.view.target?.children && - this.dataManager - ) { - let childIndex: number | null = null; - let target: FrigateBrowseMediaSource | null = null; - let context: ViewContext = {}; + let view: View | null = null; - if (this.view.is('recording')) { - const thumbnails = this._generateThumbnails(properties.item); + if ( + this.timelineConfig?.show_recordings && + ['background', 'group-label'].includes(properties.what) + ) { + view = await createViewForRecordings( + this, + this.hass, + this.cameraManager, + this.cameras, + this.view, + { + targetTime: + properties.what === 'background' + ? properties.time + : this._timeline.getWindow().end, + ...(properties.group && { + cameraIDs: new Set([String(properties.group)]), + }), + }, + ); + } else if (this.timelineConfig?.show_recordings && properties.what === 'axis') { + view = await createViewForRecordings( + this, + this.hass, + this.cameraManager, + this.cameras, + this.view, + { + cameraIDs: this._getAllCameraIDs(), + start: startOfHour(properties.time), + end: endOfHour(properties.time), + targetTime: properties.time, + }, + ); + } else if (properties.item && properties.what === 'item') { + const newResults = this.view.queryResults + ?.clone() + .resetSelectedResult() + .selectResultIfFound( + (media) => !!this.cameras && media.getID() === properties.item, + ); - if (thumbnails) { - target = thumbnails.target; - childIndex = thumbnails.childIndex; - if (thumbnails.target?.children?.length) { - context = generateMediaViewerContextForChildren( - this.dataManager, - thumbnails.target.children, - properties.time, - ); - } - } - } else { - childIndex = this.view.target.children.findIndex( - (child) => child.frigate?.event?.id === properties.item, - ); - } - - if (childIndex !== null && childIndex >= 0) { - this.view - ?.evolve({ - childIndex: childIndex, - ...(target && { target: target }), - }) - .mergeInContext(context) - .dispatchChangeEvent(this); - if (!this.view.isViewerView()) { - dispatchFrigateCardEvent(this, 'thumbnails:open'); - } - } else if (!this.view.isViewerView()) { - dispatchFrigateCardEvent(this, 'thumbnails:close'); + if (!newResults || !newResults.hasSelectedResult()) { + // This can happen if this is a recording query (with recorded hours) + // and an event is clicked on the timeline, or if the current thumbnails + // is a filtered view from the media gallery (i.e. any case where the + // thumbnails may not be match the events on the timeline). + const fullEventView = await this._createViewWithEventMediaQuery( + this._createEventMediaQuerys(), + { + selectedItem: properties.item, + targetView: 'media', + }, + ); + if (fullEventView?.queryResults?.hasResults()) { + view = fullEventView; } + } else { + view = this.view.evolve({ + queryResults: newResults, + }); } + + if (view?.queryResults?.hasResults()) { + view.mergeInContext({ mediaViewer: { seek: properties.time } }); + } + } + + if (view) { + view + // If the user is clicking something in the timeline, don't + // subsequently shift the window (it's pretty jarring). + .dispatchChangeEvent(this); + + if (this.view?.is('timeline')) { + dispatchFrigateCardEvent(this, 'thumbnails:open'); + } + } else if (this.view?.is('timeline')) { + dispatchFrigateCardEvent(this, 'thumbnails:close'); } this._ignoreClick = false; @@ -545,130 +555,123 @@ export class FrigateCardTimelineCore extends LitElement { /** * Get a broader prefetch window from a start and end basis. - * @param start The earlier date. - * @param end The later date. - * @returns An object with a `start` and `end` key to prefetch. + * @param window The window to broaden. + * @returns A broader timeline. */ - protected _getPrefetchWindow(start: Date, end: Date): [Date, Date] { - const delta = differenceInSeconds(end, start); - return [sub(start, { seconds: delta }), add(end, { seconds: delta })]; + protected _getPrefetchWindow(window: TimelineWindow): TimelineWindow { + const delta = differenceInSeconds(window.end, window.start); + return { + start: sub(window.start, { seconds: delta }), + end: add(window.end, { seconds: delta }), + }; } /** * Handle a range change in the timeline. * @param properties vis.js provided range information. */ - protected _timelineRangeChangedHandler(properties: { + protected async _timelineRangeChangedHandler(properties: { start: Date; end: Date; byUser: boolean; event: Event & { additionalEvent: string }; - }): void { + }): Promise { if (!properties.byUser) { return; } this._removeTargetBar(); - if (this.hass && this.cameras && this._timeline && this.timelineConfig) { - const [prefetchStart, prefetchEnd] = this._getPrefetchWindow( - properties.start, - properties.end, + if (!this.hass || !this.cameras) { + return; + } + + const prefetchedWindow = this._getPrefetchWindow(properties); + await this._timelineSource?.refresh(this.hass, prefetchedWindow); + + // Don't show event thumbnails if the user is looking at recordings, + // as the recording "hours" are the media, not the event + // clips/snapshots. + if ( + this._timeline && + this.view && + !MediaQueriesClassifier.areRecordingQueries(this.view.query) + ) { + const newView = await this._createViewWithEventMediaQuery( + this._createEventMediaQuerys({ window: this._timeline.getWindow() }), ); - this.dataManager - ?.fetchIfNecessary(this, this.hass, prefetchStart, prefetchEnd) - .then(() => { - // Don't show event thumbnails if the user is looking at recordings, - // as the recording "hours" are the media, not the event - // clips/snapshots. - if (this._timeline && this.view && !this.view?.is('recording')) { - const thumbnails = this._generateThumbnails(); - // Update the view to reflect the new thumbnails and the timeline - // window in the context. - this.view - .evolve({ - target: thumbnails?.target ?? null, - childIndex: thumbnails?.childIndex ?? null, - }) - .mergeInContext(this._generateTimelineContext({ noSetWindow: true })) - .dispatchChangeEvent(this); - } - }); + + // Specifically avoid dispatching new results on range change unless there + // is something to be gained by doing so. Example usecase: On initial view + // load in mini timeline mode, the first 50 events are fetched -- the + // first drag of the timeline should not dispatch new results unless + // something is actually useful (as otherwise it creates a visible + // 'flicker' for the user as the viewer reloads all the media). + const newResults = newView?.queryResults; + if (newView && newResults && !this.view.queryResults?.isSupersetOf(newResults)) { + newView?.mergeInContext(this._setWindowInContext())?.dispatchChangeEvent(this); + } } } - /** - * Regenerate the thumbnails from the timeline events. - * @param selectedItem An id to select from the thumbnails (currently selected - * item is used if none is specified). - * @returns An object with two keys, or null on error. The keys are `target` - * containing all the thumbnails, and `childIndex` to refer to the currently - * selected thumbnail. - */ - protected _generateThumbnails(selectedItem?: IdType): { - target: FrigateBrowseMediaSource; - childIndex: number | null; - } | null { - if (!this._timeline) { + protected _createEventMediaQuerys(options?: { + window?: TimelineWindow; + }): EventMediaQueries | null { + if (!this._timeline || !this._timelineSource) { return null; } - const selected: IdType[] = selectedItem - ? [selectedItem] - : this._timeline.getSelection(); - let childIndex = -1; - const children: FrigateBrowseMediaSource[] = []; - this._dataview - ?.get({ - filter: (item) => item.type !== 'background', - order: sortYoungestToOldest, - }) - .forEach((item) => { - const cameraID = item.group ? String(item.group) : null; - const cameraConfig = cameraID ? this.cameras?.get(cameraID) : null; - const event = item.event; - const media = - event?.has_clip && this.timelineConfig?.media !== 'snapshots' - ? 'clips' - : event?.has_snapshot - ? 'snapshots' - : null; + const cacheFriendlyWindow = this._timelineSource.getCacheFriendlyEventWindow( + options?.window ?? this._timeline.getWindow(), + ); - if ( - cameraID && - cameraConfig && - event && - media && - cameraConfig.frigate.camera_name - ) { - children.push( - createChild( - getEventTitle(event), - getEventMediaContentID( - cameraConfig.frigate.client_id, - cameraConfig.frigate.camera_name, - event.id, - media, - ), - { - thumbnail: getEventThumbnailURL(cameraConfig.frigate.client_id, event), - event: event, - cameraID: cameraID, - }, - ), - ); - if (selected.includes(event.id)) { - childIndex = children.length - 1; - } - } - }); - if (!children.length) { + const eventQueries = + this._timelineSource.getTimelineEventQueries(cacheFriendlyWindow); + if (!eventQueries) { return null; } + return new EventMediaQueries(eventQueries); + } - return { - target: createEventParentForChildren('Timeline events', children), - childIndex: childIndex < 0 ? null : childIndex, - }; + protected async _createViewWithEventMediaQuery( + query: EventMediaQueries | null, + options?: { + targetView?: FrigateCardView; + selectedItem?: IdType; + }, + ): Promise { + if (!this.hass || !this.cameraManager || !this.cameras || !this.view || !query) { + return null; + } + const view = await createViewForEvents( + this, + this.hass, + this.cameraManager, + this.cameras, + this.view, + { + query: query, + targetView: options?.targetView, + mediaType: this.timelineConfig?.media, + }, + ); + if (!view) { + return null; + } + if (options?.selectedItem) { + view.queryResults?.selectResultIfFound( + (media) => !!this.cameras && media.getID() === options.selectedItem, + ); + } else { + // If not asked to select a new item, persist the currently selected item + // if possible. + const currentlySelectedResult = this.view.queryResults?.getSelectedResult(); + if (currentlySelectedResult) { + view.queryResults?.selectResultIfFound( + (media) => media.getID() === currentlySelectedResult.getID(), + ); + } + } + return view; } /** @@ -680,61 +683,66 @@ export class FrigateCardTimelineCore extends LitElement { this._getTimelineCameraIDs().forEach((cameraID) => { const cameraConfig = this.cameras?.get(cameraID); - if (cameraConfig) { - if ( - cameraConfig.frigate.camera_name && - cameraConfig.frigate.camera_name !== CAMERA_BIRDSEYE - ) { - groups.push({ - id: cameraID, - content: getCameraTitle(this.hass, cameraConfig), - }); - } + if (!this.hass || !cameraConfig || !this.cameraManager) { + return; + } + const cameraMetadata = this.cameraManager.getCameraMetadata( + this.hass, + cameraConfig, + ); + if ( + cameraMetadata && + cameraConfig.frigate.camera_name && + cameraConfig.frigate.camera_name !== CAMERA_BIRDSEYE + ) { + groups.push({ + id: cameraID, + content: cameraMetadata.title, + }); } }); return new DataSet(groups); } - /** - * Given an event get an appropriate start/end time window around the event. - * @param event The FrigateEvent to consider. - * @returns A tuple of start/end date. - */ - protected _getStartEndFromEvent(event: FrigateEvent): [Date, Date] { - const windowSeconds = this._getConfiguredWindowSeconds(); - if (event.end_time) { - if (event.end_time - event.start_time > windowSeconds) { - // If the event is larger than the configured window, only show the most - // recent portion of the event that fits in the window. - return [ - sub(fromUnixTime(event.end_time), { seconds: windowSeconds }), - fromUnixTime(event.end_time), - ]; - } else { - // If the event is shorter than the configured window, center the event - // in the window. - const gap = windowSeconds - (event.end_time - event.start_time); - return [ - sub(fromUnixTime(event.start_time), { seconds: gap / 2 }), - add(fromUnixTime(event.end_time), { seconds: gap / 2 }), - ]; - } - } - // If there's no end-time yet, place the start-time in the center of the - // time window. - return [ - sub(fromUnixTime(event.start_time), { seconds: windowSeconds / 2 }), - add(fromUnixTime(event.start_time), { seconds: windowSeconds / 2 }), - ]; - } + protected _getPerfectWindowFromMedia(media: ViewMedia): TimelineWindow | null { + const startTime = media.getStartTime(); + const endTime = media.getEndTime(); - /** - * Given a recording get the start/end window. - * @param recording The FrigateRecording to consider. - * @returns A tuple of start/end date. - */ - protected _getStartEndFromRecording(recording: FrigateRecording): [Date, Date] { - return [fromUnixTime(recording.start_time), fromUnixTime(recording.end_time)]; + if (ViewMediaClassifier.isEvent(media)) { + const windowSeconds = this._getConfiguredWindowSeconds(); + + if (startTime && endTime) { + if (endTime.getTime() - startTime.getTime() > windowSeconds * 1000) { + // If the event is larger than the configured window, only show the most + // recent portion of the event that fits in the window. + return { + start: sub(endTime, { seconds: windowSeconds }), + end: endTime, + }; + } else { + // If the event is shorter than the configured window, center the event + // in the window. + const gap = windowSeconds - (endTime.getTime() - startTime.getTime()) / 1000; + return { + start: sub(startTime, { seconds: gap / 2 }), + end: add(endTime, { seconds: gap / 2 }), + }; + } + } else if (startTime) { + // If there's no end-time yet, place the start-time in the center of the + // time window. + return { + start: sub(startTime, { seconds: windowSeconds / 2 }), + end: add(startTime, { seconds: windowSeconds / 2 }), + }; + } + } else if (ViewMediaClassifier.isRecording(media) && startTime && endTime) { + return { + start: startTime, + end: endTime, + }; + } + return null; } /** @@ -751,12 +759,12 @@ export class FrigateCardTimelineCore extends LitElement { * Get desired timeline start/end time. * @returns A tuple of start/end date. */ - protected _getStartEnd(): [Date, Date] { + protected _getDefaultStartEnd(): TimelineWindow { const end = new Date(); const start = sub(end, { seconds: this._getConfiguredWindowSeconds(), }); - return [start, end]; + return { start: start, end: end }; } /** @@ -778,7 +786,7 @@ export class FrigateCardTimelineCore extends LitElement { return null; } - const [start, end] = this._getStartEnd(); + const defaultWindow = this._getDefaultStartEnd(); // Configuration for the Timeline, see: // https://visjs.github.io/vis-timeline/docs/timeline/#Configuration_Options @@ -796,28 +804,40 @@ export class FrigateCardTimelineCore extends LitElement { maxItems: this.timelineConfig.clustering_threshold, clusterCriteria: (first: TimelineItem, second: TimelineItem): boolean => { - // Never include the target media in a cluster, and never group - // different object types together (e.g. person and car). + if (!this.cameras) { + return false; + } + + const media = this.view?.queryResults?.getSelectedResult(); + const selectedId = media?.getID(); + const firstMedia = (first).media; + const secondMedia = (second).media; + + // Never include the currently selected item in a cluster, and + // never group different object types together (e.g. person and + // car). return ( - [first.type, second.type].every((type) => type !== 'background') && + first.type !== 'background' && first.type === second.type && - !!first.id && - first.id !== this.view?.media?.frigate?.event?.id && - !!second.id && - second.id != this.view?.media?.frigate?.event?.id && - (first).event?.label === - (second).event?.label + first.id !== selectedId && + second.id !== selectedId && + !!firstMedia && + !!secondMedia && + ViewMediaClassifier.isEvent(firstMedia) && + ViewMediaClassifier.isEvent(secondMedia) && + firstMedia.isGroupableWith(secondMedia) ); }, } - : (false as TimelineOptionsCluster), + : // Timeline type information is incorrect requiring this 'as'. + (false as unknown as TimelineOptionsCluster), minHeight: '100%', maxHeight: '100%', zoomMax: 1 * 24 * 60 * 60 * 1000, zoomMin: 1 * 1000, selectable: true, - start: start, - end: end, + start: defaultWindow.start, + end: defaultWindow.end, groupHeightMode: 'auto', tooltip: { followMouse: true, @@ -828,12 +848,7 @@ export class FrigateCardTimelineCore extends LitElement { disabled: false, filterOptions: { whiteList: { - 'frigate-card-timeline-thumbnail': [ - 'details', - 'thumbnail', - 'label', - 'event', - ], + 'frigate-card-timeline-thumbnail': ['details', 'item'], div: ['title'], span: ['style'], }, @@ -856,86 +871,86 @@ export class FrigateCardTimelineCore extends LitElement { * Update the timeline from the view object. */ protected async _updateTimelineFromView(): Promise { - if (!this.hass || !this.cameras || !this.view || !this.timelineConfig) { + if ( + !this.hass || + !this.cameras || + !this.view || + !this.timelineConfig || + !this._timelineSource || + !this._timeline + ) { return; } - const event = this.view?.media?.frigate?.event; - const recording = this.view?.media?.frigate?.recording; + const timelineWindow = this._timeline.getWindow(); - const [windowStart, windowEnd] = event - ? this._getStartEndFromEvent(event) - : recording - ? this._getStartEndFromRecording(recording) - : this._getStartEnd(); + // Calculate the timeline window to show. If there is a window set in the + // view context, always honor that. Otherwise, if there's a selected media + // item that is already within the current window (even if it's not + // perfectly positioned) -- leave it as is. Otherwise, change the window to + // perfectly center on the media. + + let desiredWindow = timelineWindow; + const media = this.view.queryResults?.getSelectedResult(); + const mediaStartTime = media?.getStartTime(); + const mediaEndTime = media?.getEndTime(); + const mediaWindow: TimelineWindow | null = + media && mediaStartTime && mediaEndTime + ? { start: mediaStartTime, end: mediaEndTime } + : null; + const context = this.view.context?.timeline; + + if (context && context.window) { + desiredWindow = context.window; + } else if (media && mediaWindow && !rangesOverlap(mediaWindow, timelineWindow)) { + const perfectMediaWindow = this._getPerfectWindowFromMedia(media); + if (perfectMediaWindow) { + desiredWindow = perfectMediaWindow; + } + } + const prefetchedWindow = this._getPrefetchWindow(desiredWindow); - let fetched = false; if (!this._pointerHeld) { // Don't fetch any data or touch the timeline in any way if the user is // currently interacting with it. Without this the subsequent data fetches // (via fetchIfNecessary) may update the timeline contents which causes // the visjs timeline to stop dragging/panning operations which is very // disruptive to the user. - const [prefetchStart, prefetchEnd] = this._getPrefetchWindow( - windowStart, - windowEnd, - ); - fetched = !!(await this.dataManager?.fetchIfNecessary( - this, - this.hass, - prefetchStart, - prefetchEnd, - )); + await this._timelineSource?.refresh(this.hass, prefetchedWindow); } - this._timeline?.setSelection(event ? [event.id] : [], { - focus: false, - animation: { - animation: false, - zoom: false, - }, - }); - - if (!this._pointerHeld && event && this._isClustering()) { + const mediaID = media?.getID(); + if (!this._pointerHeld && media && mediaID && this._isClustering()) { // Hack: Clustering may not update unless the dataset changes, artifically // update the dataset to ensure the newly selected item cannot be included // in a cluster. Only do this when the pointer is not held to avoid // interrupting the user and to make the timeline smoother. - this.dataManager?.rewriteItem(event.id); + + // Need to this rewrite prior to setting the selection (just below), or + // the selection will be lost on rewrite. + this._timelineSource?.rewriteEvent(mediaID); } - if ( - !this._pointerHeld && - !this.view.context?.timeline?.noSetWindow && - this._timeline - ) { - // Regenerate the thumbnails after the selection, to allow the new selection - // to be in the generated view. - const context = this.view.context?.timeline; - const timelineWindow = this._timeline.getWindow(); - - // If there's a set context window, always move to it. - if (context?.window && !isEqual(context.window, timelineWindow)) { - this._timeline.setWindow(context.window.start, context.window.end); - } else if (event || recording) { - const source = event ?? (recording as FrigateEvent | FrigateRecording); - const start = fromUnixTime(source.start_time); - const end = source.end_time ? fromUnixTime(source.end_time) : 0; - - // If there's an event or recording outside the current window, move to it. - if ( - start < timelineWindow.start || - start > timelineWindow.end || - (end && (end < timelineWindow.start || end > timelineWindow.end)) - ) { - this._timeline.setWindow(windowStart, windowEnd); - } - } + const desiredId = + !!media && ViewMediaClassifier.isEvent(media) ? media.getID() : null; + if (desiredId) { + this._timeline?.setSelection([desiredId], { + focus: false, + animation: { + animation: false, + zoom: false, + }, + }); } - // Only generate thumbnails if an actual fetch occurred, to avoid getting - // stuck in a loop (the subsequent fetches will not actually fetch since the - // data will have been cached). + // Set the timeline window if necessary. + if (!this._pointerHeld && !isEqual(desiredWindow, timelineWindow)) { + this._timeline.setWindow(desiredWindow.start, desiredWindow.end); + } + + // Only generate thumbnails if the existing query is not an acceptable + // match, to avoid getting stuck in a loop (the subsequent fetches will not + // actually fetch since the data will have been cached). // // Timeline receives a new `view` // -> Events fetched @@ -944,40 +959,53 @@ export class FrigateCardTimelineCore extends LitElement { // -> New view received ... [loop] // // Also don't generate thumbnails in mini-timelines (they will already have - // been generated), or if the media child is a recording. + // been generated), or if the view is for recordings (media thumbnails are + // recordings, not events in this case). + + const freshMediaQuery = this._createEventMediaQuerys({ + window: desiredWindow, + }); + if ( - (fetched || !this.view.context?.timeline?.generatedThumbnails) && !this.mini && - !recording + !MediaQueriesClassifier.areRecordingQueries(this.view.query) && + freshMediaQuery && + !this._alreadyHasAcceptableMediaQuery(freshMediaQuery) ) { - const thumbnails = this._generateThumbnails(); - this.view - ?.evolve({ - target: thumbnails?.target ?? null, - childIndex: thumbnails?.childIndex ?? null, - }) - .mergeInContext(this._generateTimelineContext()) + (await this._createViewWithEventMediaQuery(freshMediaQuery)) + ?.mergeInContext(this._setWindowInContext(desiredWindow)) .dispatchChangeEvent(this); } } + protected _alreadyHasAcceptableMediaQuery(freshMediaQuery: MediaQueries): boolean { + const currentQueries = this.view?.query?.getQueries(); + const currentResultTimestamp = this.view?.queryResults?.getResultsTimestamp(); + + return ( + !!this.cameraManager && + !!currentQueries && + !!currentResultTimestamp && + isEqual(currentQueries, freshMediaQuery.getQueries()) && + this.cameraManager.areMediaQueriesResultsFresh( + currentQueries, + currentResultTimestamp, + ) + ); + } + /** * Generate the context for timeline views. - * @param options Configure how the context is set. * @returns The TimelineViewContext object. */ - protected _generateTimelineContext(options?: { - noSetWindow?: boolean; - generatedThumbnails?: boolean; - }): ViewContext { - const newContext: TimelineViewContext = { - generatedThumbnails: options?.generatedThumbnails ?? true, + protected _setWindowInContext(window?: TimelineWindow): ViewContext { + const newWindow = window ?? this._timeline?.getWindow(); + return { + timeline: { + ...this.view?.context?.timeline, + ...(newWindow && { window: newWindow }), + }, }; - - if (options?.noSetWindow) { - newContext.noSetWindow = options.noSetWindow; - } - return { timeline: newContext }; } /** @@ -1003,6 +1031,23 @@ export class FrigateCardTimelineCore extends LitElement { this.removeAttribute('recordings'); } } + + if ( + changedProps.has('cameraManager') || + changedProps.has('cameras') || + changedProps.has('timelineConfig') || + changedProps.has('cameraIDs') + ) { + if (this.cameraManager && this.cameras && this.timelineConfig) { + this._timelineSource = new TimelineDataSource( + this.cameraManager, + this._getTimelineCameraIDs(), + this.timelineConfig.media, + ); + } else { + this._timelineSource = null; + } + } } /** @@ -1011,6 +1056,7 @@ export class FrigateCardTimelineCore extends LitElement { protected _destroy(): void { this._timeline?.destroy(); this._timeline = undefined; + this._targetBarVisible = false; } /** @@ -1020,7 +1066,7 @@ export class FrigateCardTimelineCore extends LitElement { protected updated(changedProperties: PropertyValues): void { super.updated(changedProperties); - if (changedProperties.has('cameras')) { + if (changedProperties.has('cameras') || changedProperties.has('cameraManager')) { this._destroy(); } @@ -1028,14 +1074,11 @@ export class FrigateCardTimelineCore extends LitElement { let createdTimeline = false; if ( - this.dataManager && + this._timelineSource && this._refTimeline.value && options && this.timelineConfig && - (changedProperties.has('timelineConfig') || - (this.mini && - changedProperties.has('view') && - this.view?.camera !== changedProperties.get('view').camera)) + (changedProperties.has('timelineConfig') || changedProperties.has('cameraIDs')) ) { if (this._timeline) { this._destroy(); @@ -1052,26 +1095,20 @@ export class FrigateCardTimelineCore extends LitElement { return; } - this._dataview = this.dataManager.createDataView( - this._getTimelineCameraIDs(), - !!this.timelineConfig.show_recordings, - this.timelineConfig.media, - ); - createdTimeline = true; if (this.mini && groups.length === 1) { // In a mini timeline, if there's only one group don't bother grouping // at all. this._timeline = new Timeline( this._refTimeline.value, - this._dataview, + this._timelineSource.dataset, options, ) as Timeline; this.removeAttribute('groups'); } else { this._timeline = new Timeline( this._refTimeline.value, - this._dataview, + this._timelineSource.dataset, groups, options, ) as Timeline; diff --git a/src/components/timeline.ts b/src/components/timeline.ts index 9c7f1c1e..b8a8446b 100644 --- a/src/components/timeline.ts +++ b/src/components/timeline.ts @@ -2,8 +2,8 @@ import { CSSResultGroup, html, LitElement, TemplateResult, unsafeCSS } from 'lit import { customElement, property } from 'lit/decorators.js'; import timelineStyle from '../scss/timeline.scss'; import { CameraConfig, ExtendedHomeAssistant, TimelineConfig } from '../types'; -import { DataManager } from '../utils/data-manager'; -import { View } from '../view'; +import { CameraManager } from '../camera-manager/manager'; +import { View } from '../view/view'; import './surround.js'; import './timeline-core.js'; @@ -27,7 +27,7 @@ export class FrigateCardTimeline extends LitElement { public timelineConfig?: TimelineConfig; @property({ attribute: false }) - public dataManager?: DataManager; + public cameraManager?: CameraManager; /** * Master render method. @@ -42,8 +42,8 @@ export class FrigateCardTimeline extends LitElement { .hass=${this.hass} .view=${this.view} .thumbnailConfig=${this.timelineConfig.controls.thumbnails} + .cameraManager=${this.cameraManager} .cameras=${this.cameras} - .fetch=${false} > `; diff --git a/src/components/viewer.ts b/src/components/viewer.ts index 156a21ac..d68f0dea 100644 --- a/src/components/viewer.ts +++ b/src/components/viewer.ts @@ -1,5 +1,5 @@ import { Task } from '@lit-labs/task'; -import { EmblaOptionsType, EmblaPluginType } from 'embla-carousel'; +import { EmblaPluginType } from 'embla-carousel'; import { WheelGesturesPlugin } from 'embla-carousel-wheel-gestures'; import { CSSResultGroup, @@ -12,34 +12,25 @@ import { import { customElement, property } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { createRef, Ref, ref } from 'lit/directives/ref.js'; -import { renderProgressIndicator } from '../components/message.js'; +import { dispatchMessageEvent, renderProgressIndicator } from '../components/message.js'; import viewerStyle from '../scss/viewer.scss'; import viewerCarouselStyle from '../scss/viewer-carousel.scss'; import { - BrowseMediaNeighbors, - BrowseMediaQueryParameters, CameraConfig, CardWideConfig, ExtendedHomeAssistant, - FrigateBrowseMediaSource, frigateCardConfigDefaults, FrigateCardMediaPlayer, MediaLoadedInfo, + ResolvedMedia, TransitionEffect, ViewerConfig, } from '../types.js'; import { stopEventFromActivatingCardWideActions } from '../utils/action.js'; -import { contentsChanged } from '../utils/basic.js'; -import { - fetchLatestMediaAndDispatchViewChange, - getEventStartTime, - getFullDependentBrowseMediaQueryParametersOrDispatchError, - isTrueMedia, - multipleBrowseMediaQueryMerged, - overrideMultiBrowseMediaQueryParameters, -} from '../utils/ha/browse-media.js'; +import { contentsChanged, errorToConsole } from '../utils/basic.js'; import { ResolvedMediaCache, resolveMedia } from '../utils/ha/resolved-media.js'; -import { View } from '../view.js'; +import { View } from '../view/view.js'; +import { MediaQueriesClassifier } from '../view/media-queries-classifier'; import { AutoMediaPlugin } from './embla-plugins/automedia.js'; import { Lazyload } from './embla-plugins/lazyload.js'; import { @@ -55,20 +46,19 @@ import '../patches/ha-hls-player'; import './surround.js'; import { renderTask } from '../utils/task.js'; import { updateElementStyleFromMediaLayoutConfig } from '../utils/media-layout.js'; -import { DataManager } from '../utils/data-manager.js'; -import { changeViewToRecentRecordingForCameraAndDependents } from '../utils/media-to-view.js'; - -export interface MediaSeek { - // Specifies the point at which this recording should be played, the - // seek_time is the date of the desired play point (for display purposes - // usually), and seek_seconds is the number of seconds to seek into the video - // stream to reach that point. - seekTime: number; - seekSeconds: number; -} +import { CameraManager } from '../camera-manager/manager.js'; +import { + changeViewToRecentEventsForCameraAndDependents, + changeViewToRecentRecordingForCameraAndDependents, +} from '../utils/media-to-view.js'; +import { ViewMedia } from '../view/media.js'; +import { ViewMediaClassifier } from '../view/media-classifier'; +import { guard } from 'lit/directives/guard.js'; +import { localize } from '../localize/localize.js'; +import { MediaQueriesResults } from '../view/media-queries-results.js'; export interface MediaViewerViewContext { - seek: Map; + seek?: Date; } declare module 'view' { @@ -95,7 +85,7 @@ export class FrigateCardViewer extends LitElement { public resolvedMediaCache?: ResolvedMediaCache; @property({ attribute: false }) - public dataManager?: DataManager; + public cameraManager?: CameraManager; @property({ attribute: false }) public cardWideConfig?: CardWideConfig; @@ -110,26 +100,18 @@ export class FrigateCardViewer extends LitElement { !this.view || !this.cameras || !this.viewerConfig || - !this.dataManager + !this.cameraManager ) { return; } - const browseMediaQueryParameters = - getFullDependentBrowseMediaQueryParametersOrDispatchError( - this, - this.hass, - this.cameras, - this.view.camera, - ); - - if (!this.view.target) { - // If the target is not specified, the view must tell us which mediaType - // to search for. When the target *is* specified, the view is not required - // to indicate the media type (e.g. the mixed 'events' view from the + if (!this.view.queryResults?.hasResults()) { + // If the query is not specified, the view must tell us which mediaType to + // search for. When the query *is* specified, the view is not required to + // indicate the media type (e.g. the mixed 'media' view from the // timeline). - const mediaType = this.view.getMediaType(); - if (!browseMediaQueryParameters || !mediaType) { + const mediaType = this.view.getDefaultMediaType(); + if (!mediaType) { return; } @@ -137,7 +119,7 @@ export class FrigateCardViewer extends LitElement { changeViewToRecentRecordingForCameraAndDependents( this, this.hass, - this.dataManager, + this.cameraManager, this.cameras, this.view, { @@ -145,13 +127,16 @@ export class FrigateCardViewer extends LitElement { }, ); } else { - fetchLatestMediaAndDispatchViewChange( + changeViewToRecentEventsForCameraAndDependents( this, this.hass, + this.cameraManager, + this.cameras, this.view, - overrideMultiBrowseMediaQueryParameters(browseMediaQueryParameters, { + { + targetView: 'media', mediaType: mediaType, - }), + }, ); } return renderProgressIndicator({ cardWideConfig: this.cardWideConfig }); @@ -160,18 +145,18 @@ export class FrigateCardViewer extends LitElement { return html` @@ -204,46 +189,52 @@ export class FrigateCardViewerCarousel extends LitElement { @property({ attribute: false, hasChanged: contentsChanged }) public viewerConfig?: ViewerConfig; - @property({ attribute: false }) - public browseMediaQueryParameters?: BrowseMediaQueryParameters[] | null; - @property({ attribute: false }) public resolvedMediaCache?: ResolvedMediaCache; @property({ attribute: false }) public cardWideConfig?: CardWideConfig; + @property({ attribute: false }) + public cameras?: Map; + + @property({ attribute: false }) + public cameraManager?: CameraManager; + protected _refMediaCarousel: Ref = createRef(); - // Mapping of slide # to FrigateBrowseMediaSource child #. - // (Folders are not media items that can be rendered). - protected _slideToChild: Record = {}; - - protected _carouselOptions?: EmblaOptionsType; - protected _carouselPlugins?: EmblaPluginType[]; - // A task to resolve target media if lazy loading is disabled. protected _mediaResolutionTask = new Task< - [FrigateBrowseMediaSource | null | undefined], + [ViewerConfig | undefined, Map | undefined, View | undefined], void >( this, - async ([target]: (FrigateBrowseMediaSource | null | undefined)[]): Promise => { - for ( - let i = 0; - !this.viewerConfig?.lazy_load && - this.hass && - target && - target.children && - i < (target.children || []).length; - ++i + async ([viewerConfig, cameras, view]: [ + ViewerConfig | undefined, + Map | undefined, + View | undefined, + ]): Promise => { + if ( + !this.hass || + !viewerConfig?.lazy_load || + !cameras || + !view || + !view.queryResults?.hasResults() ) { - if (isTrueMedia(target.children[i])) { - await resolveMedia(this.hass, target.children[i], this.resolvedMediaCache); - } + return; } + const promises: Promise[] = []; + view.queryResults?.getResults()?.forEach((media: ViewMedia) => { + const mediaContentID = media.getContentID(); + if (this.hass && mediaContentID) { + promises.push( + resolveMedia(this.hass, mediaContentID, this.resolvedMediaCache), + ); + } + }); + await Promise.all(promises); }, - () => [this.view?.target], + () => [this.viewerConfig, this.cameras, this.view], ); /** @@ -251,50 +242,17 @@ export class FrigateCardViewerCarousel extends LitElement { * @param changedProperties The properties that were changed in this render. */ updated(changedProperties: PropertyValues): void { - const frigateCardCarousel = this._refMediaCarousel.value?.frigateCardCarousel(); + super.updated(changedProperties); - if (frigateCardCarousel && changedProperties.has('view')) { + if (changedProperties.has('view')) { const oldView = changedProperties.get('view') as View | undefined; - if (oldView) { - if ( - oldView.target === this.view?.target && - oldView.childIndex !== this.view.childIndex - ) { - const slide = this._getSlideForChild(this.view.childIndex); - if ( - slide !== null && - slide !== frigateCardCarousel.getCarouselSelected()?.index - ) { - // If the media target is the same as already loaded, but isn't of - // the selected slide, scroll to that slide. - frigateCardCarousel.carouselScrollTo(slide); - } - } - } - // Seek into the video if the seek time has changed (this is also called // on media load, since the media may or may not have been loaded at // this point). if (this.view?.context?.mediaViewer !== oldView?.context?.mediaViewer) { - this._recordingSeekHandler(); + this._seekHandler(); } } - super.updated(changedProperties); - } - - /** - * Get the slide number given a media child number. - * @param childIndex The child index (relative to `view.target`) - * @returns A number or null if the child is not found. - */ - protected _getSlideForChild(childIndex: number | null | undefined): number | null { - if (childIndex === undefined || childIndex === null) { - return null; - } - const slideIndex = Object.keys(this._slideToChild).find( - (key) => this._slideToChild[key] === childIndex, - ); - return slideIndex !== undefined ? Number(slideIndex) : null; } /** @@ -308,18 +266,6 @@ export class FrigateCardViewerCarousel extends LitElement { ); } - /** - * Get the Embla options to use. - * @returns An EmblaOptionsType object or undefined for no options. - */ - protected _getOptions(): EmblaOptionsType { - return { - // Start the carousel on the selected child number. - startIndex: this._getSlideForChild(this.view?.childIndex) ?? 0, - draggable: this.viewerConfig?.draggable ?? true, - }; - } - /** * The the HLS player on a slide (or current slide if not provided.) * @param slide An optional slide. @@ -344,10 +290,7 @@ export class FrigateCardViewerCarousel extends LitElement { protected _getPlugins(): EmblaPluginType[] { return [ // Only enable wheel plugin if there is more than one media item. - ...(this.view && - this.view.target && - this.view.target.children && - this.view.target.children.length > 1 + ...(this.view?.queryResults?.getResultsCount() ?? 0 > 1 ? [ WheelGesturesPlugin({ // Whether the carousel is vertical or horizontal, interpret y-axis wheel @@ -384,154 +327,107 @@ export class FrigateCardViewerCarousel extends LitElement { * @returns A BrowseMediaNeighbors with indices and objects of true media * neighbors. */ - protected _getMediaNeighbors(): BrowseMediaNeighbors | null { - if ( - !this.view || - !this.view.target || - !this.view.target.children || - this.view.childIndex === null - ) { - return null; + protected _getMediaNeighbors(): [ViewMedia | null, ViewMedia | null] { + const selectedIndex = this.view?.queryResults?.getSelectedIndex() ?? null; + const resultCount = this.view?.queryResults?.getResultsCount() ?? 0; + if (!this.view || !this.view.queryResults || selectedIndex === null) { + return [null, null]; } - // Work backwards from the index to get the previous real media. - let prevIndex: number | null = null; - for (let i = this.view.childIndex - 1; i >= 0; i--) { - const media = this.view.target.children[i]; - if (media && isTrueMedia(media)) { - prevIndex = i; - break; - } - } - - // Work forwards from the index to get the next real media. - let nextIndex: number | null = null; - for (let i = this.view.childIndex + 1; i < this.view.target.children.length; i++) { - const media = this.view.target.children[i]; - if (media && isTrueMedia(media)) { - nextIndex = i; - break; - } - } - - return { - previousIndex: prevIndex, - previous: prevIndex != null ? this.view.target.children[prevIndex] : null, - nextIndex: nextIndex, - next: nextIndex != null ? this.view.target.children[nextIndex] : null, - }; + const previous: ViewMedia | null = + selectedIndex > 0 ? this.view.queryResults.getResult(selectedIndex - 1) : null; + const next: ViewMedia | null = + selectedIndex + 1 < resultCount + ? this.view.queryResults.getResult(selectedIndex + 1) + : null; + return [previous, next]; } /** - * Get a clip view that matches a given snapshot. Includes clips within the - * same range as the current view. - * @param snapshot The snapshot to find a matching clip for. - * @returns The view that would show the matching clip. + * Dispatch a clip view that matches the current (snapshot) query. + * @param index The index of the selected media. */ - protected async _findRelatedClipView( - snapshot: FrigateBrowseMediaSource, - ): Promise { + protected async _dispatchRelatedClipView(index: number): Promise { + const media = this.view?.queryResults?.getResult(index); + if ( !this.hass || !this.view || - !this.view.target || - !this.view.target.children || - !this.view.target.children.length || - !this.browseMediaQueryParameters + !this.cameraManager || + !media || + // If this specific media item has no clip, then do nothing (even if all + // the other media items do). + !ViewMediaClassifier.isEvent(media) || + // If the event certainly has no clip, don't bother going further. If + // we're not sure for this camera type (i.e. hasClip() === null) the query + // will proceed anyway. + media.hasClip() === false || + !MediaQueriesClassifier.areEventQueries(this.view.query) ) { - return null; + return; } - const snapshotStartTime = getEventStartTime(snapshot); - if (!snapshotStartTime) { - return null; + // Convert the query to a clips equivalent. + const clipQuery = this.view.query.clone(); + clipQuery.convertToClipsQueries(); + + const queries = clipQuery.getQueries(); + if (!queries) { + return; } - // Heuristic: At this point, the user has a particular snapshot that they - // are interested in and want to see a related clip, yet the viewer code - // does not know the exact search criteria that led to that snapshot (e.g. - // it could be a 10-deep folder in the gallery). To give the user to ability - // to 'navigate' in the clips view once they change into that mode, this - // heuristic finds the earliest and latest snapshot that the user is - // currently viewing and mirrors that range into the clips view. Then, - // within the results see if there's a clip that matches the same time as - // the snapshot. - let earliest: number | null = null; - let latest: number | null = null; - for (let i = 0; i < this.view.target.children.length; i++) { - const child = this.view.target.children[i]; - if (!isTrueMedia(child)) { - continue; - } - const startTime = getEventStartTime(child); - - if (startTime && (earliest === null || startTime < earliest)) { - earliest = startTime; - } - if (startTime && (latest === null || startTime > latest)) { - latest = startTime; - } - } - if (!earliest || !latest) { - return null; - } - - let clips: FrigateBrowseMediaSource | null; - - const params = overrideMultiBrowseMediaQueryParameters( - this.browseMediaQueryParameters, - { - mediaType: 'clips', - before: latest, - after: earliest, - }, - ); - + let mediaArray: ViewMedia[] | null; try { - clips = await multipleBrowseMediaQueryMerged(this.hass, params); + mediaArray = await this.cameraManager.executeMediaQueries(this.hass, queries); } catch (e) { - // This is best effort. - return null; + errorToConsole(e as Error); + return; + } + if (!mediaArray) { + return; } - if (!clips || !clips.children || !clips.children.length) { - return null; + const results = new MediaQueriesResults(mediaArray); + results.selectResultIfFound((clipMedia) => clipMedia.getID() === media.getID()); + if (!results.hasSelectedResult()) { + return; } - for (let i = 0; i < clips.children.length; i++) { - const child = clips.children[i]; - if (!isTrueMedia(child)) { - continue; - } - const clipStartTime = getEventStartTime(child); - if (clipStartTime && clipStartTime === snapshotStartTime) { - return this.view.evolve({ - view: 'clip', - target: clips, - childIndex: i, - }); - } - } - return null; + this.view + .evolve({ + view: 'media', + query: clipQuery, + queryResults: results, + }) + .dispatchChangeEvent(this); } /** * Handle the user selecting a new slide in the carousel. */ protected _setViewHandler(ev: CustomEvent): void { - if (!this._refMediaCarousel.value || !this.view) { + // The slide may already be selected on load, so don't dispatch a new view + // unless necessary. + if (ev.detail.index !== this.view?.queryResults?.getSelectedIndex()) { + this._setViewSelectedIndex(ev.detail.index); + } + } + + protected _setViewSelectedIndex(index: number): void { + const newResults = this.view?.queryResults?.clone().selectResult(index); + if (!newResults) { return; } + const cameraID = newResults.getSelectedResult()?.getCameraID(); - // Update the childIndex in the view. - const childIndex = this._slideToChild[ev.detail.index]; - if (childIndex !== undefined) { - this.view - .evolve({ - childIndex: childIndex, - }) - .dispatchChangeEvent(this); - } + this.view + ?.evolve({ + queryResults: newResults, + + // Always change the camera to the owner of the selected media. + ...(cameraID && { camera: cameraID }), + }) + .dispatchChangeEvent(this); } /** @@ -539,11 +435,11 @@ export class FrigateCardViewerCarousel extends LitElement { * default location will be the Chromecast receiver, not HA). * @param url The media URL */ - protected _canonicalizeHAURL(url?: string): string | undefined { + protected _canonicalizeHAURL(url?: string): string | null { if (this.hass && url && url.startsWith('/')) { return this.hass.hassUrl(url); } - return url; + return url ?? null; } /** @@ -551,44 +447,38 @@ export class FrigateCardViewerCarousel extends LitElement { * @param index The index of the slide to lazy load. * @param slide The slide to lazy load. */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars protected _lazyloadSlide(index: number, slide: HTMLElement): void { - const childIndex: number | undefined = this._slideToChild[index]; - - if ( - childIndex === undefined || - !this.hass || - !this.view || - !this.view.target || - !this.view.target.children || - !isTrueMedia(this.view.target.children[childIndex]) - ) { + if (!this.hass || !this.view || !this.view.query || !this.cameras) { return; } - resolveMedia( - this.hass, - this.view.target.children[childIndex], - this.resolvedMediaCache, - ).then((resolvedMedia) => { - if (!resolvedMedia) { - return; - } + const media = this.view.queryResults?.getResult(index); + const mediaContentID = media ? media.getContentID() : null; + if (!mediaContentID) { + return; + } - // Snapshots. - const img = slide.querySelector('img') as HTMLImageElement; + resolveMedia(this.hass, mediaContentID, this.resolvedMediaCache).then( + (resolvedMedia) => { + if (!resolvedMedia) { + return; + } - // Frigate >= 0.9.0+ clips. - const hls_player = this._getPlayer(slide) as FrigateCardMediaPlayer & { - url: string; - }; + // Snapshots. + const img = slide.querySelector('img') as HTMLImageElement; - if (img) { - img.src = this._canonicalizeHAURL(resolvedMedia.url) || ''; - } else if (hls_player) { - hls_player.url = this._canonicalizeHAURL(resolvedMedia.url) || ''; - } - }); + // Frigate >= 0.9.0+ clips. + const hls_player = this._getPlayer(slide) as FrigateCardMediaPlayer & { + url: string; + }; + + if (img) { + img.src = this._canonicalizeHAURL(resolvedMedia.url) ?? ''; + } else if (hls_player) { + hls_player.url = this._canonicalizeHAURL(resolvedMedia.url) ?? ''; + } + }, + ); } /** @@ -596,21 +486,18 @@ export class FrigateCardViewerCarousel extends LitElement { * @returns The slides to include in the render. */ protected _getSlides(): TemplateResult[] { - if ( - !this.view || - !this.view.target || - !this.view.target.children || - !this.view.target.children.length - ) { + if (!this.view || !this.view.queryResults) { return []; } const slides: TemplateResult[] = []; - for (let i = 0; i < this.view.target.children?.length; ++i) { - const slide = this._renderMediaItem(this.view.target.children[i], slides.length); - - if (slide) { - slides.push(slide); + for (let i = 0; i < this.view.queryResults.getResultsCount(); ++i) { + const media = this.view.queryResults.getResult(i); + if (media) { + const slide = this._renderMediaItem(media, i); + if (slide) { + slides[i] = slide; + } } } return slides; @@ -620,8 +507,12 @@ export class FrigateCardViewerCarousel extends LitElement { * Determine if all the media in the carousel are resolved. */ protected _isMediaFullyResolved(): boolean { - for (const child of this.view?.target?.children || []) { - if (!this.resolvedMediaCache?.has(child.media_content_id)) { + if (!this.resolvedMediaCache || !this.cameras) { + return false; + } + for (const media of this.view?.queryResults?.getResults() ?? []) { + const mediaContentID = media.getContentID(); + if (mediaContentID && !this.resolvedMediaCache.has(mediaContentID)) { return false; } } @@ -633,32 +524,9 @@ export class FrigateCardViewerCarousel extends LitElement { * @param changedProps The changed properties */ protected willUpdate(changedProps: PropertyValues): void { - // Pre-populate a map between real media slides and view child indicies. - if (changedProps.has('view')) { - this._slideToChild = {}; - let i = 0; - (this.view?.target?.children ?? []).forEach((child, index) => { - if (isTrueMedia(child) && ['video', 'image'].includes(child.media_content_type)) { - this._slideToChild[i++] = index; - } - }) - } - if (changedProps.has('viewerConfig')) { updateElementStyleFromMediaLayoutConfig(this, this.viewerConfig?.layout); } - if (!this._carouselOptions || changedProps.has('viewerConfig')) { - this._carouselOptions = this._getOptions(); - } - if ( - !this._carouselPlugins || - changedProps.has('viewerConfig') || - (changedProps.has('view') && - this.view?.target?.children?.length !== - changedProps.get('view')?.target?.children?.length) - ) { - this._carouselPlugins = this._getPlugins(); - } } /** @@ -680,49 +548,70 @@ export class FrigateCardViewerCarousel extends LitElement { * @returns A template to display to the user. */ protected _render(): TemplateResult | void { - const slides = this._getSlides(); + if ((this.view?.queryResults?.getResultsCount() ?? 0) === 0) { + return dispatchMessageEvent(this, localize('common.no_media'), 'info', { + icon: 'mdi:multimedia', + }); + } - if (!slides.length || !this.view?.media) { + const media = this.view?.queryResults?.getSelectedResult(); + if (!media || !this.cameras) { return; } - const neighbors = this._getMediaNeighbors(); - const [prev, next] = [neighbors?.previous, neighbors?.next]; + const [prev, next] = this._getMediaNeighbors(); + + const scroll = (direction: 'previous' | 'next'): void => { + const currentIndex = this.view?.queryResults?.getSelectedIndex() ?? null; + if (!this.view || !this.view?.queryResults || currentIndex === null) { + return; + } + const newIndex = direction === 'previous' ? currentIndex - 1 : currentIndex + 1; + if (newIndex >= 0 && newIndex < this.view.queryResults.getResultsCount()) { + this._setViewSelectedIndex(newIndex); + } + }; return html` ({ + draggable: this.viewerConfig?.draggable ?? true, + }))} + .carouselPlugins=${guard( + [this.viewerConfig, this.view?.queryResults?.getResults()], + this._getPlugins.bind(this), + )} + .label=${media.getTitle() ?? undefined} .titlePopupConfig=${this.viewerConfig?.controls.title} + .selected=${this.view?.queryResults?.getSelectedIndex() ?? 0} transitionEffect=${this._getTransitionEffect()} @frigate-card:media-carousel:select=${this._setViewHandler.bind(this)} - @frigate-card:media:loaded=${this._recordingSeekHandler.bind(this)} + @frigate-card:media:loaded=${this._seekHandler.bind(this)} > { - this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollPrevious(); + scroll('previous'); stopEventFromActivatingCardWideActions(ev); }} > - ${slides} + ${guard(this.view?.queryResults?.getResults(), () => this._getSlides())} { - this._refMediaCarousel.value?.frigateCardCarousel()?.carouselScrollNext(); + scroll('next'); stopEventFromActivatingCardWideActions(ev); }} > @@ -732,71 +621,70 @@ export class FrigateCardViewerCarousel extends LitElement { /** * Fire a media show event when a slide is selected. */ - protected _recordingSeekHandler(): void { + protected async _seekHandler(): Promise { + const seek = this.view?.context?.mediaViewer?.seek; + const media = this.view?.queryResults?.getSelectedResult(); + if (!this.hass || !media || !seek) { + return; + } + + const seekTime = + (await this.cameraManager?.getMediaSeekTime(this.hass, media, seek)) ?? null; const player = this._getPlayer(); - const childIndex = this.view?.childIndex ?? null; - const seek = - childIndex !== null ? this.view?.context?.mediaViewer?.seek.get(childIndex) : null; - if (player && seek) { - player.seek(seek.seekSeconds); + if (player && seekTime !== null) { + player.seek(seekTime); } } /** * Render a single media item in the viewer carousel. - * @param mediaToRender The FrigateBrowseMediaSource to render. - * @param slideIndex The index of the slide to render. + * @param media The ViewMedia to render. + * @param index The (slide|queryResult) index of the item to render. * @returns A rendered template. */ - protected _renderMediaItem( - mediaToRender: FrigateBrowseMediaSource, - slideIndex: number, - ): TemplateResult | void { + protected _renderMediaItem(media: ViewMedia, index: number): TemplateResult | null { // Skip folders as they cannot be rendered by this viewer. - if ( - !this.hass || - !this.view || - !this.viewerConfig || - !isTrueMedia(mediaToRender) || - !['video', 'image'].includes(mediaToRender.media_content_type) - ) { - return; + if (!this.hass || !this.view || !this.viewerConfig || !this.cameras) { + return null; } const lazyLoad = this.viewerConfig.lazy_load; - const resolvedMedia = this.resolvedMediaCache?.get(mediaToRender.media_content_id); + const mediaContentID = media.getContentID(); + const resolvedMedia = mediaContentID + ? this.resolvedMediaCache?.get(mediaContentID) + : null; if (!resolvedMedia && !lazyLoad) { - return; + return null; } // The media is attached to the player as '.media' which is used in // `_selectSlideMediaShowHandler` (and not used by the player itself). return html`
- ${mediaToRender.media_content_type === 'video' + ${ViewMediaClassifier.isVideo(media) ? html`) => { - wrapMediaLoadedEventForCarousel(slideIndex, e); + wrapMediaLoadedEventForCarousel(index, e); }} > ` : html` { if ( this._refMediaCarousel.value @@ -804,11 +692,7 @@ export class FrigateCardViewerCarousel extends LitElement { ?.carouselClickAllowed() && this.viewerConfig?.snapshot_click_plays_clip ) { - this._findRelatedClipView(mediaToRender).then((view) => { - if (view) { - view.dispatchChangeEvent(this); - } - }); + this._dispatchRelatedClipView(index); } }} @load="${(e: Event) => { @@ -822,9 +706,9 @@ export class FrigateCardViewerCarousel extends LitElement { // images in media-carousel.ts). Here we need to only call the // media load handler on a 'real' load. !lazyLoad || - lazyloadPlugin?.hasLazyloaded(slideIndex) + lazyloadPlugin?.hasLazyloaded(index) ) { - wrapRawMediaLoadedEventForCarousel(slideIndex, e); + wrapRawMediaLoadedEventForCarousel(index, e); } }}" />`} diff --git a/src/config-mgmt.ts b/src/config-mgmt.ts index ab2ebd21..f6a7f16e 100644 --- a/src/config-mgmt.ts +++ b/src/config-mgmt.ts @@ -117,7 +117,7 @@ export const isConfigUpgradeable = function (obj: RawFrigateCardConfig): boolean * @param obj Configuration object. * @returns `true` if the configuration was modified. */ -export const trimConfig = function (obj: RawFrigateCardConfig): boolean { +const trimConfig = function (obj: RawFrigateCardConfig): boolean { const keys = Object.keys(obj); let modified = false; for (let i = 0; i < keys.length; i++) { @@ -221,7 +221,7 @@ const deleteProperty = function (_value: unknown): number | null | undefined { * @param transform An optional transform for the value. * @returns `true` if the configuration was modified. */ -export const moveConfigValue = ( +const moveConfigValue = ( obj: RawFrigateCardConfig, oldPath: string, newPath: string, diff --git a/src/const.ts b/src/const.ts index 2a2d3504..75ee787b 100644 --- a/src/const.ts +++ b/src/const.ts @@ -35,7 +35,7 @@ export const CONF_CAMERAS_ARRAY_TRIGGERS_ENTITIES = export const CONF_ELEMENTS = 'elements' as const; -export const CONF_VIEW = 'view' as const; +const CONF_VIEW = 'view' as const; export const CONF_VIEW_CAMERA_SELECT = `${CONF_VIEW}.camera_select` as const; export const CONF_VIEW_DARK_MODE = `${CONF_VIEW}.dark_mode` as const; export const CONF_VIEW_DEFAULT = `${CONF_VIEW}.default` as const; @@ -53,7 +53,9 @@ export const CONF_VIEW_SCAN_UNTRIGGER_RESET = export const CONF_VIEW_SCAN_UNTRIGGER_SECONDS = `${CONF_VIEW_SCAN}.untrigger_seconds` as const; -export const CONF_EVENT_GALLERY = 'event_gallery' as const; +const CONF_EVENT_GALLERY = 'event_gallery' as const; +export const CONF_EVENT_GALLERY_CONTROLS_FILTER_MODE = + `${CONF_EVENT_GALLERY}.controls.filter.mode` as const; export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS = `${CONF_EVENT_GALLERY}.controls.thumbnails.show_details` as const; export const CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL = @@ -109,7 +111,7 @@ export const CONF_MEDIA_VIEWER_LAYOUT_POSITION_X = export const CONF_MEDIA_VIEWER_LAYOUT_POSITION_Y = `${CONF_MEDIA_VIEWER}.layout.position.y` as const; -export const CONF_LIVE = 'live' as const; +const CONF_LIVE = 'live' as const; export const CONF_LIVE_AUTO_PLAY = `${CONF_LIVE}.auto_play` as const; export const CONF_LIVE_AUTO_PAUSE = `${CONF_LIVE}.auto_pause` as const; export const CONF_LIVE_AUTO_MUTE = `${CONF_LIVE}.auto_mute` as const; @@ -149,7 +151,6 @@ export const CONF_LIVE_LAYOUT_FIT = `${CONF_LIVE}.layout.fit` as const; export const CONF_LIVE_LAYOUT_POSITION_X = `${CONF_LIVE}.layout.position.x` as const; export const CONF_LIVE_LAYOUT_POSITION_Y = `${CONF_LIVE}.layout.position.y` as const; export const CONF_LIVE_DRAGGABLE = `${CONF_LIVE}.draggable` as const; -export const CONF_LIVE_JSMPEG = `${CONF_LIVE}.jsmpeg` as const; export const CONF_LIVE_LAZY_LOAD = `${CONF_LIVE}.lazy_load` as const; export const CONF_LIVE_LAZY_UNLOAD = `${CONF_LIVE}.lazy_unload` as const; export const CONF_LIVE_PRELOAD = `${CONF_LIVE}.preload` as const; @@ -158,7 +159,7 @@ export const CONF_LIVE_SHOW_IMAGE_DURING_LOAD = `${CONF_LIVE}.show_image_during_load` as const; export const CONF_LIVE_WEBRTC_CARD = `${CONF_LIVE}.webrtc_card` as const; -export const CONF_IMAGE = 'image' as const; +const CONF_IMAGE = 'image' as const; export const CONF_IMAGE_LAYOUT_FIT = `${CONF_IMAGE}.layout.fit` as const; export const CONF_IMAGE_LAYOUT_POSITION_X = `${CONF_IMAGE}.layout.position.x` as const; export const CONF_IMAGE_LAYOUT_POSITION_Y = `${CONF_IMAGE}.layout.position.y` as const; @@ -166,7 +167,7 @@ export const CONF_IMAGE_MODE = `${CONF_IMAGE}.mode` as const; export const CONF_IMAGE_REFRESH_SECONDS = `${CONF_IMAGE}.refresh_seconds` as const; export const CONF_IMAGE_URL = `${CONF_IMAGE}.url` as const; -export const CONF_TIMELINE = 'timeline' as const; +const CONF_TIMELINE = 'timeline' as const; export const CONF_TIMELINE_WINDOW_SECONDS = `${CONF_TIMELINE}.window_seconds` as const; export const CONF_TIMELINE_CLUSTERING_THRESHOLD = `${CONF_TIMELINE}.clustering_threshold` as const; @@ -203,14 +204,14 @@ export const CONF_MENU_BUTTONS_MEDIA_PLAYER = export const CONF_MENU_BUTTONS_SNAPSHOTS = `${CONF_MENU}.buttons.snapshots` as const; export const CONF_MENU_BUTTONS_TIMELINE = `${CONF_MENU}.buttons.timeline` as const; -export const CONF_DIMENSIONS = 'dimensions' as const; +const CONF_DIMENSIONS = 'dimensions' as const; export const CONF_DIMENSIONS_ASPECT_RATIO = `${CONF_DIMENSIONS}.aspect_ratio` as const; export const CONF_DIMENSIONS_ASPECT_RATIO_MODE = `${CONF_DIMENSIONS}.aspect_ratio_mode` as const; export const CONF_OVERRIDES = 'overrides' as const; -export const CONF_PERFORMANCE = 'performance' as const; +const CONF_PERFORMANCE = 'performance' as const; export const CONF_PERFORMANCE_FEATURES_ANIMATED_PROGRESS_INDICATOR = `${CONF_PERFORMANCE}.features.animated_progress_indicator`; export const CONF_PERFORMANCE_PROFILE = `${CONF_PERFORMANCE}.profile`; export const CONF_PERFORMANCE_STYLE_BOX_SHADOW = `${CONF_PERFORMANCE}.style.box_shadow`; diff --git a/src/editor.ts b/src/editor.ts index ef0c0002..29ae6893 100644 --- a/src/editor.ts +++ b/src/editor.ts @@ -32,6 +32,7 @@ import { CONF_CAMERAS_ARRAY_WEBRTC_CARD_URL, CONF_DIMENSIONS_ASPECT_RATIO, CONF_DIMENSIONS_ASPECT_RATIO_MODE, + CONF_EVENT_GALLERY_CONTROLS_FILTER_MODE, CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS, CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, @@ -139,10 +140,14 @@ import { THUMBNAIL_WIDTH_MAX, THUMBNAIL_WIDTH_MIN, } from './types.js'; -import { arrayMove } from './utils/basic.js'; -import { getCameraID, getCameraTitle } from './utils/camera.js'; -import { FRIGATE_ICON_SVG_PATH } from './utils/frigate.js'; -import { getEntitiesFromHASS, sideLoadHomeAssistantElements } from './utils/ha'; +import { arrayMove, prettifyTitle } from './utils/basic.js'; +import { getCameraID } from './utils/camera.js'; +import { FRIGATE_ICON_SVG_PATH } from './camera-manager/frigate/icon.js'; +import { + getEntitiesFromHASS, + getEntityTitle, + sideLoadHomeAssistantElements, +} from './utils/ha'; import { setLowPerformanceProfile } from './performance.js'; const MENU_BUTTONS = 'buttons'; @@ -152,6 +157,7 @@ const MENU_CAMERAS_FRIGATE = 'cameras.frigate'; const MENU_CAMERAS_TRIGGERS = 'cameras.triggers'; const MENU_CAMERAS_WEBRTC = 'cameras.webrtc'; const MENU_EVENT_GALLERY_CONTROLS_THUMBNAILS = 'event_gallery.controls.thumbnails'; +const MENU_EVENT_GALLERY_CONTROLS_FILTER = 'event_gallery.controls.filter'; const MENU_IMAGE_LAYOUT = 'image.layout'; const MENU_LIVE_CONTROLS = 'live.controls'; const MENU_LIVE_CONTROLS_NEXT_PREVIOUS = 'live.controls.next_previous'; @@ -279,6 +285,22 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor { value: 'current', label: localize('config.view.views.current') }, ]; + protected _filterModes: EditorSelectOption[] = [ + { value: '', label: '' }, + { + value: 'none', + label: localize('config.common.controls.filter.modes.none'), + }, + { + value: 'left', + label: localize('config.common.controls.filter.modes.left'), + }, + { + value: 'right', + label: localize('config.common.controls.filter.modes.right'), + }, + ]; + protected _menuStyles: EditorSelectOption[] = [ { value: '', label: '' }, { value: 'none', label: localize('config.menu.styles.none') }, @@ -690,8 +712,29 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor cameraIndex: number, cameraConfig: RawFrigateCardConfig, ): string { + // Attempt to render a recognizable name for the camera, starting with the + // most likely to be useful and working our ways towards the least useful. + // This is only used for the editor since the card itself can use the + // cameraManager. return ( - getCameraTitle(this.hass, cameraConfig) || + (typeof cameraConfig?.title === 'string' && cameraConfig.title) || + (typeof cameraConfig?.camera_entity === 'string' + ? getEntityTitle(this.hass, cameraConfig.camera_entity) + : '') || + (typeof cameraConfig?.webrtc_card === 'object' && + cameraConfig.webrtc_card && + typeof cameraConfig.webrtc_card['entity'] === 'string' && + cameraConfig.webrtc_card['entity']) || + // Usage of engine specific logic here is allowed as an exception, since + // the camera manager cannot be started with an unparsed and unloaded + // config. + (typeof cameraConfig?.frigate === 'object' && + cameraConfig.frigate && + typeof cameraConfig?.frigate['camera_name'] === 'string' && + cameraConfig.frigate['camera_name'] + ? prettifyTitle(cameraConfig.frigate['camera_name']) + : '') || + (typeof cameraConfig?.id === 'string' && cameraConfig.id) || localize('editor.camera') + ' #' + cameraIndex ); } @@ -1007,6 +1050,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor configPathShowDetails: string, configPathShowFavoriteControl: string, configPathShowTimelineControl: string, + defaults: { + show_details: boolean; + show_favorite_control: boolean; + show_timeline_control: boolean; + }, options?: { configPathMedia?: string; configPathMode?: string; @@ -1041,23 +1089,19 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor max: THUMBNAIL_WIDTH_MAX, label: localize('config.common.controls.thumbnails.size'), })} - ${this._renderSwitch( - configPathShowDetails, - this._defaults.live.controls.thumbnails.show_details, - { - label: localize('config.common.controls.thumbnails.show_details'), - }, - )} + ${this._renderSwitch(configPathShowDetails, defaults.show_details, { + label: localize('config.common.controls.thumbnails.show_details'), + })} ${this._renderSwitch( configPathShowFavoriteControl, - this._defaults.live.controls.thumbnails.show_favorite_control, + defaults.show_favorite_control, { label: localize('config.common.controls.thumbnails.show_favorite_control'), }, )} ${this._renderSwitch( configPathShowTimelineControl, - this._defaults.live.controls.thumbnails.show_timeline_control, + defaults.show_timeline_control, { label: localize('config.common.controls.thumbnails.show_timeline_control'), }, @@ -1066,6 +1110,31 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor ); } + /** + * Render the thumbnails controls. + * @param domain The submenu domain. + * @param configPathMode Filter mode config path. + * @returns A rendered template. + */ + protected _renderFilterControls( + domain: string, + configPathMode: string, + ): TemplateResult | void { + return this._putInSubmenu( + domain, + true, + 'config.common.controls.filter.editor_label', + { name: 'mdi:filter-cog' }, + html` + ${configPathMode + ? html`${this._renderOptionSelector(configPathMode, this._filterModes, { + label: localize('config.common.controls.filter.mode'), + })}` + : html``} + `, + ); + } + /** * Render the titles controls. * @param domain The submenu domain. @@ -1570,6 +1639,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_DETAILS, CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_LIVE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + this._defaults.live.controls.thumbnails, { configPathMedia: CONF_LIVE_CONTROLS_THUMBNAILS_MEDIA, configPathMode: CONF_LIVE_CONTROLS_THUMBNAILS_MODE, @@ -1617,6 +1687,11 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_DETAILS, CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_EVENT_GALLERY_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + this._defaults.event_gallery.controls.thumbnails, + )} + ${this._renderFilterControls( + MENU_EVENT_GALLERY_CONTROLS_FILTER, + CONF_EVENT_GALLERY_CONTROLS_FILTER_MODE, )}
` : ''} @@ -1675,6 +1750,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_DETAILS, CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + this._defaults.media_viewer.controls.thumbnails, { configPathMode: CONF_MEDIA_VIEWER_CONTROLS_THUMBNAILS_MODE, }, @@ -1735,6 +1811,7 @@ export class FrigateCardEditor extends LitElement implements LovelaceCardEditor CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_DETAILS, CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_FAVORITE_CONTROL, CONF_TIMELINE_CONTROLS_THUMBNAILS_SHOW_TIMELINE_CONTROL, + this._defaults.timeline.controls.thumbnails, { configPathMode: CONF_TIMELINE_CONTROLS_THUMBNAILS_MODE, }, diff --git a/src/localize/languages/en.json b/src/localize/languages/en.json index d296043b..c2dafc51 100644 --- a/src/localize/languages/en.json +++ b/src/localize/languages/en.json @@ -3,11 +3,7 @@ "frigate_card": "Frigate card", "frigate_card_description": "A Lovelace card for use with Frigate", "live": "Live", - "no_clip": "No recent clip", - "no_clips": "No clips", - "no_snapshot": "No recent snapshot", - "no_snapshots": "No snapshots", - "no_recording": "No recent recording", + "no_media": "No media to display", "recordings": "Recordings", "version": "Version" }, @@ -83,6 +79,15 @@ "window_seconds": "The default length of the timeline view in seconds" }, "controls": { + "filter": { + "editor_label": "Media Filter", + "mode": "Filter mode", + "modes": { + "none": "No media filter", + "left": "Media filter in a drawer to the left", + "right": "Media filter in a drawer to the right" + } + }, "next_previous": { "editor_label": "Next & Previous", "size": "Next & previous control size in pixels", @@ -333,7 +338,6 @@ "could_not_render_elements": "Could not render picture elements", "could_not_resolve": "Could not resolve media URL", "diagnostics": "Card diagnostics. Please review for confidential information prior to sharing", - "download_no_event_id": "Could not extract Frigate event id from media", "download_no_media": "No media to download", "download_sign_failed": "Could not sign media URL for download", "duplicate_camera_id": "Duplicate Frigate camera id for the following camera, use the 'id' parameter to uniquely identify cameras", @@ -369,6 +373,33 @@ "start": "Start", "seek": "Seek" }, + "media_filter": { + "all": "All", + "camera": "Camera", + "favorite": "Favorite", + "media_type": "Media Type", + "media_types": { + "clips": "Clips", + "snapshots": "Snapshots", + "recordings": "Recordings" + }, + "not_favorite": "Not Favorite", + "select_camera": "Select camera...", + "select_favorite": "Select favorite...", + "select_media_type": "Select media type...", + "select_what": "Select what...", + "select_when": "Select when...", + "select_where": "Select where...", + "what": "What", + "when": "When", + "whens": { + "past_month": "Past Month", + "past_week": "Past Week", + "today": "Today", + "yesterday": "Yesterday" + }, + "where": "Where" + }, "recording": { "events": "Events", "seek": "Seek" diff --git a/src/localize/languages/it.json b/src/localize/languages/it.json index 8364175e..dd735149 100644 --- a/src/localize/languages/it.json +++ b/src/localize/languages/it.json @@ -3,10 +3,7 @@ "frigate_card": "Frigate card", "frigate_card_description": "Una scheda Lovelace per l'uso con Frigate", "live": "Live", - "no_clip": "Nessuna clip recente", - "no_clips": "Nessun clip", - "no_snapshot": "Nessuna istantanea recente", - "no_snapshots": "Nessuna istantanea", + "no_media": "", "recordings": "Registrazioni", "version": "Versione" }, @@ -81,6 +78,15 @@ "window_seconds": "La lunghezza predefinita della vista della sequenza temporale in secondi" }, "controls": { + "filter": { + "editor_label": "", + "mode": "", + "modes": { + "none": "", + "left": "", + "right": "" + } + }, "next_previous": { "editor_label": "", "size": "Successiva e Precedenti dimensioni di controllo nei pixel", @@ -303,7 +309,6 @@ "could_not_render_elements": "Impossibile renderizzare gli elementi dell'immagine", "could_not_resolve": "Impossibile risolvere l'URL dei media", "diagnostics": "Diagnostica delle carte.Si prega di rivedere per informazioni riservate prima di condividere", - "download_no_event_id": "Impossibile estrarre l'evento ID tramite media", "download_no_media": "Nessun media da scaricare", "download_sign_failed": "Impossibile firmare URL multimediale per il download", "duplicate_camera_id": "Duplicato ID dellla telecamera Frigate, utilizzare il parametro 'ID' per identificare in modo univoco le telecamere", @@ -339,6 +344,33 @@ "start": "Avvia", "seek": "Cercare" }, + "media_filter": { + "all": "", + "camera": "", + "favorite": "", + "media_type": "", + "media_types": { + "clips": "", + "snapshots": "", + "recordings": "" + }, + "not_favorite": "", + "select_camera": "", + "select_favorite": "", + "select_media_type": "", + "select_what": "", + "select_when": "", + "select_where": "", + "what": "", + "when": "", + "whens": { + "past_month": "", + "past_week": "", + "today": "", + "yesterday": "" + }, + "where": "" + }, "recording": { "events": "Eventi", "seek": "Cercare" diff --git a/src/localize/languages/pt-BR.json b/src/localize/languages/pt-BR.json index f93707a5..24b125b3 100644 --- a/src/localize/languages/pt-BR.json +++ b/src/localize/languages/pt-BR.json @@ -3,10 +3,7 @@ "frigate_card": "Cartão Frigate", "frigate_card_description": "Um cartão da Lovelace para usar com Frigate", "live": "Ao Vivo", - "no_clip": "Sem clip recente", - "no_clips": "Sem clips", - "no_snapshot": "Sem snapshot recente", - "no_snapshots": "Sem snapshots", + "no_media": "", "recordings": "Gravações", "version": "Versão" }, @@ -81,6 +78,15 @@ "window_seconds": "A duração padrão da visualização da linha do tempo em segundos" }, "controls": { + "filter": { + "editor_label": "", + "mode": "", + "modes": { + "none": "", + "left": "", + "right": "" + } + }, "next_previous": { "editor_label": "", "size": "Tamanho de controle próximo e anterior", @@ -303,7 +309,6 @@ "could_not_render_elements": "Não foi possível renderizar os elementos da imagem", "could_not_resolve": "Não foi possível resolver o URL de mídia", "diagnostics": "Diagnósticos do cartão. Revise as informações confidenciais antes de compartilhar", - "download_no_event_id": "Não foi possível extrair o Frigate ID do evento da mídia", "download_no_media": "Nenhuma mídia para download", "download_sign_failed": "Não foi possível assinar o URL de mídia para download", "duplicate_camera_id": "Duplique o ID da câmera Frigate para a câmera a seguir, use o parâmetro 'id' para identificar exclusivamente as câmeras", @@ -339,6 +344,33 @@ "start": "Início", "seek": "Procurar" }, + "media_filter": { + "all": "", + "camera": "", + "favorite": "", + "media_type": "", + "media_types": { + "clips": "", + "snapshots": "", + "recordings": "" + }, + "not_favorite": "", + "select_camera": "", + "select_favorite": "", + "select_media_type": "", + "select_what": "", + "select_when": "", + "select_where": "", + "what": "", + "when": "", + "whens": { + "past_month": "", + "past_week": "", + "today": "", + "yesterday": "" + }, + "where": "" + }, "recording": { "events": "Eventos", "seek": "Procurar" diff --git a/src/scoped-elements/gr-select.js b/src/scoped-elements/gr-select.js new file mode 100644 index 00000000..ac71fe58 --- /dev/null +++ b/src/scoped-elements/gr-select.js @@ -0,0 +1,24 @@ +import { GrSelect } from '@graphiteds/core/components/gr-select'; +import { GrMenuItem } from '@graphiteds/core/components/gr-menu-item'; + +// It was difficult to find a multi-select web component that matches these criteria: +// - Open source. +// - Supports being in a ScopedRegistry out of the box (i.e. does not auto-register with customElements). +// - Looks attractive / compatible with mostly Material elements. +// - Styleable +// - Does not bloat output size considerably. + +// Web components evaluated (https://open-wc.org/guides/community/component-libraries/): +// - Material: No multiselect component. +// - Freshwords/@crayon: Considerable bloat in output due to i18n translations +// that are used by _other_ components. +// - Carbon Design System: Workable, but less moderm / Material-like. +// - UI5: Auto-registers globally. +// - Vaadin: Auto-registers globally. +// - Liquid: Not open source. +// - [Many others]: No multiselect component. + +export const grSelectElements = { + 'gr-select': GrSelect, + 'gr-menu-item': GrMenuItem, +}; diff --git a/src/scss/dotdotdot.scss b/src/scss/dotdotdot.scss new file mode 100644 index 00000000..784a6b45 --- /dev/null +++ b/src/scss/dotdotdot.scss @@ -0,0 +1,23 @@ +.dotdotdot:after { + @keyframes dots { + 0%, + 20% { + content: '.'; + } + 40% { + content: '..'; + } + 60% { + content: '...'; + } + 90%, + 100% { + content: ''; + } + } + + animation: dots 2s linear infinite; + content: ''; + display: inline-block; + width: 3em; +} diff --git a/src/scss/gallery.scss b/src/scss/gallery.scss index ad22eb9d..56e1be5d 100644 --- a/src/scss/gallery.scss +++ b/src/scss/gallery.scss @@ -14,7 +14,7 @@ display: grid; grid-template-columns: repeat(var(--frigate-card-gallery-columns), minmax(0, 1fr)); - grid-auto-rows: 1fr; + grid-auto-rows: min-content; gap: var(--frigate-card-gallery-gap); } @@ -23,36 +23,6 @@ display: none; } -:host ha-card { - display: flex; - justify-content: center; - align-items: center; - box-sizing: border-box; - text-align: center; - color: var(--primary-text-color, white); - border: 1px solid var(--primary-color); - border-radius: var(--frigate-card-css-border-radius, var(--ha-card-border-radius, 4px)); - - // Folder background color should match the thumbnail element background - // color. - background-color: var(--primary-background-color, black); - padding: 10px; - line-height: 1; - overflow: hidden; - - // Some themes override these values (with high CSS specificity) which is - // intended for full cards rather than internal ones. Since we use multiple of - // these for folders, we cannot use an ID and need to resort to !important. - margin: 0px !important; - box-shadow: none !important; - - transition: transform 0.2s linear; -} -ha-card:hover { - transform: scale(1.04); -} - -ha-card, frigate-card-thumbnail { height: 100%; max-height: var(--frigate-card-thumbnail-size); @@ -60,3 +30,7 @@ frigate-card-thumbnail { frigate-card-thumbnail:not([details]) { width: 100%; } +frigate-card-progress-indicator { + // Progress indicator spans the grid. + grid-column: 1 / -1; +} \ No newline at end of file diff --git a/src/scss/media-filter.scss b/src/scss/media-filter.scss new file mode 100644 index 00000000..46ad7beb --- /dev/null +++ b/src/scss/media-filter.scss @@ -0,0 +1,24 @@ +:host { + display: flex; + + flex-direction: column; + overflow: auto; + + // Hide scrollbar: Firefox + scrollbar-width: none; + // Hide scrollbar: IE and Edge + -ms-overflow-style: none; + + height: 100%; + width: 300px; + margin: 5px; +} + +/* Hide scrollbar for Chrome, Safari and Opera */ +:host::-webkit-scrollbar { + display: none; +} + +frigate-card-select { + padding: 5px; +} \ No newline at end of file diff --git a/src/scss/message.scss b/src/scss/message.scss index 953c9331..ff52434d 100644 --- a/src/scss/message.scss +++ b/src/scss/message.scss @@ -1,3 +1,5 @@ +@use 'dotdotdot.scss'; + :host { min-height: 100%; width: 100%; @@ -56,28 +58,4 @@ div.message div.icon { .message ha-icon, ha-circular-progress { padding: 10px; -} - -.dotdotdot:after { - @keyframes dots { - 0%, - 20% { - content: '.'; - } - 40% { - content: '..'; - } - 60% { - content: '...'; - } - 90%, - 100% { - content: ''; - } - } - - animation: dots 2s linear infinite; - content: ''; - display: inline-block; - width: 3em; -} +} \ No newline at end of file diff --git a/src/scss/select.scss b/src/scss/select.scss new file mode 100644 index 00000000..ad238004 --- /dev/null +++ b/src/scss/select.scss @@ -0,0 +1,7 @@ +@use '@graphiteds/core/css/core.css'; + +:host { + // The graphite css (above) loads variables into :root, which is lost in the + // shadow DOM, so copy them into the host. + @extend :root; +} diff --git a/src/types.ts b/src/types.ts index fd2a01e4..9f1d433a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -27,6 +27,7 @@ export const THUMBNAIL_WIDTH_MIN = 75; */ export type ClipsOrSnapshots = 'clips' | 'snapshots'; +export type ClipsOrSnapshotsOrAll = 'clips' | 'snapshots' | 'all'; export const FRIGATE_CARD_VIEWS_USER_SPECIFIED = [ 'live', @@ -63,7 +64,7 @@ const FRIGATE_MENU_STYLES = [ const FRIGATE_MENU_POSITIONS = ['left', 'right', 'top', 'bottom'] as const; const FRIGATE_MENU_ALIGNMENTS = FRIGATE_MENU_POSITIONS; -export const FRIGATE_MENU_PRIORITY_DEFAULT = 50; +const FRIGATE_MENU_PRIORITY_DEFAULT = 50; export const FRIGATE_MENU_PRIORITY_MAX = 100; const LIVE_PROVIDERS = ['auto', 'image', 'ha', 'frigate-jsmpeg', 'webrtc-card'] as const; @@ -130,7 +131,7 @@ const actionBaseSchema = z.object({ // HA accepts either a boolean or a ConfirmationRestrictionConfig object. // `custom-card-helpers` currently only supports the latter. For maximum // compatibility, this card supports what HA supports. -export interface ExtendedConfirmationRestrictionConfig { +interface ExtendedConfirmationRestrictionConfig { confirmation?: boolean | ConfirmationRestrictionConfig; } @@ -379,7 +380,7 @@ const customSchema = z /** * Camera configuration section */ -export const cameraConfigDefault = { +const cameraConfigDefault = { live_provider: 'auto' as const, frigate: { client_id: 'frigate' as const, @@ -459,12 +460,12 @@ const menuBaseSchema = z.object({ icon: z.string().optional(), }); -export const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({ +const menuIconSchema = menuBaseSchema.merge(iconSchema).extend({ type: z.literal('custom:frigate-card-menu-icon'), }); export type MenuIcon = z.infer; -export const menuStateIconSchema = menuBaseSchema +const menuStateIconSchema = menuBaseSchema .merge(stateIconSchema) .extend({ type: z.literal('custom:frigate-card-menu-state-icon'), @@ -572,7 +573,6 @@ const pictureElementSchema = z.union([ conditionalSchema, customSchema, ]); -export type PictureElement = z.infer; const pictureElementsSchema = pictureElementSchema.array().optional(); export type PictureElements = z.infer; @@ -642,7 +642,7 @@ const viewConfigSchema = z * Image view configuration section. */ -export const IMAGE_MODES = ['screensaver', 'camera', 'url'] as const; +const IMAGE_MODES = ['screensaver', 'camera', 'url'] as const; const imageConfigDefault = { mode: 'url' as const, refresh_seconds: 0, @@ -661,13 +661,30 @@ export type ImageViewConfig = z.infer; /** * Thumbnail controls configuration section. */ +const thumbnailControlsDefaults = { + mode: 'right' as const, + size: 100, + show_details: true, + show_favorite_control: true, + show_timeline_control: true, +}; const thumbnailsControlSchema = z.object({ - mode: z.enum(['none', 'above', 'below', 'left', 'right']), - size: z.number().min(THUMBNAIL_WIDTH_MIN).max(THUMBNAIL_WIDTH_MAX).optional(), - show_details: z.boolean().optional(), - show_favorite_control: z.boolean().optional(), - show_timeline_control: z.boolean().optional(), + mode: z + .enum(['none', 'above', 'below', 'left', 'right']) + .default(thumbnailControlsDefaults.mode), + size: z + .number() + .min(THUMBNAIL_WIDTH_MIN) + .max(THUMBNAIL_WIDTH_MAX) + .default(thumbnailControlsDefaults.size), + show_details: z.boolean().default(thumbnailControlsDefaults.show_details), + show_favorite_control: z + .boolean() + .default(thumbnailControlsDefaults.show_favorite_control), + show_timeline_control: z + .boolean() + .default(thumbnailControlsDefaults.show_timeline_control), }); export type ThumbnailsControlConfig = z.infer; @@ -683,8 +700,6 @@ const timelineCoreConfigDefault = { }; const timelineMediaSchema = z.enum(['all', 'clips', 'snapshots']); -export type TimelineMedia = z.infer; - const timelineCoreConfigSchema = z.object({ clustering_threshold: z .number() @@ -752,6 +767,11 @@ const liveImageConfigDefault = { refresh_seconds: 1, }; +const liveThumbnailControlsDefaults = { + ...thumbnailControlsDefaults, + media: 'all' as const, +}; + const liveConfigDefault = { auto_play: 'all' as const, auto_pause: 'never' as const, @@ -769,14 +789,7 @@ const liveConfigDefault = { size: 48, style: 'chevrons' as const, }, - thumbnails: { - media: 'clips' as const, - size: 100, - show_details: true, - show_favorite_control: true, - show_timeline_control: true, - mode: 'left' as const, - }, + thumbnails: liveThumbnailControlsDefaults, timeline: miniTimelineConfigDefault, title: { mode: 'popup-bottom-right' as const, @@ -785,6 +798,12 @@ const liveConfigDefault = { }, }; +const livethumbnailsControlSchema = thumbnailsControlSchema.extend({ + media: z + .enum(['all', 'clips', 'snapshots']) + .default(liveConfigDefault.controls.thumbnails.media), +}); + const liveImageConfigSchema = z.object({ refresh_seconds: z.number().min(0).default(liveConfigDefault.image.refresh_seconds), }); @@ -834,30 +853,9 @@ const liveOverridableConfigSchema = z ), }) .default(liveConfigDefault.controls.next_previous), - thumbnails: thumbnailsControlSchema - .extend({ - mode: thumbnailsControlSchema.shape.mode.default( - liveConfigDefault.controls.thumbnails.mode, - ), - size: thumbnailsControlSchema.shape.size.default( - liveConfigDefault.controls.thumbnails.size, - ), - show_details: thumbnailsControlSchema.shape.show_details.default( - liveConfigDefault.controls.thumbnails.show_details, - ), - show_favorite_control: - thumbnailsControlSchema.shape.show_favorite_control.default( - liveConfigDefault.controls.thumbnails.show_favorite_control, - ), - show_timeline_control: - thumbnailsControlSchema.shape.show_timeline_control.default( - liveConfigDefault.controls.thumbnails.show_timeline_control, - ), - media: z - .enum(['clips', 'snapshots']) - .default(liveConfigDefault.controls.thumbnails.media), - }) - .default(liveConfigDefault.controls.thumbnails), + thumbnails: livethumbnailsControlSchema.default( + liveConfigDefault.controls.thumbnails, + ), timeline: miniTimelineConfigSchema.default(liveConfigDefault.controls.timeline), title: titleControlConfigSchema .extend({ @@ -994,13 +992,7 @@ const viewerConfigDefault = { size: 48, style: 'thumbnails' as const, }, - thumbnails: { - size: 100, - show_details: true, - show_favorite_control: true, - show_timeline_control: true, - mode: 'left' as const, - }, + thumbnails: thumbnailControlsDefaults, timeline: miniTimelineConfigDefault, title: { mode: 'popup-bottom-right' as const, @@ -1016,9 +1008,6 @@ const viewerNextPreviousControlConfigSchema = nextPreviousControlConfigSchema.ex viewerConfigDefault.controls.next_previous.size, ), }); -export type ViewerNextPreviousControlConfig = z.infer< - typeof viewerNextPreviousControlConfigSchema ->; const viewerConfigSchema = z .object({ @@ -1047,27 +1036,9 @@ const viewerConfigSchema = z next_previous: viewerNextPreviousControlConfigSchema.default( viewerConfigDefault.controls.next_previous, ), - thumbnails: thumbnailsControlSchema - .extend({ - mode: thumbnailsControlSchema.shape.mode.default( - viewerConfigDefault.controls.thumbnails.mode, - ), - size: thumbnailsControlSchema.shape.size.default( - viewerConfigDefault.controls.thumbnails.size, - ), - show_details: thumbnailsControlSchema.shape.show_details.default( - viewerConfigDefault.controls.thumbnails.show_details, - ), - show_favorite_control: - thumbnailsControlSchema.shape.show_favorite_control.default( - viewerConfigDefault.controls.thumbnails.show_favorite_control, - ), - show_timeline_control: - thumbnailsControlSchema.shape.show_timeline_control.default( - viewerConfigDefault.controls.thumbnails.show_timeline_control, - ), - }) - .default(viewerConfigDefault.controls.thumbnails), + thumbnails: thumbnailsControlSchema.default( + viewerConfigDefault.controls.thumbnails, + ), timeline: miniTimelineConfigSchema.default( viewerConfigDefault.controls.timeline, ), @@ -1092,41 +1063,38 @@ export type ViewerConfig = z.infer; /** * Event gallery configuration section (clips, snapshots). */ +const galleryThumbnailControlsDefaults = { + ...thumbnailControlsDefaults, + show_details: false, +}; + const galleryConfigDefault = { controls: { - thumbnails: { - size: 100, - show_details: false, - show_favorite_control: true, - show_timeline_control: true, + thumbnails: galleryThumbnailControlsDefaults, + filter: { + mode: 'right' as const, }, }, }; +const gallerythumbnailsControlSchema = thumbnailsControlSchema.extend({ + show_details: z.boolean().default(galleryThumbnailControlsDefaults.show_details), +}); + const galleryConfigSchema = z .object({ controls: z .object({ - thumbnails: thumbnailsControlSchema - // Gallery shows thumbnails "centrally" so no need for the mode. - .omit({ mode: true }) - .extend({ - size: thumbnailsControlSchema.shape.size.default( - galleryConfigDefault.controls.thumbnails.size, - ), - show_details: thumbnailsControlSchema.shape.show_details.default( - galleryConfigDefault.controls.thumbnails.show_details, - ), - show_favorite_control: - thumbnailsControlSchema.shape.show_favorite_control.default( - galleryConfigDefault.controls.thumbnails.show_favorite_control, - ), - show_timeline_control: - thumbnailsControlSchema.shape.show_timeline_control.default( - galleryConfigDefault.controls.thumbnails.show_timeline_control, - ), + thumbnails: gallerythumbnailsControlSchema.default( + galleryConfigDefault.controls.thumbnails, + ), + filter: z + .object({ + mode: z + .enum(['none', 'left', 'right']) + .default(galleryConfigDefault.controls.filter.mode), }) - .default(galleryConfigDefault.controls.thumbnails), + .default(galleryConfigDefault.controls.filter), }) .default(galleryConfigDefault.controls), }) @@ -1166,13 +1134,7 @@ const dimensionsConfigSchema = z const timelineConfigDefault = { ...timelineCoreConfigDefault, controls: { - thumbnails: { - mode: 'left' as const, - size: 100, - show_details: true, - show_favorite_control: true, - show_timeline_control: true, - }, + thumbnails: thumbnailControlsDefaults, }, }; @@ -1180,27 +1142,9 @@ const timelineConfigSchema = timelineCoreConfigSchema .extend({ controls: z .object({ - thumbnails: thumbnailsControlSchema - .extend({ - mode: thumbnailsControlSchema.shape.mode.default( - timelineConfigDefault.controls.thumbnails.mode, - ), - size: thumbnailsControlSchema.shape.size.default( - timelineConfigDefault.controls.thumbnails.size, - ), - show_details: thumbnailsControlSchema.shape.show_details.default( - timelineConfigDefault.controls.thumbnails.show_details, - ), - show_favorite_control: - thumbnailsControlSchema.shape.show_favorite_control.default( - timelineConfigDefault.controls.thumbnails.show_favorite_control, - ), - show_timeline_control: - thumbnailsControlSchema.shape.show_timeline_control.default( - timelineConfigDefault.controls.thumbnails.show_timeline_control, - ), - }) - .default(timelineConfigDefault.controls.thumbnails), + thumbnails: thumbnailsControlSchema.default( + timelineConfigDefault.controls.thumbnails, + ), }) .default(timelineConfigDefault.controls), }) @@ -1269,8 +1213,20 @@ const performanceConfigSchema = z .default(performanceConfigDefault); export type PerformanceConfig = z.infer; +const debugConfigDefault = { + logging: false, +}; + +const debugConfigSchema = z + .object({ + logging: z.boolean().default(debugConfigDefault.logging), + }) + .default(debugConfigDefault); +type DebugConfig = z.infer; + export interface CardWideConfig { performance?: PerformanceConfig; + debug?: DebugConfig; } /** @@ -1289,6 +1245,7 @@ export const frigateCardConfigSchema = z.object({ dimensions: dimensionsConfigSchema, timeline: timelineConfigSchema, performance: performanceConfigSchema, + debug: debugConfigSchema, // Configuration overrides. overrides: overridesSchema, @@ -1314,6 +1271,7 @@ export const frigateCardConfigDefaults = { image: imageConfigDefault, timeline: timelineConfigDefault, performance: performanceConfigDefault, + debug: debugConfigDefault, }; const menuButtonSchema = z.discriminatedUnion('type', [ @@ -1330,48 +1288,6 @@ export interface ExtendedHomeAssistant extends HomeAssistant { }; } -export interface BrowseMediaQueryParameters { - // ======================================== - // Parameters used to construct media query - // ======================================== - mediaType?: 'clips' | 'snapshots'; - clientId: string; - cameraName: string; - label?: string; - zone?: string; - before?: number; - after?: number; - unlimited?: boolean; - - // ======================================== - // Parameters used to differentiate results - // ======================================== - // Optional title to be used for separating results when merging multiple - // sets of results. See `mergeFrigateBrowseMediaSources()` . - title?: string; - - // Optional camera-id to which this query is associated. May be used to map - // results to a particular camera within the card. - cameraID?: string; -} - -export interface BrowseRecordingQueryParameters { - clientId: string; - cameraName: string; - year: number; - month: number; - day: number; - hour: number; -} - -export interface BrowseMediaNeighbors { - previous: FrigateBrowseMediaSource | null; - previousIndex: number | null; - - next: FrigateBrowseMediaSource | null; - nextIndex: number | null; -} - export interface MediaLoadedInfo { width: number; height: number; @@ -1424,83 +1340,6 @@ export interface CardHelpers { * Home Assistant API types. */ -export const MEDIA_CLASS_PLAYLIST = 'playlist' as const; -export const MEDIA_CLASS_VIDEO = 'video' as const; -export const MEDIA_TYPE_PLAYLIST = 'playlist' as const; -export const MEDIA_TYPE_IMAGE = 'image' as const; -export const MEDIA_TYPE_VIDEO = 'video' as const; - -// Recursive type, cannot use type interference: -// See: https://github.com/colinhacks/zod#recursive-types -// -// Server side data-type defined here: https://github.com/home-assistant/core/blob/dev/homeassistant/components/media_player/browse_media.py#L46 -interface BrowseMediaSource { - title: string; - media_class: string; - media_content_type: string; - media_content_id: string; - can_play: boolean; - can_expand: boolean; - children_media_class?: string | null; - thumbnail: string | null; - children?: BrowseMediaSource[] | null; -} - -export interface FrigateRecording { - // Frigate camera name (may not be unique) - camera: string; - start_time: number; - end_time: number; - events: number; -} - -export interface FrigateBrowseMediaSource extends BrowseMediaSource { - children?: FrigateBrowseMediaSource[] | null; - frigate?: { - event?: FrigateEvent; - recording?: FrigateRecording; - cameraID?: string; - }; -} - -export const frigateEventSchema = z.object({ - camera: z.string(), - end_time: z.number().nullable(), - false_positive: z.boolean().nullable(), - has_clip: z.boolean(), - has_snapshot: z.boolean(), - id: z.string(), - label: z.string(), - start_time: z.number(), - top_score: z.number(), - zones: z.string().array(), - retain_indefinitely: z.boolean().optional(), -}); -export type FrigateEvent = z.infer; - -export const frigateEventsSchema = frigateEventSchema.array(); -export type FrigateEvents = z.infer; - -export const frigateBrowseMediaSourceSchema: z.ZodSchema = z.lazy( - () => - z.object({ - title: z.string(), - media_class: z.string(), - media_content_type: z.string(), - media_content_id: z.string(), - can_play: z.boolean(), - can_expand: z.boolean(), - children_media_class: z.string().nullable().optional(), - thumbnail: z.string().nullable(), - children: z.array(frigateBrowseMediaSourceSchema).nullable().optional(), - frigate: z - .object({ - event: frigateEventSchema, - }) - .optional(), - }), -); - // Server side data-type defined here: https://github.com/home-assistant/core/blob/dev/homeassistant/components/media_source/models.py export const resolvedMediaSchema = z.object({ url: z.string(), @@ -1513,7 +1352,7 @@ export const signedPathSchema = z.object({ }); export type SignedPath = z.infer; -export const entitySchema = z.object({ +const entitySchema = z.object({ config_entry_id: z.string().nullable(), disabled_by: z.string().nullable(), entity_id: z.string(), diff --git a/src/utils/basic.ts b/src/utils/basic.ts index a9f93620..dc89e6d8 100644 --- a/src/utils/basic.ts +++ b/src/utils/basic.ts @@ -1,10 +1,15 @@ +import differenceInHours from 'date-fns/differenceInHours'; +import differenceInMinutes from 'date-fns/differenceInMinutes'; +import differenceInSeconds from 'date-fns/differenceInSeconds'; import format from 'date-fns/format'; import isEqual from 'lodash-es/isEqual'; import { FrigateCardError } from '../types'; +export type ModifyInterface = Omit & R; + /** * Dispatch a Frigate Card event. - * @param element The element to send the event. + * @param target The target from which send the event. * @param name The name of the Frigate card event to send. * @param detail An optional detail object to attach. */ @@ -27,6 +32,8 @@ export function dispatchFrigateCardEvent( * @param input The input Frigate (camera/label/zone) name. * @returns A prettified name. */ +export function prettifyTitle(input: string): string; +export function prettifyTitle(input?: string): string | undefined; export function prettifyTitle(input?: string): string | undefined { if (!input) { return undefined; @@ -51,6 +58,24 @@ export function arrayMove(target: unknown[], from: number, to: number): void { target.splice(to, 0, element); } +/** + * Convert a value to an array if it is not already one. + * @param value: A value (which may be an array). + * @returns An array. + */ +export const arrayify = (value: T | T[]): T[] => { + return Array.isArray(value) ? value : [value]; +}; + +/** + * Convert a value to an set if it is not already one. + * @param value: A value (which may be a set, an array or a T) + * @returns A set of T. + */ +export const setify = (value: T | T[] | Set): Set => { + return value instanceof Set ? value : new Set(arrayify(value)); +}; + /** * Determine if the contents of the n(ew) and o(ld) values have changed. For use * in lit web components that may have a value that changes address but not @@ -68,10 +93,7 @@ export function contentsChanged(n: unknown, o: unknown): boolean { * @param e The Error object. * @param func The Console func to call. */ -export function errorToConsole(e: Error, func?: CallableFunction): void { - if (!func) { - func = console.warn; - } +export function errorToConsole(e: Error, func: CallableFunction = console.warn): void { if (e instanceof FrigateCardError && e.context) { func(e, e.context); } else { @@ -83,9 +105,8 @@ export function errorToConsole(e: Error, func?: CallableFunction): void { * Determine if the device supports hovering. * @returns `true` if the device supports hovering, `false` otherwise. */ -export const isHoverableDevice = (): boolean => window.matchMedia( - '(hover: hover) and (pointer: fine)', -).matches; +export const isHoverableDevice = (): boolean => + window.matchMedia('(hover: hover) and (pointer: fine)').matches; /** * Format a date object to RFC3339. @@ -94,7 +115,16 @@ export const isHoverableDevice = (): boolean => window.matchMedia( */ export const formatDateAndTime = (date: Date): string => { return format(date, 'yyyy-MM-dd HH:mm'); -} +}; + +/** + * Format a date object to RFC3339. + * @param date A Date object. + * @returns A date. + */ +export const formatDate = (date: Date): string => { + return format(date, 'yyyy-MM-dd'); +}; /** * Run a function in idle periods. If idle callbacks are not supported (e.g. @@ -105,9 +135,59 @@ export const formatDateAndTime = (date: Date): string => { export const runWhenIdleIfSupported = (func: () => void, timeout?: number): void => { if (window.requestIdleCallback) { window.requestIdleCallback(func, { - ...(timeout && { timeout: timeout}) + ...(timeout && { timeout: timeout }), }); } else { func(); } -} \ No newline at end of file +}; + +/** + * Convenience function to return a string representing the difference in hours, + * minutes and seconds between two dates. Heavily inspired by, and returning the + * same format as, the Frigate UI: + * https://github.com/blakeblackshear/frigate/blob/master/web/src/components/RecordingPlaylist.jsx#L97 + * @param start The start date. + * @param end The end date. + * @returns A duration string. + */ +export function getDurationString(start: Date, end: Date): string { + const hours = differenceInHours(end, start); + const minutes = differenceInMinutes(end, start) - hours * 60; + const seconds = differenceInSeconds(end, start) - hours * 60 * 60 - minutes * 60; + let duration = ''; + + if (hours) { + duration += `${hours}h `; + } + if (minutes) { + duration += `${minutes}m `; + } + duration += `${seconds}s`; + return duration; +} + +export const allPromises = async ( + items: T[], + func: (arg: T) => void, +): Promise => { + await Promise.all(Array.from(items).map((item) => func(item))); +}; + +/** + * Simple efficient YYYY-MM-DD -> date converter. + */ +export const dayToDate = (day: string): Date => { + // Must provide the hour:minute:second on parsing or Javascript will assume + // *UTC* midnight. + return new Date(`${day}T00:00:00`); +}; + +export const isSuperset = (superset: Set, subset: Set) => { + for (const item of subset) { + if (!superset.has(item)) { + return false; + } + } + return true; +}; diff --git a/src/utils/camera.ts b/src/utils/camera.ts index 3dd747e6..bcc19710 100644 --- a/src/utils/camera.ts +++ b/src/utils/camera.ts @@ -1,7 +1,4 @@ -import { HomeAssistant } from 'custom-card-helpers'; import { CameraConfig, RawFrigateCardConfig } from '../types.js'; -import { prettifyTitle } from './basic.js'; -import { getEntityIcon, getEntityTitle } from './ha'; /** * Get a camera id. @@ -26,53 +23,6 @@ export function getCameraID( ); } -/** - * Get a camera text title. - * @param hass The Home Assistant object. - * @param config The camera config (either parsed or raw). - * @returns A title string. - */ -export function getCameraTitle( - hass?: HomeAssistant, - config?: CameraConfig | RawFrigateCardConfig | null, -): string { - // Attempt to render a recognizable name for the camera, - // starting with the most likely to be useful and working our - // ways towards the least useful. Extra type checking here since this is also - // used on raw configuration in the editor. - return ( - (typeof config?.title === 'string' && config.title) || - (typeof config?.camera_entity === 'string' - ? getEntityTitle(hass, config.camera_entity) - : '') || - (typeof config?.webrtc_card === 'object' && - config.webrtc_card && - typeof config.webrtc_card['entity'] === 'string' && - config.webrtc_card['entity']) || - (typeof config?.frigate === 'object' && - config.frigate && - typeof config?.frigate['camera_name'] === 'string' && - config.frigate['camera_name'] - ? prettifyTitle(config.frigate['camera_name']) - : '') || - (typeof config?.id === 'string' && config.id) || - '' - ); -} - -/** - * Get a camera icon. - * @param hass The Home Assistant object. - * @param config The camera config. - * @returns An icon string. - */ -export function getCameraIcon( - hass?: HomeAssistant, - config?: CameraConfig | null, -): string { - return config?.icon || getEntityIcon(hass, config?.camera_entity) || 'mdi:video'; -} - /** * Get all cameras that depend on a given camera. * @param cameras Cameras map. @@ -81,13 +31,13 @@ export function getCameraIcon( */ export const getAllDependentCameras = ( cameras: Map, - camera?: string, + cameraID?: string, ): Set => { const cameraIDs: Set = new Set(); - const getDependentCameras = (camera: string): void => { - const cameraConfig = cameras.get(camera); + const getDependentCameras = (cameraID: string): void => { + const cameraConfig = cameras.get(cameraID); if (cameraConfig) { - cameraIDs.add(camera); + cameraIDs.add(cameraID); const dependentCameras: Set = new Set(); (cameraConfig.dependencies.cameras || []).forEach((item) => dependentCameras.add(item), @@ -102,39 +52,8 @@ export const getAllDependentCameras = ( } } }; - if (camera) { - getDependentCameras(camera); + if (cameraID) { + getDependentCameras(cameraID); } return cameraIDs; }; - -/** - * Return the cameraIDs of truly unique cameras (some configured cameras may be - * the same Frigate came but with different zone/labels). - * @param cameras The full set of cameras. - * @param cameraIDs The specific IDs to dedup. - */ -export const getTrueCameras = ( - cameras: Map, - cameraIDs: Set, -): Set => { - const getTrueCameraID = (cameraConfig: CameraConfig): string => { - return `${cameraConfig.frigate?.client_id ?? ''}/${ - cameraConfig.frigate.camera_name ?? '' - }`; - }; - - const output = new Set(); - const visitedTrueCameras = new Set(); - cameraIDs.forEach((cameraID: string) => { - const cameraConfig = cameras.get(cameraID) ?? null; - if (cameraConfig && cameraConfig.frigate.camera_name) { - const trueCameraID = getTrueCameraID(cameraConfig); - if (!visitedTrueCameras.has(trueCameraID)) { - output.add(cameraID); - visitedTrueCameras.add(trueCameraID); - } - } - }); - return output; -}; diff --git a/src/utils/data-manager.ts b/src/utils/data-manager.ts deleted file mode 100644 index 4eda65c9..00000000 --- a/src/utils/data-manager.ts +++ /dev/null @@ -1,540 +0,0 @@ -import { HomeAssistant } from 'custom-card-helpers'; -import { DataSet, DataView } from 'vis-data/esnext'; -import type { IdType, TimelineItem } from 'vis-timeline/esnext'; -import { CAMERA_BIRDSEYE } from '../const.js'; -import { - CameraConfig, - ExtendedHomeAssistant, - FrigateCardError, - FrigateEvent, - FrigateEvents, -} from '../types.js'; -import { errorToConsole, runWhenIdleIfSupported } from './basic.js'; -import { - FrigateGetEventsParameters, - getEventsMultiple, - getRecordingSegments, - getRecordingsSummary, - RecordingSegments, - RecordingSummary, -} from './frigate.js'; -import { dispatchFrigateCardErrorEvent } from '../components/message.js'; -import fromUnixTime from 'date-fns/fromUnixTime'; -import throttle from 'lodash-es/throttle'; - -const RECORDING_SEGMENT_TOLERANCE = 60; -const DATA_MANAGER_MAX_AGE_SECONDS = 10; -const DATA_MANAGER_MAX_FETCH_COUNT = 10000; - -export interface FrigateCardTimelineItem extends TimelineItem { - // DataView has issues using datasets with Date objects, so avoid them and use - // numbers instead. - start: number; - end?: number; - event?: FrigateEvent; -} - -type TimelineMediaType = 'all' | 'clips' | 'snapshots'; - -export interface RecordingSegmentsItem { - id: string; - cameraID: string; - start: number; - end: number; -} - -/** - * Sort the timeline items most recent to least recent. - * @param a The first item. - * @param b The second item. - * @returns -1, 0, 1 (standard array sort function configuration). - */ -export const sortYoungestToOldest = ( - a: RecordingSegmentsItem | FrigateCardTimelineItem, - b: RecordingSegmentsItem | FrigateCardTimelineItem, -): number => { - if (a.start < b.start) { - return 1; - } - if (a.start > b.start) { - return -1; - } - return 0; -}; - -/** - * Sort the segments least recent to most recent. - * @param a The first item. - * @param b The second item. - * @returns -1, 0, 1 (standard array sort function configuration). - */ -export const sortOldestToYoungest = ( - a: RecordingSegmentsItem | FrigateCardTimelineItem, - b: RecordingSegmentsItem | FrigateCardTimelineItem, -): number => { - if (a.start < b.start) { - return -1; - } - if (a.start > b.start) { - return 1; - } - return 0; -}; - -/** - * A manager to maintain/fetch timeline events. - */ -export class DataManager { - protected _recordingSummary: Map = new Map(); - protected _recordingSegments = new DataSet(); - - protected _dataset = new DataSet(); - - // The earliest date managed. - protected _dateStart: Date | null = null; - - // The latest date managed. - protected _dateEnd: Date | null = null; - - // The last fetch date. - protected _dateFetch: Date | null = null; - - // The maximum allowable age of fetch data (will not fetch more frequently - // than this). - protected _maxAgeSeconds: number = DATA_MANAGER_MAX_AGE_SECONDS; - - protected _cameras: Map; - - // Garbage collect segments at most once an hour. - protected _throttledSegmentGarbageCollector = throttle( - () => { - runWhenIdleIfSupported(this._garbageCollectSegments.bind(this)); - }, - 60 * 60 * 1000, - { trailing: true }, - ); - - constructor(cameras: Map) { - this._cameras = cameras; - } - - // Get the last event fetch date. - get lastFetchDate(): Date | null { - return this._dateFetch ?? null; - } - - public getRecordingSummaryForCamera(cameraID: string): RecordingSummary | null { - return this._recordingSummary.get(cameraID) ?? null; - } - - /** - * Create a dataview for a given set of camera. - * @param cameraIDs The cameraIDs to include. - * @param showRecordings Whether or not to show recordings. - * @returns A dataview. - */ - public createDataView( - cameraIDs: Set, - showRecordings: boolean, - mediaType: TimelineMediaType, - ): DataView { - return new DataView(this._dataset, { - filter: (item: FrigateCardTimelineItem) => - // Only return items for the given cameras. - !!item.group && - cameraIDs.has(String(item.group)) && - // Don't return recordings if the user does not want them. - (showRecordings || item.type !== 'background') && - // Don't return events that are the wrong media type. - (item.type === 'background' || - mediaType === 'all' || - (mediaType === 'clips' && !!item.event?.has_clip) || - (mediaType === 'snapshots' && !!item.event?.has_snapshot)), - }); - } - - /** - * Create a dataview for segments. - * @returns A dataview. - */ - public createSegmentDataView(): DataView { - return new DataView(this._recordingSegments); - } - - /** - * Get the underlying recording segments dataset. - */ - get recordingSegments(): DataSet { - return this._recordingSegments; - } - - /** - * Rewrite an item as-is. May be useful in cases where clustering may need to - * be recalculated. - * @param id The id to rewrite. - */ - public rewriteItem(id: IdType): void { - // Hack: Clustering may not update unless the dataset changes, artifically - // update the dataset to ensure the newly selected item cannot be included - // in a cluster. - const item = this._dataset.get(id); - if (item) { - this._dataset.updateOnly(item); - } - } - - /** - * Add events for the given camera. - * @param cameraID The camera ID. - * @param events The array of events. - */ - protected _addEvents(cameraID: string, events: FrigateEvents): void { - this._dataset.update( - events.map((event) => ({ - id: event.id, - group: cameraID, - content: '', - event: event, - start: event.start_time * 1000, - type: event.end_time ? 'range' : 'point', - ...(event.end_time && { end: event.end_time * 1000 }), - })), - ); - } - - /** - * Determine if the timeline has coverage for a given range of dates. - * @param start The start of the date range. - * @param end An optional end of the date range. - * @returns - */ - public hasCoverage(now: Date, start: Date, end?: Date): boolean { - // Never fetched: no coverage. - if (!this._dateFetch || !this._dateStart || !this._dateEnd) { - return false; - } - - // If the most recent fetch is older than maxAgeSeconds: no coverage. - if ( - this._maxAgeSeconds && - now.getTime() - this._dateFetch.getTime() > this._maxAgeSeconds * 1000 - ) { - return false; - } - - // If the most requested data is earlier than the earliest stored: no - // coverage. - if (start < this._dateStart) { - return false; - } - - // If there's no end time specified: there IS coverage. - if (!end) { - return true; - } - // If the requested end time is older than the oldest requested: there IS - // coverage. - if (end.getTime() < this._dateEnd.getTime()) { - return true; - } - // If there's no maxAgeSeconds specified: no coverage. - if (!this._maxAgeSeconds) { - return false; - } - // If the requested end time is beyond `_maxAgeSeconds` of now: no coverage. - if (now.getTime() - end.getTime() > this._maxAgeSeconds * 1000) { - return false; - } - - // End time is within `_maxAgeSeconds` of the latest data: there IS - // coverage. - return end.getTime() - this._maxAgeSeconds * 1000 <= this._dateEnd.getTime(); - } - - /** - * Fetch events if no coverage in given range. - * @param element The element to send error events from. - * @param hass The HomeAssistant object. - * @param start Fetch events that start later than this date. - * @param end Fetch events that start earlier than this date. - * @returns `true` if events were fetched, `false` otherwise. - */ - public async fetchIfNecessary( - element: HTMLElement, - hass: ExtendedHomeAssistant, - start: Date, - end: Date, - ): Promise { - // Cannot fetch the future, always clip the end date to now so as to avoid - // checking for coverage that could not possibly exist yet. - const now = new Date(); - end = end > now ? now : end; - - if (this.hasCoverage(now, start, end)) { - return false; - } - - const oldStart = this._dateStart; - const oldEnd = this._dateEnd; - let segmentStart: Date | null = null; - let segmentEnd: Date | null = null; - if (!this._dateStart || start < this._dateStart) { - this._dateStart = start; - segmentStart = start; - } else { - segmentStart = oldEnd ?? end; - } - if (!this._dateEnd || end > this._dateEnd) { - this._dateEnd = end; - segmentEnd = end; - } else { - segmentEnd = oldStart ?? start; - } - - this._dateFetch = new Date(); - - await Promise.all([ - // Events are always fetched for the maximum extent of the managed - // range. This is because events may change at any point in time - // (e.g. a long-running event that ends). - this._fetchEvents(element, hass, this._dateStart, this._dateEnd), - this._fetchRecordingSummary(hass), - ...(segmentEnd > segmentStart - ? [this._fetchRecordingSegments(hass, segmentStart, segmentEnd)] - : []), - ]); - - this._throttledSegmentGarbageCollector(); - return true; - } - - /** - * Garbage collect recording segments that no longer feature in the summary. - */ - protected _garbageCollectSegments(): void { - if (!this._recordingSegments || !this._recordingSummary) { - return; - } - - // Performance: _recordingSegments is potentially very large (e.g. 10K - 1M - // items) and each item must be examined, so care required here to stick to - // nothing worse than O(n) performance. - const getHourID = (cameraID: string, day: number, hour: number): string => { - return `${cameraID}/${day}/${hour}`; - }; - - const goodHours: Set = new Set(); - for (const cameraID of this._recordingSummary.keys()) { - for (const summaryDay of this._recordingSummary?.get(cameraID) ?? []) { - for (const summaryHour of summaryDay.hours) { - goodHours.add(getHourID(cameraID, summaryDay.day.getDate(), summaryHour.hour)); - } - } - } - - const deleteIDs: string[] = []; - this._recordingSegments.forEach((item, id) => { - const startDate = fromUnixTime(item.start / 1000); - const hourID = getHourID(item.cameraID, startDate.getDate(), startDate.getHours()); - - // ~O(1) lookup time for a JS set. - if (!goodHours.has(hourID)) { - deleteIDs.push(String(id)); - } - }); - - this._recordingSegments.remove(deleteIDs); - this._compressRecordingSegmentsOntoTimeline(); - } - - /** - * Fetch recording segments for cameras. - * @param hass The HomeAssistant object. - * @param start Fetch segments that start later than this date. - * @param end Fetch segments that start earlier than this date. - */ - protected async _fetchRecordingSegments( - hass: ExtendedHomeAssistant, - start: Date, - end: Date, - ): Promise { - const results: Map = new Map(); - const fetch = async (camera: string, config?: CameraConfig): Promise => { - if (!config || !config.frigate.camera_name || !hass) { - return; - } - - try { - const cameraResults = await getRecordingSegments( - hass, - config.frigate.client_id, - config.frigate.camera_name, - end, - start, - ); - results.set(camera, cameraResults); - } catch (e) { - errorToConsole(e as Error); - } - }; - - await Promise.all( - Array.from(this._cameras.keys()).map((camera) => - fetch(camera, this._cameras.get(camera)), - ), - ); - - const items: RecordingSegmentsItem[] = []; - results.forEach((segments, cameraID) => { - segments.forEach((segment) => { - items.push({ - id: `${cameraID}/${segment.id}`, - cameraID: cameraID, - start: segment.start_time * 1000, - end: segment.end_time * 1000, - }); - }); - }); - this._recordingSegments.update(items); - this._compressRecordingSegmentsOntoTimeline(); - } - - /** - * Compress recording segments into recordings shown on the timeline - * background. - */ - protected _compressRecordingSegmentsOntoTimeline(): void { - if (!this._recordingSegments.length) { - return; - } - - // Delete all the existing background. - this._dataset.remove( - this._dataset.get({ - filter: (item) => item.type === 'background', - }), - ); - - const convertToRecording = ( - segment: RecordingSegmentsItem, - ): FrigateCardTimelineItem => { - return { - id: `recording-${segment.cameraID}-${segment.id}`, - group: segment.cameraID, - start: segment.start, - end: segment.end, - content: ' ', - type: 'background', - }; - }; - - // Iterate through the segments least to most recent, effectively joining - // segments together that are within a certain tolerance to create large - // blocks that are visualized on the timeline as recordings. - const recordings: FrigateCardTimelineItem[] = []; - - this._cameras.forEach((_, cameraID) => { - const segments = this._recordingSegments.get({ - filter: (item) => item.cameraID === cameraID, - order: sortOldestToYoungest, - }); - let current: RecordingSegmentsItem | null = null; - for (let i = 0; i < segments.length; ++i) { - const item = segments[i]; - - if (!current) { - current = { ...item }; - } else if (current.end + RECORDING_SEGMENT_TOLERANCE * 1000 >= item.start) { - current.end = item.end; - } else { - recordings.push(convertToRecording(current)); - current = null; - } - if (i === segments.length - 1 && current) { - recordings.push(convertToRecording(current)); - } - } - }); - - this._dataset.update(recordings); - } - - /** - * Fetch recording summary. - * @param hass The HomeAssistant object. - */ - protected async _fetchRecordingSummary(hass: ExtendedHomeAssistant): Promise { - const storeRecordingSummary = async ( - cameraID: string, - cameraConfig: CameraConfig, - ): Promise => { - if (!cameraConfig.frigate.camera_name) { - return; - } - try { - this._recordingSummary.set( - cameraID, - await getRecordingsSummary( - hass, - cameraConfig.frigate.client_id, - cameraConfig.frigate.camera_name, - ), - ); - } catch (e) { - // Recording failure should not disrupt the rest of the timeline - // experience. - errorToConsole(e as Error); - } - }; - - await Promise.all( - Array.from(this._cameras.keys()).map(async (cameraID) => { - const cameraConfig = this._cameras.get(cameraID); - if (cameraConfig) { - await storeRecordingSummary(cameraID, cameraConfig); - } - }), - ); - } - - /** - * Fetch events for the timeline. - * @param element The element to send error events from. - * @param hass The HomeAssistant object. - * @param start Fetch events that start later than this date. - * @param end Fetch events that start earlier than this date. - */ - protected async _fetchEvents( - element: HTMLElement, - hass: HomeAssistant, - start: Date, - end: Date, - ): Promise { - const params: Map = new Map(); - - this._cameras.forEach((cameraConfig, cameraID) => { - if ( - cameraConfig.frigate.camera_name && - cameraConfig.frigate.camera_name !== CAMERA_BIRDSEYE - ) { - params.set(cameraID, { - instance_id: cameraConfig.frigate.client_id, - camera: cameraConfig.frigate.camera_name, - ...(cameraConfig.frigate.label && { label: cameraConfig.frigate.label }), - ...(cameraConfig.frigate.zone && { label: cameraConfig.frigate.zone }), - before: Math.floor(end.getTime() / 1000), - after: Math.floor(start.getTime() / 1000), - limit: DATA_MANAGER_MAX_FETCH_COUNT, - }); - } - }); - - let results: Map; - try { - results = await getEventsMultiple(hass, params); - } catch (e) { - return dispatchFrigateCardErrorEvent(element, e as FrigateCardError); - } - results.forEach((params, cameraID) => this._addEvents(cameraID, params)); - } -} diff --git a/src/utils/debug.ts b/src/utils/debug.ts new file mode 100644 index 00000000..9316ee09 --- /dev/null +++ b/src/utils/debug.ts @@ -0,0 +1,16 @@ +import { CardWideConfig } from '../types'; + +export const log = (cardWideConfig?: CardWideConfig, ...args: unknown[]) => { + if (cardWideConfig?.debug?.logging) { + console.debug(...args); + } +}; + +/** + * For debug purposes only. + * @param seconds + */ +// ts-prune-ignore-next +export const sleep = async (seconds: number) => { + await new Promise((r) => setTimeout(r, seconds * 1000)); +}; diff --git a/src/utils/frigate.ts b/src/utils/frigate.ts deleted file mode 100644 index ade6cf3a..00000000 --- a/src/utils/frigate.ts +++ /dev/null @@ -1,309 +0,0 @@ -import { HomeAssistant } from 'custom-card-helpers'; -import utcToZonedTime from 'date-fns-tz/utcToZonedTime'; -import differenceInHours from 'date-fns/differenceInHours'; -import differenceInMinutes from 'date-fns/differenceInMinutes'; -import differenceInSeconds from 'date-fns/differenceInSeconds'; -import fromUnixTime from 'date-fns/fromUnixTime'; -import { z } from 'zod'; -import { localize } from '../localize/localize'; -import { - BrowseRecordingQueryParameters, - ClipsOrSnapshots, - ExtendedHomeAssistant, - FrigateCardError, - FrigateEvent, - FrigateEvents, - frigateEventsSchema, -} from '../types'; -import { formatDateAndTime, prettifyTitle } from './basic'; -import { homeAssistantWSRequest } from './ha'; - -export const FRIGATE_ICON_SVG_PATH = - 'm 4.8759466,22.743573 c 0.0866,0.69274 0.811811,1.16359 0.37885,1.27183 ' + - '-0.43297,0.10824 -2.32718,-3.43665 -2.7601492,-4.95202 -0.4329602,-1.51538 ' + - '-0.6764993,-3.22017 -0.5682593,-4.19434 0.1082301,-0.97417 5.7097085,-2.48955 ' + - '5.7097085,-2.89545 0,-0.4059 -1.81304,-0.0271 -1.89422,-0.35178 -0.0812,-0.32472 ' + - '1.36925,-0.12989 1.75892,-0.64945 0.60885,-0.81181 1.3800713,-0.6765 1.8671505,' + - '-1.1094696 0.4870902,-0.4329599 1.0824089,-2.0836399 1.1906589,-2.7871996 0.108241,' + - '-0.70357 -1.0824084,-1.51538 -1.4071389,-2.05658 -0.3247195,-0.54121 0.7035702,' + - '-0.92005 3.1931099,-1.94834 2.48954,-1.02829 10.39114,-3.30134994 10.49938,' + - '-3.03074994 0.10824,0.27061 -2.59779,1.40713994 -4.492,2.11069994 -1.89422,0.70357 ' + - '-4.97909,2.05658 -4.97909,2.43542 0,0.37885 0.16236,0.67651 0.0541,1.54244 -0.10824,' + - '0.86593 -0.12123,1.2702597 -0.32472,1.8400997 -0.1353,0.37884 -0.2706,1.27183 ' + - '0,2.0836295 0.21648,0.64945 0.92005,1.13653 1.24477,1.24478 0.2706,0.018 1.01746,' + - '0.0433 1.8401,0 1.02829,-0.0541 2.48954,0.0541 2.48954,0.32472 0,0.2706 -2.21894,' + - '0.10824 -2.21894,0.48708 0,0.37885 2.27306,-0.0541 2.21894,0.32473 -0.0541,0.37884 ' + - '-1.89422,0.21648 -2.86839,0.21648 -0.77933,0 -1.93031,-0.0361 -2.43542,-0.21648 ' + - 'l -0.10824,0.37884 c -0.18038,0 -0.55744,0.10824 -0.94711,0.10824 -0.48708,0 ' + - '-0.51414,0.16236 -1.40713,0.16236 -0.892989,0 -0.622391,-0.0541 -1.4341894,-0.10824 ' + - '-0.81181,-0.0541 -3.842561,2.27306 -4.383761,3.03075 -0.54121,0.75768 ' + - '-0.21649,2.59778 -0.21649,3.43665 0,0.75379 -0.10824,2.43542 0,3.30135 z'; - -const recordingSummaryHourSchema = z.object({ - hour: z.preprocess((arg) => Number(arg), z.number().min(0).max(23)), - duration: z.number().min(0), - events: z.number().min(0), -}); - -const recordingSummarySchema = z - .object({ - day: z.preprocess((arg) => { - // Must provide the hour:minute:second on parsing or Javascript will - // assume *UTC* midnight. - return typeof arg === 'string' ? new Date(`${arg}T00:00:00`) : arg; - }, z.date()), - events: z.number(), - hours: recordingSummaryHourSchema.array(), - }) - .array(); -export type RecordingSummary = z.infer; - -const recordingSegmentSchema = z.object({ - start_time: z.number(), - end_time: z.number(), - id: z.string(), -}); -const recordingSegmentsSchema = recordingSegmentSchema.array(); -export type RecordingSegments = z.infer; - -const retainResultSchema = z.object({ - success: z.boolean(), - message: z.string(), -}); -export type RetainResult = z.infer; - -/** - * Get the recordings summary. May throw. - * @param hass The Home Assistant object. - * @param client_id The Frigate client_id. - * @param camera_name The Frigate camera name. - * @returns A RecordingSummary object. - */ -export const getRecordingsSummary = async ( - hass: ExtendedHomeAssistant, - client_id: string, - camera_name: string, -): Promise => { - return await homeAssistantWSRequest( - hass, - recordingSummarySchema, - { - type: 'frigate/recordings/summary', - instance_id: client_id, - camera: camera_name, - }, - true, - ); -}; - -/** - * Get the recording segments. May throw. - * @param hass The Home Assistant object. - * @param client_id The Frigate client_id. - * @param camera_name The Frigate camera name. - * @param before The segment low watermark. - * @param after The segment high watermark. - * @returns A RecordingSegments object. - */ -export const getRecordingSegments = async ( - hass: ExtendedHomeAssistant, - client_id: string, - camera_name: string, - before: Date, - after: Date, -): Promise => { - return await homeAssistantWSRequest( - hass, - recordingSegmentsSchema, - { - type: 'frigate/recordings/get', - instance_id: client_id, - camera: camera_name, - before: Math.floor(before.getTime() / 1000), - after: Math.ceil(after.getTime() / 1000), - }, - true, - ); -}; - -/** - * Request that Frigate retain an event. May throw. - * @param hass The HomeAssistant object. - * @param client_id The Frigate client_id. - * @param eventID The event ID to retain. - * @param retain `true` to retain or `false` to unretain. - */ -export async function retainEvent( - hass: HomeAssistant, - client_id: string, - eventID: string, - retain: boolean, -): Promise { - const retainRequest = { - type: 'frigate/event/retain', - instance_id: client_id, - event_id: eventID, - retain: retain, - }; - const response = await homeAssistantWSRequest( - hass, - retainResultSchema, - retainRequest, - true, - ); - if (!response.success) { - throw new FrigateCardError(localize('error.failed_retain'), { - request: retainRequest, - response: response, - }); - } -} - -export interface FrigateGetEventsParameters { - instance_id?: string; - camera?: string; - label?: string; - zone?: string; - after?: number; - before?: number; - limit?: number; - has_clip?: boolean; - has_snapshot?: boolean; -} - -/** - * Get events over websocket. May throw. - * @param hass The Home Assistant object. - * @param params The events search parameters. - * @returns An array of 'FrigateEvent's. - */ -export const getEvents = async ( - hass: HomeAssistant, - params?: FrigateGetEventsParameters, -): Promise => { - return await homeAssistantWSRequest( - hass, - frigateEventsSchema, - { - type: 'frigate/events/get', - ...params, - }, - true, - ); -}; - -/** - * Get multiple sets of events. - * @param hass The Home Assistant object. - * @param params A Map of parameters keyed on any key. - * @returns A Map of key -> events. - */ -export const getEventsMultiple = async ( - hass: HomeAssistant, - params: Map, -): Promise> => { - const output: Map = new Map(); - const getEventsAndStore = async ( - key: T, - param: FrigateGetEventsParameters, - ): Promise => { - output.set(key, await getEvents(hass, param)); - }; - await Promise.all( - Array.from(params).map(([key, param]) => getEventsAndStore(key, param)), - ); - return output; -}; - -/** - * Given an event generate a title. - * @param event - */ -export const getEventTitle = (event: FrigateEvent): string => { - const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const durationSeconds = Math.round( - event.end_time - ? event.end_time - event.start_time - : Date.now() / 1000 - event.start_time, - ); - return `${formatDateAndTime( - utcToZonedTime(event.start_time * 1000, localTimezone), - )} [${durationSeconds}s, ${prettifyTitle(event.label)} ${Math.round( - event.top_score * 100, - )}%]`; -}; - -/** - * Get a thumbnail URL for an event. - * @param clientId The Frigate client id. - * @param event The event. - * @returns A string URL. - */ -export const getEventThumbnailURL = (clientId: string, event: FrigateEvent): string => { - return `/api/frigate/${clientId}/thumbnail/${event.id}`; -}; - -/** - * Get a media content ID for an event. - * @param clientId The Frigate client id. - * @param cameraName The Frigate camera name. - * @param id The event id. - * @param mediaType The media type required. - * @returns A string media content id. - */ -export const getEventMediaContentID = ( - clientId: string, - cameraName: string, - id: string, - mediaType: ClipsOrSnapshots, -): string => { - return `media-source://frigate/${clientId}/event/${mediaType}/${cameraName}/${id}`; -}; - -/** - * Generate a recording identifier. - * @param hass The HomeAssistant object. - * @param params The recording parameters to use in the identifer. - * @returns A recording identifier. - */ -export const getRecordingMediaContentID = ( - params: BrowseRecordingQueryParameters, -): string => { - return [ - 'media-source://frigate', - params.clientId, - 'recordings', - `${params.year}-${String(params.month).padStart(2, '0')}`, - String(params.day).padStart(2, '0'), - String(params.hour).padStart(2, '0'), - params.cameraName, - ].join('/'); -}; - -/** - * Convenience function to convert a timestamp to hours, minutes and seconds - * string. Heavily inspired by, and returning the same format as, the Frigate - * UI: https://github.com/blakeblackshear/frigate/blob/master/web/src/components/RecordingPlaylist.jsx#L97 - * @param event The Frigate event. - * @returns A duration string. - */ -export function getEventDurationString(event: FrigateEvent): string { - if (!event.end_time) { - return localize('event.in_progress'); - } - const start = fromUnixTime(event.start_time); - const end = fromUnixTime(event.end_time); - const hours = differenceInHours(end, start); - const minutes = differenceInMinutes(end, start) - hours * 60; - const seconds = differenceInSeconds(end, start) - hours * 60 * 60 - minutes * 60; - let duration = ''; - - if (hours) { - duration += `${hours}h `; - } - if (minutes) { - duration += `${minutes}m `; - } - duration += `${seconds}s`; - return duration; -} diff --git a/src/utils/ha/browse-media.ts b/src/utils/ha/browse-media.ts deleted file mode 100644 index 3b277b69..00000000 --- a/src/utils/ha/browse-media.ts +++ /dev/null @@ -1,480 +0,0 @@ -import { HomeAssistant } from 'custom-card-helpers'; -import { ViewContext } from 'view'; -import { homeAssistantWSRequest } from '.'; -import { - dispatchErrorMessageEvent, - dispatchFrigateCardErrorEvent, - dispatchMessageEvent, -} from '../../components/message.js'; -import { localize } from '../../localize/localize.js'; -import { - BrowseMediaQueryParameters, - BrowseRecordingQueryParameters, - CameraConfig, - ClipsOrSnapshots, - FrigateBrowseMediaSource, - frigateBrowseMediaSourceSchema, - FrigateCardError, - FrigateEvent, - FrigateRecording, - MEDIA_CLASS_PLAYLIST, - MEDIA_CLASS_VIDEO, - MEDIA_TYPE_PLAYLIST, - MEDIA_TYPE_VIDEO, -} from '../../types.js'; -import { View } from '../../view.js'; -import { getAllDependentCameras, getCameraTitle } from '../camera.js'; - -/** - * Return the Frigate event_id given a FrigateBrowseMediaSource object. - * @param media The event to get the id from. - * @returns The `event_id` or `null` if not successfully parsed. - */ -export const getEventID = (media: FrigateBrowseMediaSource): string | null => { - return media.frigate?.event?.id ?? null; -}; - -/** - * Return the event start time given a FrigateBrowseMediaSource object. - * @param browseMedia The media object to get the start time from. - * @returns The start time in unix/epoch time, or null if it cannot be determined. - */ -export const getEventStartTime = (media: FrigateBrowseMediaSource): number | null => { - return media.frigate?.event?.start_time ?? null; -}; - -/** - * Determine if a FrigateBrowseMediaSource object is truly a media item (vs a folder). - * @param media The media object. - * @returns `true` if it's truly a media item, `false` otherwise. - */ -export const isTrueMedia = (media?: FrigateBrowseMediaSource): boolean => { - return !!media && !media.can_expand; -}; - -/** - * From a FrigateBrowseMediaSource item extract the first true media item from the - * children (i.e. a clip/snapshot, not a folder). - * @param media The media object with children. - * @returns The first true media item found. - */ -export const getFirstTrueMediaChildIndex = ( - media: FrigateBrowseMediaSource | null, -): number | null => { - if (!media || !media.children) { - return null; - } - const index = media.children.findIndex((child) => isTrueMedia(child)); - return index >= 0 ? index : null; -}; - -/** - * Browse Frigate media with a media content id. May throw. - * @param hass The HomeAssistant object. - * @param media_content_id The media content id to browse. - * @returns A FrigateBrowseMediaSource object or null on malformed. - */ -const browseMedia = async ( - hass: HomeAssistant, - media_content_id: string, -): Promise => { - const request = { - type: 'media_source/browse_media', - media_content_id: media_content_id, - }; - return await homeAssistantWSRequest(hass, frigateBrowseMediaSourceSchema, request); -}; - -/** - * Browse Frigate media with a media query. May throw. - * @param hass The HomeAssistant object. - * @param params The search parameters to use to search for media. - * @returns A FrigateBrowseMediaSource object or null on malformed. - */ -const browseMediaQuery = async ( - hass: HomeAssistant, - params: BrowseMediaQueryParameters, -): Promise => { - const result = await browseMedia( - hass, - // Defined in: - // https://github.com/blakeblackshear/frigate-hass-integration/blob/master/custom_components/frigate/media_source.py - [ - 'media-source://frigate', - params.clientId, - 'event-search', - params.mediaType, - - // If the name field ends in '.all' the integration will return up to 10K events. - params.unlimited ? '.all' : '', - params.after ? String(Math.floor(params.after)) : '', - params.before ? String(Math.ceil(params.before)) : '', - params.cameraName, - params.label, - params.zone, - ].join('/'), - ); - // If a cameraID was specified, imprint each child with that id for - // traceability. - if (params.cameraID) { - result.children?.forEach((child: FrigateBrowseMediaSource) => { - (child.frigate ??= {}).cameraID = params.cameraID; - }) - } - return result; -}; - -/** - * Browse multiple Frigate media queries. May throw. - * @param hass The HomeAssistant object. - * @param params An array of search parameters to use to search for media. - * @returns A map of FrigateBrowseMediaSource object or null on malformed. - */ -export const multipleBrowseMediaQuery = async ( - hass: HomeAssistant, - params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[], -): Promise> => { - params = Array.isArray(params) ? params : [params]; - const output: Map = new Map(); - await Promise.all( - params.map(async (param: BrowseMediaQueryParameters): Promise => { - output.set(param, await browseMediaQuery(hass, param)); - }), - ); - return output; -}; - -/** - * Browse multiple Frigate media queries, then merged them. May throw. - * @param hass The HomeAssistant object. - * @param params An array of search parameters to use to search for media. - * @returns A single FrigateBrowseMediaSource object or null on malformed. - */ -export const multipleBrowseMediaQueryMerged = async ( - hass: HomeAssistant, - params: BrowseMediaQueryParameters | BrowseMediaQueryParameters[], -): Promise => { - return mergeFrigateBrowseMediaSources(await multipleBrowseMediaQuery(hass, params)); -}; - -/** - * Merge multiple FrigateBrowseMediaSource into a single. Note that this may - * use information from the query to differentiate results that may otherwise - * be identical. - * @param input A map of query -> result. - * @returns A single FrigateBrowseMediaSource object. - */ -export const mergeFrigateBrowseMediaSources = async ( - input: Map, -): Promise => { - const children: FrigateBrowseMediaSource[] = []; - - for (const [query, result] of input.entries()) { - for (const child of result.children || []) { - if (isTrueMedia(child)) { - children.push(child); - } else { - // If there are multiple inputs, separate the folder names with the - // query title (if available). - if (query.title && input.size > 1) { - children.push({ ...child, title: `[${query.title}] ${child.title}` }); - } else { - children.push(child); - } - } - } - } - - return createEventParentForChildren('Merged events', children.sort(sortYoungestToOldest)); -}; - -/** - * Get the parameters to search for media. - * @returns A BrowseMediaQueryParameters object. - */ -export const getBrowseMediaQueryParameters = ( - hass: HomeAssistant, - cameraID: string, - cameraConfig?: CameraConfig, - overrides?: Partial, -): BrowseMediaQueryParameters | null => { - if (!cameraConfig || !cameraConfig.frigate.camera_name) { - return null; - } - return { - clientId: cameraConfig.frigate.client_id, - cameraName: cameraConfig.frigate.camera_name, - label: cameraConfig.frigate.label, - zone: cameraConfig.frigate.zone, - title: getCameraTitle(hass, cameraConfig), - cameraID: cameraID, - ...overrides, - }; -}; - -/** - * Apply overrides to multiple query parameters. - * @param parameters An array of query parameters. - * @param overrides The overrides to apply. - * @returns The override query parameters. - */ -export const overrideMultiBrowseMediaQueryParameters = ( - parameters: BrowseMediaQueryParameters[], - overrides: Partial, -): BrowseMediaQueryParameters[] => { - const output: BrowseMediaQueryParameters[] = []; - parameters.forEach((param) => { - output.push({ ...param, ...overrides }); - }); - return output; -}; - -/** - * Get BrowseMediaQueryParameters for a camera (including its dependencies). - * @param hass Home Assistant object. - * @param cameras Cameras map. - * @param camera Name of the current camera. - * @param mediaType Optional media type to include in the parameters. - * @returns An array of query parameters. - */ -export const getFullDependentBrowseMediaQueryParameters = ( - hass: HomeAssistant, - cameras: Map, - camera: string, - mediaType?: ClipsOrSnapshots, -): BrowseMediaQueryParameters[] | null => { - const cameraIDs = getAllDependentCameras(cameras, camera); - const params: BrowseMediaQueryParameters[] = []; - for (const cameraID of cameraIDs) { - const param = getBrowseMediaQueryParameters( - hass, - cameraID, - cameras.get(cameraID), - mediaType ? { mediaType: mediaType } : {}, - ); - if (param) { - params.push(param); - } - } - return params.length ? params : null; -}; - -/** - * Get BrowseMediaQueryParameters for a camera (including its dependencies) or dispatch an error. - * @param element The element from which to dispatch the error. - * @param hass Home Assistant object. - * @param cameras Cameras map. - * @param camera Name of the current camera. - * @param mediaType Optional media type to include in the parameters. - * @returns An array of query parameters. - */ -export const getFullDependentBrowseMediaQueryParametersOrDispatchError = ( - element: HTMLElement, - hass: HomeAssistant, - cameras: Map, - camera: string, - mediaType?: ClipsOrSnapshots, -): BrowseMediaQueryParameters[] | null => { - const params = getFullDependentBrowseMediaQueryParameters( - hass, - cameras, - camera, - mediaType, - ); - if (!params) { - dispatchErrorMessageEvent(element, localize('error.no_camera_name'), { - context: cameras.get(camera), - }); - return null; - } - return params; -}; - -/** - * Fetch the latest media and dispatch a change view event to reflect the - * results. If no media is found a suitable message event will be triggered - * instead. - * @param element The HTMLElement to dispatch events from. - * @param hass The Home Assistant object. - * @param view The current view to evolve. - * @param browseMediaQueryParameters The media parameters to query with. - * @returns - */ -export const fetchLatestMediaAndDispatchViewChange = async ( - element: HTMLElement, - hass: HomeAssistant, - view: Readonly, - browseMediaQueryParameters: BrowseMediaQueryParameters | BrowseMediaQueryParameters[], -): Promise => { - let parent: FrigateBrowseMediaSource | null; - try { - parent = await multipleBrowseMediaQueryMerged(hass, browseMediaQueryParameters); - } catch (e) { - return dispatchFrigateCardErrorEvent(element, e as FrigateCardError); - } - const childIndex = getFirstTrueMediaChildIndex(parent); - if (!parent || !parent.children || childIndex == null) { - return dispatchMessageEvent( - element, - view.isClipRelatedView() - ? localize('common.no_clip') - : localize('common.no_snapshot'), - 'info', - { - icon: view.isClipRelatedView() ? 'mdi:filmstrip-off' : 'mdi:camera-off', - }, - ); - } - - view - .evolve({ - target: parent, - childIndex: childIndex, - }) - .dispatchChangeEvent(element); -}; - -/** - * Fetch the media of a child FrigateBrowseMediaSource object and dispatch a change - * view event to reflect the results. - * @param node The HTMLElement to dispatch events from. - * @param hass The Home Assistant object. - * @param view The current view to evolve. - * @param child The FrigateBrowseMediaSource child to query for. - * @returns - */ -export const fetchChildMediaAndDispatchViewChange = async ( - element: HTMLElement, - hass: HomeAssistant, - view: Readonly, - child: Readonly, - context?: ViewContext, -): Promise => { - let parent: FrigateBrowseMediaSource; - try { - parent = await browseMedia(hass, child.media_content_id); - } catch (e) { - return dispatchFrigateCardErrorEvent(element, e as FrigateCardError); - } - - view - .evolve({ - target: parent, - }) - .mergeInContext(context) - .dispatchChangeEvent(element); -}; - -/** - * Given an array of media children, create a parent for them. - * @param title The title to use for the parent. - * @param children The children media items. - * @returns A single parent containing the children. - */ -export const createEventParentForChildren = ( - title: string, - children: FrigateBrowseMediaSource[], -): FrigateBrowseMediaSource => { - return { - title: title, - media_class: MEDIA_CLASS_PLAYLIST, - media_content_type: MEDIA_TYPE_PLAYLIST, - media_content_id: '', - can_play: false, - can_expand: true, - children_media_class: MEDIA_CLASS_PLAYLIST, - thumbnail: null, - children: children, - }; -}; - -/** - * Given a media video child with a given media_content_id. - * @param title The title to use for the child. - * @param mediaContentID The media content id to use for the child. - * @param children The children media items. - * @returns A single parent containing the children. - */ -export const createChild = ( - title: string, - mediaContentID: string, - options?: { - thumbnail?: string; - recording?: FrigateRecording; - event?: FrigateEvent; - cameraID?: string, - }, -): FrigateBrowseMediaSource => { - const result: FrigateBrowseMediaSource = { - title: title, - media_class: MEDIA_CLASS_VIDEO, - media_content_type: MEDIA_TYPE_VIDEO, - media_content_id: mediaContentID, - can_play: true, - can_expand: false, - thumbnail: options?.thumbnail ?? null, - children: null - } - if (options?.recording || options?.cameraID || options?.event) { - result.frigate = {} - if (options?.event) { - result.frigate.event = options.event; - } - if (options?.recording) { - result.frigate.recording = options.recording; - } - if (options?.cameraID) { - result.frigate.cameraID = options.cameraID; - } - } - return result; -}; - -/** - * Sort the timeline items most recent to least recent. - * @param a The first item. - * @param b The second item. - * @returns -1, 0, 1 (standard array sort function configuration). - */ -export const sortYoungestToOldest = ( - a: FrigateBrowseMediaSource, - b: FrigateBrowseMediaSource, -): number => { - const a_source = a.frigate?.event ?? a.frigate?.recording; - const b_source = b.frigate?.event ?? b.frigate?.recording; - - if ( - !a_source || - (b_source && b_source.start_time > a_source.start_time) - ) { - return 1; - } - - if ( - !b_source || - (a_source && b_source.start_time < a_source.start_time) - ) { - return -1; - } - return 0; -}; -/** - * Generate a recording identifier. - * @param hass The HomeAssistant object. - * @param params The recording parameters to use in the identifer. - * @returns A recording identifier. - */ -export const generateRecordingIdentifier = ( - params: BrowseRecordingQueryParameters, -): string => { - return [ - 'media-source://frigate', - params.clientId, - 'recordings', - params.cameraName, - `${params.year}-${String(params.month).padStart(2, '0')}-${String( - params.day, - ).padStart(2, '0')}`, - String(params.hour).padStart(2, '0'), - ].join('/'); -}; diff --git a/src/utils/ha/device-registry.ts b/src/utils/ha/device-registry.ts index 84278eda..45694418 100644 --- a/src/utils/ha/device-registry.ts +++ b/src/utils/ha/device-registry.ts @@ -7,7 +7,7 @@ const deviceSchema = z.object({ config_entries: z.string().array(), manufacturer: z.string().nullable(), }) -export const deviceListSchema = deviceSchema.array(); +const deviceListSchema = deviceSchema.array(); export type DeviceList = z.infer; /** diff --git a/src/utils/ha/index.ts b/src/utils/ha/index.ts index 516f711c..29583608 100644 --- a/src/utils/ha/index.ts +++ b/src/utils/ha/index.ts @@ -205,7 +205,7 @@ function computeStyle(state: HassEntity): StyleInfo { * @param stateObj The HassEntity object from `hass.states`. * @returns A string state, e.g. 'on'. */ -export const computeActiveState = (stateObj: HassEntity): string => { +const computeActiveState = (stateObj: HassEntity): string => { const domain = stateObj.entity_id.split('.')[0]; let state = stateObj.state; diff --git a/src/utils/ha/resolved-media.ts b/src/utils/ha/resolved-media.ts index b9e6abfd..d948a69c 100644 --- a/src/utils/ha/resolved-media.ts +++ b/src/utils/ha/resolved-media.ts @@ -1,11 +1,7 @@ import { HomeAssistant } from 'custom-card-helpers'; import QuickLRU from 'quick-lru'; import { homeAssistantWSRequest } from '.'; -import { - FrigateBrowseMediaSource, - ResolvedMedia, - resolvedMediaSchema, -} from '../../types.js'; +import { ResolvedMedia, resolvedMediaSchema } from '../../types.js'; import { errorToConsole } from '../basic'; // It's important the cache size be at least as large as the largest likely @@ -53,25 +49,22 @@ export class ResolvedMediaCache { /** * Resolve a given media source item. * @param hass The Home Assistant object. - * @param mediaSource The media source object. + * @param mediaContentID The media content ID. * @param cache An optional ResolvedMediaCache object. * @returns The resolved media or `null`. */ export const resolveMedia = async ( hass: HomeAssistant, - mediaSource?: FrigateBrowseMediaSource, + mediaContentID: string, cache?: ResolvedMediaCache, ): Promise => { - if (!mediaSource) { - return null; - } - const cachedValue = cache ? cache.get(mediaSource.media_content_id) : undefined; + const cachedValue = cache ? cache.get(mediaContentID) : undefined; if (cachedValue) { return cachedValue; } const request = { type: 'media_source/resolve_media', - media_content_id: mediaSource.media_content_id, + media_content_id: mediaContentID, }; let resolvedMedia: ResolvedMedia | null = null; try { @@ -80,7 +73,7 @@ export const resolveMedia = async ( errorToConsole(e as Error); } if (cache && resolvedMedia) { - cache.set(mediaSource.media_content_id, resolvedMedia); + cache.set(mediaContentID, resolvedMedia); } return resolvedMedia; }; diff --git a/src/utils/ha/update.ts b/src/utils/ha/update.ts index 9499b89d..f7af7974 100644 --- a/src/utils/ha/update.ts +++ b/src/utils/ha/update.ts @@ -4,11 +4,7 @@ import { HassEntityBase } from 'home-assistant-js-websocket'; -export const UPDATE_SUPPORT_INSTALL = 1; -export const UPDATE_SUPPORT_SPECIFIC_VERSION = 2; -export const UPDATE_SUPPORT_PROGRESS = 4; -export const UPDATE_SUPPORT_BACKUP = 8; -export const UPDATE_SUPPORT_RELEASE_NOTES = 16; +const UPDATE_SUPPORT_PROGRESS = 4; interface UpdateEntityAttributes extends HassEntityAttributeBase { auto_update: boolean | null; @@ -28,7 +24,7 @@ export interface UpdateEntity extends HassEntityBase { export const supportsFeature = (stateObj: HassEntity, feature: number): boolean => ((stateObj.attributes.supported_features ?? 0) & feature) !== 0; -export const updateUsesProgress = (entity: UpdateEntity): boolean => +const updateUsesProgress = (entity: UpdateEntity): boolean => supportsFeature(entity, UPDATE_SUPPORT_PROGRESS) && typeof entity.attributes.in_progress === 'number'; diff --git a/src/utils/icons/alarm-panel-icon.ts b/src/utils/icons/alarm-panel-icon.ts index fd9915fa..b0bb5da9 100644 --- a/src/utils/icons/alarm-panel-icon.ts +++ b/src/utils/icons/alarm-panel-icon.ts @@ -21,22 +21,3 @@ export const alarmPanelIcon = (state?: string) => { return 'mdi:shield'; } }; - -export const alarmPanelIconAction = (state?: string) => { - switch (state) { - case 'armed_away': - return 'mdi:shield-lock-outline'; - case 'armed_vacation': - return 'mdi:shield-airplane-outline'; - case 'armed_home': - return 'mdi:shield-home-outline'; - case 'armed_night': - return 'mdi:shield-moon-outline'; - case 'armed_custom_bypass': - return 'mdi:shield-half-full'; - case 'disarmed': - return 'mdi:shield-off-outline'; - default: - return 'mdi:shield-outline'; - } -}; diff --git a/src/utils/icons/cover-icon.ts b/src/utils/icons/cover-icon.ts index 37cd15d6..44b5985b 100644 --- a/src/utils/icons/cover-icon.ts +++ b/src/utils/icons/cover-icon.ts @@ -87,27 +87,3 @@ export const coverIcon = (state?: string, entity?: HassEntity): string => { return 'mdi:window-open'; } }; - -export const computeOpenIcon = (stateObj: HassEntity): string => { - switch (stateObj.attributes.device_class) { - case 'awning': - case 'curtain': - case 'door': - case 'gate': - return 'mdi:arrow-expand-horizontal'; - default: - return 'mdi:arrow-up'; - } -}; - -export const computeCloseIcon = (stateObj: HassEntity): string => { - switch (stateObj.attributes.device_class) { - case 'awning': - case 'curtain': - case 'door': - case 'gate': - return 'mdi:arrow-collapse-horizontal'; - default: - return 'mdi:arrow-down'; - } -}; diff --git a/src/utils/icons/sensor-icon.ts b/src/utils/icons/sensor-icon.ts index 885641ef..8331a9aa 100644 --- a/src/utils/icons/sensor-icon.ts +++ b/src/utils/icons/sensor-icon.ts @@ -70,7 +70,7 @@ const batteryStateIcon = ( return batteryIcon(battery, batteryCharging); }; -export const batteryIcon = ( +const batteryIcon = ( batteryState: number | string, batteryCharging?: boolean, ) => { diff --git a/src/utils/media-to-view.ts b/src/utils/media-to-view.ts index c6bb6413..01b59eba 100644 --- a/src/utils/media-to-view.ts +++ b/src/utils/media-to-view.ts @@ -1,41 +1,99 @@ import add from 'date-fns/add'; -import endOfHour from 'date-fns/endOfHour'; -import fromUnixTime from 'date-fns/fromUnixTime'; -import getUnixTime from 'date-fns/getUnixTime'; -import startOfHour from 'date-fns/startOfHour'; import sub from 'date-fns/sub'; import { ViewContext } from 'view'; -import { dispatchMessageEvent } from '../components/message'; -import { localize } from '../localize/localize'; -import { CameraConfig, ExtendedHomeAssistant, FrigateBrowseMediaSource } from '../types'; -import { View } from '../view'; -import { formatDateAndTime, prettifyTitle } from './basic'; -import { getRecordingMediaContentID } from './frigate'; +import { CameraConfig, ClipsOrSnapshotsOrAll, FrigateCardView } from '../types'; +import { View } from '../view/view'; import { - createChild, - createEventParentForChildren, - sortYoungestToOldest, -} from './ha/browse-media'; -import { - RecordingSegmentsItem, - sortOldestToYoungest, - DataManager, -} from './data-manager'; -import { getAllDependentCameras, getTrueCameras } from './camera.js'; + EventMediaQueries, + MediaQueries, + RecordingMediaQueries, +} from '../view/media-queries'; +import { CameraManager } from '../camera-manager/manager'; +import { getAllDependentCameras } from './camera.js'; +import { ViewMedia } from '../view/media'; +import { HomeAssistant } from 'custom-card-helpers'; +import { dispatchFrigateCardErrorEvent } from '../components/message'; +import { MediaQueriesResults } from '../view/media-queries-results'; +import { errorToConsole } from './basic'; +import { MediaQuery } from '../camera-manager/types'; + +export const changeViewToRecentEventsForCameraAndDependents = async ( + element: HTMLElement, + hass: HomeAssistant, + cameraManager: CameraManager, + cameras: Map, + view: View, + options?: { + mediaType?: ClipsOrSnapshotsOrAll; + targetView?: FrigateCardView; + }, +): Promise => { + ( + await createViewForEvents(element, hass, cameraManager, cameras, view, { + ...options, + limit: 50, // Capture the 50 most recent events. + }) + )?.dispatchChangeEvent(element); +}; + +export const createViewForEvents = async ( + element: HTMLElement, + hass: HomeAssistant, + cameraManager: CameraManager, + cameras: Map, + view: View, + options?: { + query?: EventMediaQueries; + cameraIDs?: Set; + mediaType?: ClipsOrSnapshotsOrAll; + targetCameraID?: string; + targetView?: FrigateCardView; + limit?: number; + }, +): Promise => { + let query: EventMediaQueries; + const cameraIDs: Set = options?.cameraIDs + ? options.cameraIDs + : new Set(getAllDependentCameras(cameras, view.camera)); + + if (options?.query) { + query = options.query; + } else { + const eventQueries = cameraManager.generateDefaultEventQueries(cameraIDs, { + ...(options?.limit && { limit: options.limit }), + ...(options?.mediaType === 'clips' && { hasClip: true }), + ...(options?.mediaType === 'snapshots' && { hasSnapshot: true }), + }); + if (!eventQueries) { + return null; + } + query = new EventMediaQueries(eventQueries); + } + + if (!query) { + return null; + } + + return executeMediaQueryForView(element, hass, cameraManager, view, query, { + cameraIDs: cameraIDs, + targetView: options?.targetView, + targetCameraID: options?.targetCameraID, + }); +}; /** * Change the view to a recent recording. * @param element The element to dispatch the view change from. * @param hass The Home Assistant object. - * @param dataManager The datamanager to use for data access. + * @param cameraManager The datamanager to use for data access. * @param cameras The camera configurations. * @param view The current view. * @param options A set of cameraIDs to fetch recordings for, and a targetView to dispatch to. */ export const changeViewToRecentRecordingForCameraAndDependents = async ( element: HTMLElement, - hass: ExtendedHomeAssistant, - dataManager: DataManager, + hass: HomeAssistant, + cameraManager: CameraManager, cameras: Map, view: View, options?: { @@ -43,200 +101,133 @@ export const changeViewToRecentRecordingForCameraAndDependents = async ( }, ): Promise => { const now = new Date(); - - await changeViewToRecording(element, hass, dataManager, cameras, view, { - ...options, - - // Fetch 1 days worth of recordings (including recordings that are for the current hour). - cameraIDs: getAllDependentCameras(cameras, view.camera), - start: sub(now, { days: 1 }), - end: add(now, { hours: 1 }), - }); + ( + await createViewForRecordings(element, hass, cameraManager, cameras, view, { + ...options, + // Fetch 7 days worth of recordings (including recordings that are for the + // current hour). + start: sub(now, { days: 7 }), + end: add(now, { hours: 1 }), + }) + )?.dispatchChangeEvent(element); }; /** - * Change the view to a recording. + * Create a view for recordings. * @param element The element to dispatch the view change from. * @param hass The Home Assistant object. - * @param dataManager The datamanager to use for data access. + * @param cameraManager The datamanager to use for data access. * @param cameras The camera configurations. * @param view The current view. * @param options A specific window (start and end) to fetch recordings for, a * targetTime to seek to, a targetView to dispatch to and a set of cameraIDs to * restrict to. */ -export const changeViewToRecording = async ( +export const createViewForRecordings = async ( element: HTMLElement, - hass: ExtendedHomeAssistant, - dataManager: DataManager, + hass: HomeAssistant, + cameraManager: CameraManager, cameras: Map, view: View, options?: { + query?: RecordingMediaQueries; cameraIDs?: Set; + targetCameraID?: string; targetView?: 'recording' | 'recordings'; targetTime?: Date; start?: Date; end?: Date; }, -): Promise => { - if (options && options.start && options.end) { - await dataManager.fetchIfNecessary(element, hass, options.start, options.end); - } - +): Promise => { const cameraIDs: Set = options?.cameraIDs ? options.cameraIDs - : new Set([view.camera]); - const children = createRecordingChildren(dataManager, cameras, cameraIDs, { - ...(options?.start && options?.end && { start: options.start, end: options.end }), - }); + : new Set(getAllDependentCameras(cameras, view.camera)); - if (!children.length) { - return dispatchMessageEvent(element, localize('common.no_recording'), 'info', { - icon: 'mdi:album', + let query: RecordingMediaQueries; + if (options?.query) { + query = options.query; + } else { + const recordingQueries = cameraManager.generateDefaultRecordingQueries(cameraIDs, { + ...(options?.start && { start: options.start }), + ...(options?.end && { end: options.end }), }); + + if (!recordingQueries) { + return null; + } + + query = new RecordingMediaQueries(recordingQueries); } - const viewerContext = options?.targetTime - ? generateMediaViewerContextForChildren(dataManager, children, options.targetTime) - : {}; - const childIndex = options?.targetTime - ? findChildIndex(children, options.targetTime, cameraIDs) - : null; - const child = childIndex !== null ? children[childIndex] ?? null : null; - - view - ?.evolve({ - view: options?.targetView ? options.targetView : 'recording', - target: createEventParentForChildren(localize('common.recordings'), children), - childIndex: childIndex ?? 0, - ...(child?.frigate?.cameraID && { camera: child.frigate?.cameraID }), - }) - .mergeInContext(viewerContext) - .dispatchChangeEvent(element); -}; - -/** - * Create recording objects. - * @param dataManager The datamanager to use for data access. - * @param cameras The camera configurations. - * @param cameraIDs The camera IDs to include recordings for. - * @param options A specific window (start and end) to allow recordings for. - * @returns - */ -const createRecordingChildren = ( - dataManager: DataManager, - cameras: Map, - cameraIDs: Set, - options?: { - start?: Date; - end?: Date; - }, -): FrigateBrowseMediaSource[] => { - const children: FrigateBrowseMediaSource[] = []; - - for (const cameraID of getTrueCameras(cameras, cameraIDs)) { - const config = cameras.get(cameraID) ?? null; - const recordingSummary = dataManager.getRecordingSummaryForCamera(cameraID); - if (!config?.frigate.camera_name || !recordingSummary) { - continue; - } - - for (const dayData of recordingSummary) { - for (const hourData of dayData.hours) { - const hour = add(dayData.day, { hours: hourData.hour }); - const startHour = startOfHour(hour); - const endHour = endOfHour(hour); - - if ( - (!options?.start || startHour >= options.start) && - (!options?.end || endHour <= options.end) - ) { - children.push( - createChild( - `${prettifyTitle(config.frigate.camera_name)} ${formatDateAndTime(hour)}`, - getRecordingMediaContentID({ - clientId: config.frigate.client_id, - year: dayData.day.getFullYear(), - month: dayData.day.getMonth() + 1, - day: dayData.day.getDate(), - hour: hourData.hour, - cameraName: config.frigate.camera_name, - }), - { - recording: { - camera: config.frigate.camera_name, - start_time: getUnixTime(startHour), - end_time: getUnixTime(endHour), - events: hourData.events, - }, - cameraID: cameraID, - }, - ), - ); - } - } - } - } - // Sort the events by time (to align recordings for different cameras at the - // same time). - return children.sort(sortYoungestToOldest); -}; - -/** - * Generate the media view context for a set of media children (used to set - * seek times into each media item). - * @param dataManager The datamanager to use for data access. - * @param children The media children. - * @param targetTime The target time. - * @returns The ViewContext. - */ -export const generateMediaViewerContextForChildren = ( - dataManager: DataManager, - children: FrigateBrowseMediaSource[], - targetTime: Date, -): ViewContext => { - const seek = new Map(); - const segmentsDataset = dataManager.recordingSegments; - const hourStart = startOfHour(targetTime); - - children.forEach((child, index) => { - const source = child.frigate?.recording ?? child.frigate?.event; - if (source && source.end_time && child.frigate?.cameraID) { - const start = source.start_time * 1000; - const end = source.end_time * 1000; - let seekSeconds: number | null = null; - - if (targetTime.getTime() >= start && targetTime.getTime() <= end) { - const segments = segmentsDataset.get({ - filter: (segment) => - segment.cameraID === child.frigate?.cameraID && - segment.start >= start && - segment.end <= end, - order: sortOldestToYoungest, - }); - seekSeconds = getSeekTimeInSegments( - // Recordings start from the top of the hour. - child.frigate.recording ? hourStart : fromUnixTime(source.start_time), - targetTime, - segments, - ); - } - - if (seekSeconds !== null) { - seek.set(index, { - seekSeconds: seekSeconds, - seekTime: targetTime.getTime() / 1000, - }); - } - } + return executeMediaQueryForView(element, hass, cameraManager, view, query, { + cameraIDs: cameraIDs, + targetView: options?.targetView, + targetCameraID: options?.targetCameraID, + targetTime: options?.targetTime, }); - return seek.size > 0 ? { mediaViewer: { seek: seek } } : {}; +}; + +const executeMediaQueryForView = async ( + element: HTMLElement, + hass: HomeAssistant, + cameraManager: CameraManager, + view: View, + query: MediaQueries, + options?: { + cameraIDs?: Set; + targetCameraID?: string; + targetView?: FrigateCardView; + targetTime?: Date; + }, +): Promise => { + let mediaArray: ViewMedia[] | null; + + const queries = query.getQueries(); + if (!queries) { + return null; + } + + try { + mediaArray = await cameraManager.executeMediaQueries(hass, queries); + } catch (e) { + errorToConsole(e as Error); + dispatchFrigateCardErrorEvent(element, e as Error); + return null; + } + + if (!mediaArray) { + return null; + } + + const queryResults = new MediaQueriesResults(mediaArray); + let viewerContext: ViewContext | undefined = {}; + + if (options?.targetTime && options.cameraIDs) { + queryResults.selectBestResult((media) => + findClosestMediaIndex(media, options.targetTime as Date, options.cameraIDs), + ); + viewerContext = { + mediaViewer: { + seek: options.targetTime, + }, + }; + } + + return ( + view + ?.evolve({ + query: query, + queryResults: queryResults, + view: options?.targetView, + camera: options?.targetCameraID, + }) + .mergeInContext(viewerContext) ?? null + ); }; /** - * Find the relevant recording child given a date target. - * @param children The FrigateBrowseMediaSource[] children. Must be sorted - * most recent first. + * Find the closest matching media object. + * @param mediaArray The media. Must be sorted most recent first. * @param targetTime The target time used to find the relevant child. * @param cameraIDs The camera IDs to search for. * @param refPoint Whether to find based on the start or end of the @@ -244,10 +235,10 @@ export const generateMediaViewerContextForChildren = ( * the best match. * @returns The childindex or null if no matching child is found. */ -export const findChildIndex = ( - children: FrigateBrowseMediaSource[], +export const findClosestMediaIndex = ( + mediaArray: ViewMedia[], targetTime: Date, - cameraIDs: Set, + cameraIDs?: Set, refPoint?: 'start' | 'end', ): number | null => { let bestMatch: @@ -257,60 +248,29 @@ export const findChildIndex = ( } | undefined; - for (let i = 0; i < children.length; ++i) { - const child = children[i]; - if (child.frigate?.cameraID && cameraIDs.has(child.frigate.cameraID)) { - const source = child.frigate.event ?? child.frigate.recording; - if (!source?.start_time || !source?.end_time) { - continue; - } - const startTime = fromUnixTime(source.start_time); - const endTime = fromUnixTime(source.end_time); + if (!cameraIDs) { + return null; + } - if (startTime <= targetTime && endTime >= targetTime) { - if (!refPoint) { - return i; - } - const delta = - refPoint === 'end' - ? endTime.getTime() - targetTime.getTime() - : targetTime.getTime() - startTime.getTime(); - if (!bestMatch || delta < bestMatch.delta) { - bestMatch = { index: i, delta: delta }; - } + for (const [i, media] of mediaArray.entries()) { + if (!cameraIDs.has(media.getCameraID())) { + continue; + } + + if (media.includesTime(targetTime)) { + const start = media.getStartTime(); + const end = media.getEndTime(); + if (!refPoint || !start || !end) { + return i; + } + const delta = + refPoint === 'end' + ? end.getTime() - targetTime.getTime() + : targetTime.getTime() - start.getTime(); + if (!bestMatch || delta < bestMatch.delta) { + bestMatch = { index: i, delta: delta }; } } } return bestMatch ? bestMatch.index : null; }; - -/** - * Get the number of seconds to seek into a video stream consisting of the - * provided segments to reach the target time provided. - * @param startTime The earliest allowable time to seek from. - * @param targetTime Target time. - * @param segments An array of segments dataset items. Must be sorted from oldest to youngest. - * @returns - */ -const getSeekTimeInSegments = ( - startTime: Date, - targetTime: Date, - segments: RecordingSegmentsItem[], -): number | null => { - if (!segments.length) { - return null; - } - let seekMilliseconds = 0; - - // Inspired by: https://github.com/blakeblackshear/frigate/blob/release-0.11.0/web/src/routes/Recording.jsx#L27 - for (const segment of segments) { - if (segment.start > targetTime.getTime()) { - break; - } - const start = - segment.start < startTime.getTime() ? startTime.getTime() : segment.start; - const end = segment.end > targetTime.getTime() ? targetTime.getTime() : segment.end; - seekMilliseconds += end - start; - } - return seekMilliseconds / 1000; -}; diff --git a/src/utils/media.ts b/src/utils/media.ts index 0e300087..b7aabcae 100644 --- a/src/utils/media.ts +++ b/src/utils/media.ts @@ -2,7 +2,7 @@ // to give a cleaner UI appearance, see: // https://github.com/dermotduffy/frigate-hass-card/issues/856 export const MEDIA_LOAD_CONTROLS_HIDE_SECONDS = 2; -export const MEDIA_SEEK_CONTROLS_HIDE_SECONDS = 1; +const MEDIA_SEEK_CONTROLS_HIDE_SECONDS = 1; /** * Temporarily hide media controls. diff --git a/src/utils/thumbnail.ts b/src/utils/thumbnail.ts index 6c2f0b53..b13dd57d 100644 --- a/src/utils/thumbnail.ts +++ b/src/utils/thumbnail.ts @@ -8,7 +8,7 @@ import { HomeAssistant } from 'custom-card-helpers'; * @param thumbnailURL The thumbnail URL. * @returns A base64 encoded data URL for the thumbnail. */ -export const fetchThumbnail = async ( +const fetchThumbnail = async ( hass: HomeAssistant, thumbnailURL: string, ): Promise => { @@ -42,7 +42,7 @@ export const fetchThumbnail = async ( }); }; -type FetchThumbnailTaskArgs = [boolean, string | undefined]; +export type FetchThumbnailTaskArgs = [boolean, string | undefined]; /** * Create a Lit task to fetch a thumbnail. diff --git a/src/utils/timeline-source.ts b/src/utils/timeline-source.ts new file mode 100644 index 00000000..ab84cf00 --- /dev/null +++ b/src/utils/timeline-source.ts @@ -0,0 +1,266 @@ +import { HomeAssistant } from 'custom-card-helpers'; +import add from 'date-fns/add'; +import sub from 'date-fns/sub'; +import { DataSet } from 'vis-data'; +import { IdType, TimelineItem, TimelineWindow } from 'vis-timeline/esnext'; +import { ClipsOrSnapshotsOrAll } from '../types'; +import { CameraManager } from '../camera-manager/manager'; +import { EventQuery, RecordingSegment } from '../camera-manager/types'; +import { capEndDate, convertRangeToCacheFriendlyTimes } from '../camera-manager/util'; +import { ViewMedia } from '../view/media'; +import { + compressRanges, + ExpiringMemoryRangeSet, + MemoryRangeSet, +} from '../camera-manager/range'; +import { errorToConsole, ModifyInterface } from './basic.js'; + +// Allow timeline freshness to be at least this number of seconds out of date +// (caching times in the data-engine may increase the effective delay). +const TIMELINE_FRESHNESS_TOLERANCE_SECONDS = 30; + +// Number of seconds gap allowable in order to consider two recording segments +// to be consecutive. Some low performance cameras have trouble and without a +// generous allowance here the timeline may be littered with individual segments +// instead of clean recording blocks. +const TIMELINE_RECORDING_SEGMENT_CONSECUTIVE_TOLERANCE_SECONDS = 60; + +export interface FrigateCardTimelineItem extends TimelineItem { + // Use numbers to avoid significant volumes of Date object construction (for + // high-quantity recording segments). + start: number; + end?: number; + media?: ViewMedia; +} + +export class TimelineDataSource { + protected _cameraManager: CameraManager; + protected _dataset: DataSet = new DataSet(); + + // The ranges in which recordings have been calculated and added for. + // Calculating recordings is a very expensive process since it is based on + // segments (not just the fetch is expensive, but the JS to dedup and turn the + // high-N segments into a smaller number of consecutive recording blocks). + protected _recordingRanges = new MemoryRangeSet(); + + // Cache event ranges since re-adding the same events is a timeline + // performance killer (even if the request results are cached). + protected _eventRanges = new ExpiringMemoryRangeSet(); + + protected _cameraIDs: Set; + protected _mediaType: ClipsOrSnapshotsOrAll; + + constructor( + cameraManager: CameraManager, + cameraIDs: Set, + media: ClipsOrSnapshotsOrAll, + ) { + this._cameraManager = cameraManager; + this._cameraIDs = cameraIDs; + this._mediaType = media; + } + + get dataset(): DataSet { + return this._dataset; + } + + public rewriteEvent(id: IdType): void { + // Hack: For timeline uses of the event dataset clustering may not update + // unless the dataset changes, artifically update the dataset to ensure the + // newly selected item cannot be included in a cluster. + + // Hack2: Cannot use `updateOnly` here, as vis-data loses the object + // prototype, see: https://github.com/visjs/vis-data/issues/997 . Instead, + // remove then add. + const item = this._dataset.get(id); + if (item) { + this._dataset.remove(id); + this._dataset.add(item); + } + } + + public async refresh(hass: HomeAssistant, window: TimelineWindow): Promise { + try { + await Promise.all([ + this._refreshEvents(hass, window), + this._refreshRecordings(hass, window), + ]); + } catch (e) { + errorToConsole(e as Error); + + // Intentionally ignore errors here, since it is likely the user will + // change the range again and a subsequent call may work. To do otherwise + // would be jarring to the timeline experience in the case of transient + // errors from the backend. + } + } + + public getCacheFriendlyEventWindow(window: TimelineWindow): TimelineWindow { + return convertRangeToCacheFriendlyTimes(window, { + endCap: true, + }); + } + + public getTimelineEventQueries(window: TimelineWindow): EventQuery[] | null { + return this._cameraManager.generateDefaultEventQueries(this._cameraIDs, { + start: window.start, + end: window.end, + ...(this._mediaType === 'clips' && { hasClip: true }), + ...(this._mediaType === 'snapshots' && { hasSnapshot: true }), + }); + } + + protected async _refreshEvents( + hass: HomeAssistant, + window: TimelineWindow, + ): Promise { + if ( + this._eventRanges.hasCoverage({ + start: window.start, + end: sub(capEndDate(window.end), { + seconds: TIMELINE_FRESHNESS_TOLERANCE_SECONDS, + }), + }) + ) { + return; + } + + const cacheFriendlyWindow = this.getCacheFriendlyEventWindow(window); + const eventQueries = this.getTimelineEventQueries(cacheFriendlyWindow); + if (!eventQueries) { + return; + } + + const mediaArray = await this._cameraManager.executeMediaQueries(hass, eventQueries); + for (const media of mediaArray ?? []) { + const endTime = media.getEndTime(); + const startTime = media.getStartTime(); + const id = media.getID(); + if (id && startTime) { + this._dataset.update({ + id: id, + group: media.getCameraID(), + content: '', + media: media, + start: startTime.getTime(), + type: endTime ? 'range' : 'point', + ...(endTime && { end: endTime.getTime() }), + }); + } + } + + this._eventRanges.add({ + ...cacheFriendlyWindow, + expires: add(new Date(), { seconds: TIMELINE_FRESHNESS_TOLERANCE_SECONDS }), + }); + } + + protected async _refreshRecordings( + hass: HomeAssistant, + window: TimelineWindow, + ): Promise { + type FrigateCardTimelineItemWithEnd = ModifyInterface< + FrigateCardTimelineItem, + { end: number } + >; + + const convertSegmentToRecording = ( + cameraID: string, + segment: RecordingSegment, + ): FrigateCardTimelineItemWithEnd => { + return { + id: `recording-${cameraID}-${segment.id}`, + group: cameraID, + start: segment.start_time * 1000, + end: segment.end_time * 1000, + content: '', + type: 'background', + }; + }; + + const getExistingRecordingsForCameraID = ( + cameraID: string, + ): FrigateCardTimelineItemWithEnd[] => { + return this._dataset.get({ + filter: (item) => + item.type == 'background' && item.group === cameraID && item.end !== undefined, + }) as FrigateCardTimelineItemWithEnd[]; + }; + + const deleteRecordingsForCameraID = (cameraID: string): void => { + this._dataset.remove( + this._dataset.get({ + filter: (item) => item.type === 'background' && item.group === cameraID, + }), + ); + }; + + const addRecordings = (recordings: FrigateCardTimelineItemWithEnd[]): void => { + this._dataset.add(recordings); + }; + + // Calculate an end date that's slightly short of the current time to allow + // for caching up to the freshness tolerance. + if ( + this._recordingRanges.hasCoverage({ + start: window.start, + end: sub(capEndDate(window.end), { + seconds: TIMELINE_FRESHNESS_TOLERANCE_SECONDS, + }), + }) + ) { + return; + } + + const cacheFriendlyWindow = convertRangeToCacheFriendlyTimes(window, { + endCap: true, + }); + + const recordingQueries = this._cameraManager.generateDefaultRecordingSegmentsQueries( + this._cameraIDs, + { + start: cacheFriendlyWindow.start, + end: cacheFriendlyWindow.end, + }, + ); + + if (!recordingQueries) { + return; + } + const results = await this._cameraManager.getRecordingSegments( + hass, + recordingQueries, + ); + + const newSegments: Map = new Map(); + for (const [query, result] of results) { + for (const cameraID of query.cameraIDs) { + let destination: RecordingSegment[] | undefined = newSegments.get(cameraID); + if (!destination) { + destination = []; + newSegments.set(cameraID, destination); + } + result.segments.forEach((segment) => destination?.push(segment)); + } + } + + for (const [cameraID, segments] of newSegments.entries()) { + const existingRecordings = getExistingRecordingsForCameraID(cameraID); + const mergedRecordings = existingRecordings.concat( + segments.map((segment) => convertSegmentToRecording(cameraID, segment)), + ); + const compressedRecordings = compressRanges( + mergedRecordings, + TIMELINE_RECORDING_SEGMENT_CONSECUTIVE_TOLERANCE_SECONDS, + ) as FrigateCardTimelineItemWithEnd[]; + + deleteRecordingsForCameraID(cameraID); + addRecordings(compressedRecordings); + } + + this._recordingRanges.add({ + start: cacheFriendlyWindow.start, + end: cacheFriendlyWindow.end, + }); + } +} diff --git a/src/view/media-classifier.ts b/src/view/media-classifier.ts new file mode 100644 index 00000000..be9668c1 --- /dev/null +++ b/src/view/media-classifier.ts @@ -0,0 +1,19 @@ +import { ViewMedia, RecordingViewMedia, EventViewMedia } from './media'; + +export class ViewMediaClassifier { + public static isEvent(media: ViewMedia): media is EventViewMedia { + return this.isClip(media) || this.isSnapshot(media); + } + public static isRecording(media: ViewMedia): media is RecordingViewMedia { + return media.getMediaType() === 'recording'; + } + public static isClip(media: ViewMedia): boolean { + return media.getMediaType() === 'clip'; + } + public static isSnapshot(media: ViewMedia): boolean { + return media.getMediaType() === 'snapshot'; + } + public static isVideo(media: ViewMedia): boolean { + return this.isClip(media) || this.isRecording(media); + } +} diff --git a/src/view/media-queries-classifier.ts b/src/view/media-queries-classifier.ts new file mode 100644 index 00000000..4508e97f --- /dev/null +++ b/src/view/media-queries-classifier.ts @@ -0,0 +1,15 @@ +import { EventMediaQueries, MediaQueries, RecordingMediaQueries } from './media-queries'; + +export class MediaQueriesClassifier { + public static areEventQueries( + queries?: MediaQueries | null, + ): queries is EventMediaQueries { + return queries instanceof EventMediaQueries; + } + + public static areRecordingQueries( + queries?: MediaQueries | null, + ): queries is RecordingMediaQueries { + return queries instanceof RecordingMediaQueries; + } +} diff --git a/src/view/media-queries-results.ts b/src/view/media-queries-results.ts new file mode 100644 index 00000000..318ae794 --- /dev/null +++ b/src/view/media-queries-results.ts @@ -0,0 +1,112 @@ +import clone from 'lodash-es/clone.js'; +import { isSuperset } from '../utils/basic.js'; +import { ViewMedia } from './media.js'; + +export class MediaQueriesResults { + protected _results: ViewMedia[] | null = null; + protected _resultsTimestamp: Date | null = null; + protected _selectedIndex: number | null = null; + + constructor(results?: ViewMedia[], selectedIndex?: number | null) { + if (results) { + this.setResults(results); + } + this.selectResult(selectedIndex ?? 0); + } + + public clone(): MediaQueriesResults { + // Shallow clone -- will reuse the same _results object (as there are no + // methods that support modification of the results themselves, and since + // changing the selectedIndex on a consistent set of results is a common + // operation). + return clone(this); + } + + public isSupersetOf(that: MediaQueriesResults): boolean { + if (!this._results || !that._results) { + return false; + } + + const thisMediaIDs = new Set(this._results.map((media) => media.getID())); + const thatMediaIDs = new Set(that._results.map((media) => media.getID())); + + if ( + !thisMediaIDs || + !thatMediaIDs || + // If either media sets contain a null identifier (i.e. a media item with + // no ID) we must assume this is not a subset as multiple media items may + // reduce to the same null identifier above. + thisMediaIDs.has(null) || + thatMediaIDs.has(null) + ) { + return false; + } + return isSuperset(thisMediaIDs, thatMediaIDs); + } + + public getResults(): ViewMedia[] | null { + return this._results; + } + public getResultsCount(): number { + return this._results?.length ?? 0; + } + public hasResults(): boolean { + return !!this._results; + } + public setResults(results: ViewMedia[]) { + this._results = results; + this._resultsTimestamp = new Date(); + } + public getResult(index?: number): ViewMedia | null { + if (!this._results || index === undefined) { + return null; + } + return this._results[index]; + } + public getSelectedResult(): ViewMedia | null { + return this._selectedIndex === null ? null : this.getResult(this._selectedIndex); + } + public getSelectedIndex(): number | null { + return this._selectedIndex; + } + public hasSelectedResult(): boolean { + return this.getSelectedResult() !== null; + } + public resetSelectedResult(): MediaQueriesResults { + this._selectedIndex = null; + return this; + } + public getResultsTimestamp(): Date | null { + return this._resultsTimestamp; + } + + public selectResult(index: number | null): MediaQueriesResults { + if ( + index === null || + (this._results && index >= 0 && index < this._results.length) + ) { + this._selectedIndex = index; + } + return this; + } + public selectResultIfFound(func: (media: ViewMedia) => boolean): MediaQueriesResults { + for (const [index, result] of this._results?.entries() ?? []) { + if (func(result)) { + this._selectedIndex = index; + break; + } + } + return this; + } + public selectBestResult( + func: (media: ViewMedia[]) => number | null, + ): MediaQueriesResults { + if (this._results) { + const resultIndex = func(this._results); + if (resultIndex !== null) { + this._selectedIndex = resultIndex; + } + } + return this; + } +} diff --git a/src/view/media-queries.ts b/src/view/media-queries.ts new file mode 100644 index 00000000..718c1d98 --- /dev/null +++ b/src/view/media-queries.ts @@ -0,0 +1,41 @@ +import cloneDeep from 'lodash-es/cloneDeep.js'; +import { EventQuery, MediaQuery, RecordingQuery } from '../camera-manager/types.js'; + +export type MediaQueries = EventMediaQueries | RecordingMediaQueries; + +class MediaQueriesBase { + protected _queries: T[] | null = null; + + public constructor(queries?: T[]) { + if (queries) { + this._queries = queries; + } + } + + public clone(): MediaQueriesBase { + return cloneDeep(this); + } + + public getQueries(): T[] | null { + return this._queries; + } + + public setQueries(queries: T[]): void { + this._queries = queries; + } +} + +export class EventMediaQueries extends MediaQueriesBase { + public convertToClipsQueries(): void { + for (const query of this._queries ?? []) { + delete query.hasSnapshot; + query.hasClip = true; + } + } + + public clone(): EventMediaQueries { + return cloneDeep(this); + } +} + +export class RecordingMediaQueries extends MediaQueriesBase {} diff --git a/src/view/media.ts b/src/view/media.ts new file mode 100644 index 00000000..a1f198e7 --- /dev/null +++ b/src/view/media.ts @@ -0,0 +1,67 @@ +export type ViewMediaType = 'clip' | 'snapshot' | 'recording'; + +export class ViewMedia { + protected _mediaType: ViewMediaType; + protected _cameraID: string; + + constructor(mediaType: ViewMediaType, cameraID: string) { + this._mediaType = mediaType; + this._cameraID = cameraID; + } + public getContentType(): 'image' | 'video' { + return this._mediaType === 'snapshot' ? 'image' : 'video'; + } + public getCameraID(): string { + return this._cameraID; + } + public getMediaType(): ViewMediaType { + return this._mediaType; + } + public getID(): string | null { + return null; + } + public getStartTime(): Date | null { + return null; + } + public getEndTime(): Date | null { + return null; + } + public getContentID(): string | null { + return null; + } + public getTitle(): string | null { + return null; + } + public getThumbnail(): string | null { + return null; + } + public isFavorite(): boolean | null { + return null; + } + public includesTime(seek: Date): boolean { + const startTime = this.getStartTime(); + const endTime = this.getEndTime(); + return !!startTime && !!endTime && seek >= startTime && seek <= endTime; + } + + // Sets the favorite attribute (if any). This purely sets the media item as a + // favorite in JS. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public setFavorite(_favorite: boolean): void { + return; + } + public getWhere(): string[] | null { + return null; + } +} + +export interface EventViewMedia extends ViewMedia { + getScore(): number | null; + getWhat(): string[] | null; + isGroupableWith(that: EventViewMedia): boolean; + hasClip(): boolean | null; +} + +export interface RecordingViewMedia extends ViewMedia { + getEventCount(): number | null; +} diff --git a/src/view.ts b/src/view/view.ts similarity index 63% rename from src/view.ts rename to src/view/view.ts index fcc5177f..2dc4705f 100644 --- a/src/view.ts +++ b/src/view/view.ts @@ -1,22 +1,23 @@ import { ViewContext } from 'view'; import { - FrigateBrowseMediaSource, FrigateCardUserSpecifiedView, FrigateCardView, FRIGATE_CARD_VIEWS_USER_SPECIFIED, FRIGATE_CARD_VIEW_DEFAULT, -} from './types.js'; -import { dispatchFrigateCardEvent } from './utils/basic.js'; +} from '../types.js'; +import { dispatchFrigateCardEvent } from '../utils/basic.js'; +import { MediaQueries } from './media-queries'; +import { MediaQueriesResults } from './media-queries-results'; -export interface ViewEvolveParameters { +interface ViewEvolveParameters { view?: FrigateCardView; camera?: string; - target?: FrigateBrowseMediaSource | null; - childIndex?: number | null; + query?: MediaQueries | null; + queryResults?: MediaQueriesResults | null; context?: ViewContext | null; } -export interface ViewParameters extends ViewEvolveParameters { +interface ViewParameters extends ViewEvolveParameters { view: FrigateCardView; camera: string; } @@ -24,15 +25,15 @@ export interface ViewParameters extends ViewEvolveParameters { export class View { public view: FrigateCardView; public camera: string; - public target: FrigateBrowseMediaSource | null; - public childIndex: number | null; + public query: MediaQueries | null; + public queryResults: MediaQueriesResults | null; public context: ViewContext | null; constructor(params: ViewParameters) { this.view = params.view; this.camera = params.camera; - this.target = params.target ?? null; - this.childIndex = params.childIndex ?? null; + this.query = params.query ?? null; + this.queryResults = params.queryResults ?? null; this.context = params.context ?? null; } @@ -71,10 +72,13 @@ export class View { !curr || prev.view !== curr.view || prev.camera !== curr.camera || - // When in the live view, the target/childIndex are the events that - // happened in the past -- not reflective of the actual live media viewer. + // When in the live view, the queryResults contain the events that + // happened in the past -- not reflective of the actual live media viewer + // the user is seeing. (curr.view !== 'live' && - (prev.target !== curr.target || prev.childIndex !== curr.childIndex)) + (prev.queryResults !== curr.queryResults || + prev.queryResults?.getSelectedResult() !== + curr.queryResults?.getSelectedResult())) ); } @@ -85,8 +89,8 @@ export class View { return new View({ view: this.view, camera: this.camera, - target: this.target, - childIndex: this.childIndex, + query: this.query?.clone() ?? null, + queryResults: this.queryResults?.clone() ?? null, context: this.context, }); } @@ -100,8 +104,11 @@ export class View { return new View({ view: params.view !== undefined ? params.view : this.view, camera: params.camera !== undefined ? params.camera : this.camera, - target: params.target !== undefined ? params.target : this.target, - childIndex: params.childIndex !== undefined ? params.childIndex : this.childIndex, + query: params.query !== undefined ? params.query : this.query?.clone() ?? null, + queryResults: + params.queryResults !== undefined + ? params.queryResults + : this.queryResults?.clone() ?? null, context: params.context !== undefined ? params.context : this.context, }); } @@ -123,7 +130,7 @@ export class View { */ public removeContext(key: keyof ViewContext): View { if (this.context) { - delete(this.context[key]); + delete this.context[key]; } return this; } @@ -142,20 +149,6 @@ export class View { return ['clips', 'snapshots', 'recordings'].includes(this.view); } - /** - * Get the viewer view given a gallery view. - */ - public getViewerViewForGalleryView(): 'clip' | 'snapshot' | 'recording' | null { - if (this.is('clips')) { - return 'clip'; - } else if (this.is('snapshots')) { - return 'snapshot'; - } else if (this.is('recordings')) { - return 'recording'; - } - return null; - } - /** * Determine if a view is of a piece of media (including the media viewer, * live view, image view -- anything that can create a MediaLoadedInfo event). @@ -172,49 +165,19 @@ export class View { } /** - * Determine if a view is related to a clip or clips. - */ - public isClipRelatedView(): boolean { - return ['clip', 'clips'].includes(this.view); - } - - /** - * Determine if a view is related to a snapshot or snapshots. - */ - public isSnapshotRelatedView(): boolean { - return ['snapshot', 'snapshots'].includes(this.view); - } - - /** - * Determine if a view is related to a recording or recordings. - */ - public isRecordingRelatedView(): boolean { - return ['recording', 'recordings'].includes(this.view); - } - - /** - * Get the media type for this view if available. - * @returns Whether the media is `clips`, `snapshots`, `recordings` or unknown + * Get the default media type for this view if available. + * @returns Whether the default media is `clips`, `snapshots`, `recordings` or unknown * (`null`). */ - public getMediaType(): 'clips' | 'snapshots' | 'recordings' | null { - return this.isClipRelatedView() - ? 'clips' - : this.isSnapshotRelatedView() - ? 'snapshots' - : this.isRecordingRelatedView() - ? 'recordings' - : null; - } - - /** - * Get the media item that should be played. - **/ - get media(): FrigateBrowseMediaSource | null { - if (this.target) { - if (this.target.children && this.childIndex !== null) { - return this.target.children[this.childIndex] ?? null; - } + public getDefaultMediaType(): 'clips' | 'snapshots' | 'recordings' | null { + if (['clip', 'clips'].includes(this.view)) { + return 'clips'; + } + if (['snapshot', 'snapshots'].includes(this.view)) { + return 'snapshots'; + } + if (['recording', 'recordings'].includes(this.view)) { + return 'recordings'; } return null; } diff --git a/tsconfig.json b/tsconfig.json index b5045a7e..0b7a905d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,6 +23,7 @@ // imported by a custom card directly. "globalTags": [ "ha-card", + "ha-combo-box", "ha-icon", "ha-icon-button", "ha-button-menu", diff --git a/yarn.lock b/yarn.lock index f05034d5..790427da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1,3276 +1,5482 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@ampproject/remapping@^2.1.0": - version "2.2.0" - resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" - integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== - dependencies: - "@jridgewell/gen-mapping" "^0.1.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/compat-data@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86" - integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw== - -"@babel/core@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3" - integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-compilation-targets" "^7.19.0" - "@babel/helper-module-transforms" "^7.19.0" - "@babel/helpers" "^7.19.0" - "@babel/parser" "^7.19.0" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - -"@babel/generator@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a" - integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg== - dependencies: - "@babel/types" "^7.19.0" - "@jridgewell/gen-mapping" "^0.3.2" - jsesc "^2.5.1" - -"@babel/helper-annotate-as-pure@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" - integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-compilation-targets@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0" - integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA== - dependencies: - "@babel/compat-data" "^7.19.0" - "@babel/helper-validator-option" "^7.18.6" - browserslist "^4.20.2" - semver "^6.3.0" - -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b" - integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw== - dependencies: - "@babel/helper-annotate-as-pure" "^7.18.6" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/helper-replace-supers" "^7.18.9" - "@babel/helper-split-export-declaration" "^7.18.6" - -"@babel/helper-environment-visitor@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" - integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== - -"@babel/helper-function-name@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz#941574ed5390682e872e52d3f38ce9d1bef4648c" - integrity sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w== - dependencies: - "@babel/template" "^7.18.10" - "@babel/types" "^7.19.0" - -"@babel/helper-hoist-variables@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" - integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-member-expression-to-functions@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815" - integrity sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg== - dependencies: - "@babel/types" "^7.18.9" - -"@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" - integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-module-transforms@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30" - integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-module-imports" "^7.18.6" - "@babel/helper-simple-access" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/helper-validator-identifier" "^7.18.6" - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" - -"@babel/helper-optimise-call-expression@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" - integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf" - integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw== - -"@babel/helper-replace-supers@^7.18.9": - version "7.18.9" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6" - integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ== - dependencies: - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-member-expression-to-functions" "^7.18.9" - "@babel/helper-optimise-call-expression" "^7.18.6" - "@babel/traverse" "^7.18.9" - "@babel/types" "^7.18.9" - -"@babel/helper-simple-access@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" - integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-split-export-declaration@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" - integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== - dependencies: - "@babel/types" "^7.18.6" - -"@babel/helper-string-parser@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" - integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== - -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== - -"@babel/helper-validator-option@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" - integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== - -"@babel/helpers@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18" - integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg== - dependencies: - "@babel/template" "^7.18.10" - "@babel/traverse" "^7.19.0" - "@babel/types" "^7.19.0" - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.18.10", "@babel/parser@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c" - integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw== - -"@babel/plugin-proposal-class-properties@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" - integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - -"@babel/plugin-proposal-decorators@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.19.0.tgz#5a3bc0699ee34117c73c960a5396ffce104c4eaa" - integrity sha512-Bo5nOSjiJccjv00+BrDkmfeBLBi2B0qe8ygj24KdL8VdwtZz+710NCwehF+x/Ng+0mkHx5za2eAofmvVFLF4Fg== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.19.0" - "@babel/helper-plugin-utils" "^7.19.0" - "@babel/helper-replace-supers" "^7.18.9" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/plugin-syntax-decorators" "^7.19.0" - -"@babel/plugin-syntax-decorators@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.19.0.tgz#5f13d1d8fce96951bea01a10424463c9a5b3a599" - integrity sha512-xaBZUEDntt4faL1yN8oIFlhfXeQAWJW7CLKYsHTUqriCUbj8xOra8bfxxKGi/UwExPFBuPdH4XfHc9rGQhrVkQ== - dependencies: - "@babel/helper-plugin-utils" "^7.19.0" - -"@babel/template@^7.18.10": - version "7.18.10" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" - integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/parser" "^7.18.10" - "@babel/types" "^7.18.10" - -"@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed" - integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.19.0" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.19.0" - "@babel/types" "^7.19.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0": - version "7.19.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600" - integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA== - dependencies: - "@babel/helper-string-parser" "^7.18.10" - "@babel/helper-validator-identifier" "^7.18.6" - to-fast-properties "^2.0.0" - -"@cycjimmy/jsmpeg-player@^6.0.4": - version "6.0.4" - resolved "https://registry.yarnpkg.com/@cycjimmy/jsmpeg-player/-/jsmpeg-player-6.0.4.tgz#2ad00118a48bef30db0115ba873b335fa1559ae7" - integrity sha512-w3E1nwSDKPJaBD3bq/aMvR3hgUJpyCrqX2jJbQAjE9g4xk9bRkeAIH1h1mBtdRFMpTzUlE3GX8r44lXEH0dVVQ== - -"@egjs/hammerjs@^2.0.17": - version "2.0.17" - resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124" - integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A== - dependencies: - "@types/hammerjs" "^2.0.36" - -"@eslint/eslintrc@^1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.1.tgz#de0807bfeffc37b964a7d0400e0c348ce5a2543d" - integrity sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.4.0" - globals "^13.15.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@formatjs/ecma402-abstract@1.11.4": - version "1.11.4" - resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.11.4.tgz#b962dfc4ae84361f9f08fbce411b4e4340930eda" - integrity sha512-EBikYFp2JCdIfGEb5G9dyCkTGDmC57KSHhRQOC3aYxoPWVZvfWCDjZwkGYHN7Lis/fmuWl906bnNTJifDQ3sXw== - dependencies: - "@formatjs/intl-localematcher" "0.2.25" - tslib "^2.1.0" - -"@formatjs/fast-memoize@1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-1.2.1.tgz#e6f5aee2e4fd0ca5edba6eba7668e2d855e0fc21" - integrity sha512-Rg0e76nomkz3vF9IPlKeV+Qynok0r7YZjL6syLz4/urSg0IbjPZCB/iYUMNsYA643gh4mgrX3T7KEIFIxJBQeg== - dependencies: - tslib "^2.1.0" - -"@formatjs/icu-messageformat-parser@2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.1.0.tgz#a54293dd7f098d6a6f6a084ab08b6d54a3e8c12d" - integrity sha512-Qxv/lmCN6hKpBSss2uQ8IROVnta2r9jd3ymUEIjm2UyIkUCHVcbUVRGL/KS/wv7876edvsPe+hjHVJ4z8YuVaw== - dependencies: - "@formatjs/ecma402-abstract" "1.11.4" - "@formatjs/icu-skeleton-parser" "1.3.6" - tslib "^2.1.0" - -"@formatjs/icu-skeleton-parser@1.3.6": - version "1.3.6" - resolved "https://registry.yarnpkg.com/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.3.6.tgz#4ce8c0737d6f07b735288177049e97acbf2e8964" - integrity sha512-I96mOxvml/YLrwU2Txnd4klA7V8fRhb6JG/4hm3VMNmeJo1F03IpV2L3wWt7EweqNLES59SZ4d6hVOPCSf80Bg== - dependencies: - "@formatjs/ecma402-abstract" "1.11.4" - tslib "^2.1.0" - -"@formatjs/intl-localematcher@0.2.25": - version "0.2.25" - resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.25.tgz#60892fe1b271ec35ba07a2eb018a2dd7bca6ea3a" - integrity sha512-YmLcX70BxoSopLFdLr1Ds99NdlTI2oWoLbaUW2M406lxOIPzE1KQhRz2fPUkq34xVZQaihCoU29h0KK7An3bhA== - dependencies: - tslib "^2.1.0" - -"@formatjs/intl-utils@^3.8.4": - version "3.8.4" - resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-3.8.4.tgz#291baac91001db428fc3275c515a3e40fbe95945" - integrity sha512-j5C6NyfKevIxsfLK8KwO1C0vvP7k1+h4A9cFpc+cr6mEwCc1sPkr17dzh0Ke6k9U5pQccAQoXdcNBl3IYa4+ZQ== - dependencies: - emojis-list "^3.0.0" - -"@humanwhocodes/config-array@^0.10.4": - version "0.10.4" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" - integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/gitignore-to-minimatch@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" - integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== - -"@humanwhocodes/module-importer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" - integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@jridgewell/gen-mapping@^0.1.0": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" - integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== - dependencies: - "@jridgewell/set-array" "^1.0.0" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@lit-labs/task@^1.1.3": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@lit-labs/task/-/task-1.1.3.tgz#1215ae8ca746ecc895813463b81b2ce4b823098a" - integrity sha512-Ta5rdbmuDaYAZwlkjezCoqcnn+J54XBAGI0X4PsCjycY1nJ/Ntbd+YY/eMdfnu777qrvznuqanTa4r1ptPXXxA== - dependencies: - "@lit/reactive-element" "^1.1.0" - -"@lit/reactive-element@^1.1.0", "@lit/reactive-element@^1.3.0", "@lit/reactive-element@^1.4.0": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@lit/reactive-element/-/reactive-element-1.4.1.tgz#3f587eec5708692135bc9e94cf396130604979f3" - integrity sha512-qDv4851VFSaBWzpS02cXHclo40jsbAjRXnebNXpm0uVg32kCneZPo9RYVQtrTNICtZ+1wAYHu1ZtxWSWMbKrBw== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@rollup/plugin-babel@^5.3.1": - version "5.3.1" - resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz#04bc0608f4aa4b2e4b1aebf284344d0f68fda283" - integrity sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q== - dependencies: - "@babel/helper-module-imports" "^7.10.4" - "@rollup/pluginutils" "^3.1.0" - -"@rollup/plugin-commonjs@^22.0.2": - version "22.0.2" - resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz#ee8ca8415cda30d383b4096aad5222435b4b69b6" - integrity sha512-//NdP6iIwPbMTcazYsiBMbJW7gfmpHom33u1beiIoHDEM0Q9clvtQB1T0efvMqHeKsGohiHo97BCPCkBXdscwg== - dependencies: - "@rollup/pluginutils" "^3.1.0" - commondir "^1.0.1" - estree-walker "^2.0.1" - glob "^7.1.6" - is-reference "^1.2.1" - magic-string "^0.25.7" - resolve "^1.17.0" - -"@rollup/plugin-image@^2.1.1": - version "2.1.1" - resolved "https://registry.yarnpkg.com/@rollup/plugin-image/-/plugin-image-2.1.1.tgz#898d6b59ac0025d7971ef45640ab330cb0663b0c" - integrity sha512-AgP4U85zuQJdUopLUCM+hTf45RepgXeTb8EJsleExVy99dIoYpt3ZlDYJdKmAc2KLkNntCDg6BPJvgJU3uGF+g== - dependencies: - "@rollup/pluginutils" "^3.1.0" - mini-svg-data-uri "^1.2.3" - -"@rollup/plugin-json@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" - integrity sha512-yfLbTdNS6amI/2OpmbiBoW12vngr5NW2jCJVZSBEz+H5KfUJZ2M7sDjk0U6GOOdCWFVScShte29o9NezJ53TPw== - dependencies: - "@rollup/pluginutils" "^3.0.8" - -"@rollup/plugin-node-resolve@^13.3.0": - version "13.3.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.3.0.tgz#da1c5c5ce8316cef96a2f823d111c1e4e498801c" - integrity sha512-Lus8rbUo1eEcnS4yTFKLZrVumLPY+YayBdWXgFSHYhTT2iJbMhoaaBL3xl5NCdeRytErGr8tZ0L71BMRmnlwSw== - dependencies: - "@rollup/pluginutils" "^3.1.0" - "@types/resolve" "1.17.1" - deepmerge "^4.2.2" - is-builtin-module "^3.1.0" - is-module "^1.0.0" - resolve "^1.19.0" - -"@rollup/plugin-replace@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-4.0.0.tgz#e34c457d6a285f0213359740b43f39d969b38a67" - integrity sha512-+rumQFiaNac9y64OHtkHGmdjm7us9bo1PlbgQfdihQtuNxzjpaB064HbRnewUOggLQxVCCyINfStkgmBeQpv1g== - dependencies: - "@rollup/pluginutils" "^3.1.0" - magic-string "^0.25.7" - -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" - integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== - dependencies: - "@types/estree" "0.0.39" - estree-walker "^1.0.1" - picomatch "^2.2.2" - -"@rollup/pluginutils@^4.1.2": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" - integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== - dependencies: - estree-walker "^2.0.1" - picomatch "^2.2.2" - -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/bluebird@^3.5.36": - version "3.5.36" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.36.tgz#00d9301d4dc35c2f6465a8aec634bb533674c652" - integrity sha512-HBNx4lhkxN7bx6P0++W8E289foSu8kO8GCk2unhuVggO+cE7rh9DhZUyPhUxNRG9m+5B5BTKxZQ5ZP92x/mx9Q== - -"@types/cssnano@^5.0.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/cssnano/-/cssnano-5.1.0.tgz#a83529c148dccc984bccbe33563941f0fa6bd1e9" - integrity sha512-ikR+18UpFGgvaWSur4og6SJYF/6QEYHXvrIt36dp81p1MG3cAPTYDMBJGeyWa3LCnqEbgNMHKRb+FP0NrXtoWQ== - dependencies: - cssnano "*" - -"@types/estree@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2" - integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ== - -"@types/estree@0.0.39": - version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== - -"@types/hammerjs@^2.0.36": - version "2.0.41" - resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.41.tgz#f6ecf57d1b12d2befcce00e928a6a097c22980aa" - integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA== - -"@types/json-schema@^7.0.9": - version "7.0.11" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" - integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== - -"@types/json5@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" - integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== - -"@types/lodash-es@^4.17.5": - version "4.17.6" - resolved "https://registry.yarnpkg.com/@types/lodash-es/-/lodash-es-4.17.6.tgz#c2ed4c8320ffa6f11b43eb89e9eaeec65966a0a0" - integrity sha512-R+zTeVUKDdfoRxpAryaQNRKk3105Rrgx2CFRClIgRGaqDTdjsm8h6IYA8ir584W3ePzkZfst5xIgDwYrlh9HLg== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.14.184" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" - integrity sha512-RoZphVtHbxPZizt4IcILciSWiC6dcn+eZ8oX9IWEYfDMcocdd42f7NPI6fQj+6zI8y4E0L7gu2pcZKLGTRaV9Q== - -"@types/node@*": - version "18.7.16" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.16.tgz#0eb3cce1e37c79619943d2fd903919fc30850601" - integrity sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/resolve@1.17.1": - version "1.17.1" - resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" - integrity sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw== - dependencies: - "@types/node" "*" - -"@types/trusted-types@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" - integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg== - -"@typescript-eslint/eslint-plugin@^5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.36.2.tgz#6df092a20e0f9ec748b27f293a12cb39d0c1fe4d" - integrity sha512-OwwR8LRwSnI98tdc2z7mJYgY60gf7I9ZfGjN5EjCwwns9bdTuQfAXcsjSB2wSQ/TVNYSGKf4kzVXbNGaZvwiXw== - dependencies: - "@typescript-eslint/scope-manager" "5.36.2" - "@typescript-eslint/type-utils" "5.36.2" - "@typescript-eslint/utils" "5.36.2" - debug "^4.3.4" - functional-red-black-tree "^1.0.1" - ignore "^5.2.0" - regexpp "^3.2.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.36.2.tgz#3ddf323d3ac85a25295a55fcb9c7a49ab4680ddd" - integrity sha512-qS/Kb0yzy8sR0idFspI9Z6+t7mqk/oRjnAYfewG+VN73opAUvmYL3oPIMmgOX6CnQS6gmVIXGshlb5RY/R22pA== - dependencies: - "@typescript-eslint/scope-manager" "5.36.2" - "@typescript-eslint/types" "5.36.2" - "@typescript-eslint/typescript-estree" "5.36.2" - debug "^4.3.4" - -"@typescript-eslint/scope-manager@5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.36.2.tgz#a75eb588a3879ae659514780831370642505d1cd" - integrity sha512-cNNP51L8SkIFSfce8B1NSUBTJTu2Ts4nWeWbFrdaqjmn9yKrAaJUBHkyTZc0cL06OFHpb+JZq5AUHROS398Orw== - dependencies: - "@typescript-eslint/types" "5.36.2" - "@typescript-eslint/visitor-keys" "5.36.2" - -"@typescript-eslint/type-utils@5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.36.2.tgz#752373f4babf05e993adf2cd543a763632826391" - integrity sha512-rPQtS5rfijUWLouhy6UmyNquKDPhQjKsaKH0WnY6hl/07lasj8gPaH2UD8xWkePn6SC+jW2i9c2DZVDnL+Dokw== - dependencies: - "@typescript-eslint/typescript-estree" "5.36.2" - "@typescript-eslint/utils" "5.36.2" - debug "^4.3.4" - tsutils "^3.21.0" - -"@typescript-eslint/types@5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.36.2.tgz#a5066e500ebcfcee36694186ccc57b955c05faf9" - integrity sha512-9OJSvvwuF1L5eS2EQgFUbECb99F0mwq501w0H0EkYULkhFa19Qq7WFbycdw1PexAc929asupbZcgjVIe6OK/XQ== - -"@typescript-eslint/typescript-estree@5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.36.2.tgz#0c93418b36c53ba0bc34c61fe9405c4d1d8fe560" - integrity sha512-8fyH+RfbKc0mTspfuEjlfqA4YywcwQK2Amcf6TDOwaRLg7Vwdu4bZzyvBZp4bjt1RRjQ5MDnOZahxMrt2l5v9w== - dependencies: - "@typescript-eslint/types" "5.36.2" - "@typescript-eslint/visitor-keys" "5.36.2" - debug "^4.3.4" - globby "^11.1.0" - is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/utils@5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.36.2.tgz#b01a76f0ab244404c7aefc340c5015d5ce6da74c" - integrity sha512-uNcopWonEITX96v9pefk9DC1bWMdkweeSsewJ6GeC7L6j2t0SJywisgkr9wUTtXk90fi2Eljj90HSHm3OGdGRg== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.36.2" - "@typescript-eslint/types" "5.36.2" - "@typescript-eslint/typescript-estree" "5.36.2" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" - -"@typescript-eslint/visitor-keys@5.36.2": - version "5.36.2" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.36.2.tgz#2f8f78da0a3bad3320d2ac24965791ac39dace5a" - integrity sha512-BtRvSR6dEdrNt7Net2/XDjbYKU5Ml6GqJgVfXT0CxTCJlnIqK7rAGreuWKMT2t8cFUT2Msv5oxw0GMRD7T5J7A== - dependencies: - "@typescript-eslint/types" "5.36.2" - eslint-visitor-keys "^3.3.0" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.5.0, acorn@^8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-includes@^3.1.4: - version "3.1.5" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" - integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - get-intrinsic "^1.1.1" - is-string "^1.0.7" - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -array.prototype.flat@^1.2.5: - version "1.3.0" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" - integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.2" - es-shim-unscopables "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserslist@^4.0.0, browserslist@^4.16.6, browserslist@^4.20.2, browserslist@^4.20.3: - version "4.21.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== - dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" - node-releases "^2.0.6" - update-browserslist-db "^1.0.5" - -buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -builtin-modules@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" - integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== - -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -caniuse-api@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" - integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== - dependencies: - browserslist "^4.0.0" - caniuse-lite "^1.0.0" - lodash.memoize "^4.1.2" - lodash.uniq "^4.5.0" - -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001370: - version "1.0.30001393" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356" - integrity sha512-N/od11RX+Gsk+1qY/jbPa0R6zJupEa0lxeBG598EbrtblxVCTJsQwbRBm6+V+rxpc5lHKdsXb9RY83cZIPLseA== - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -"chokidar@>=3.0.0 <4.0.0": - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colord@^2.9.1: - version "2.9.3" - resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" - integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== - -commander@^2.20.0, commander@^2.20.3: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -commondir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" - integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== - -component-emitter@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== - -confusing-browser-globals@^1.0.10: - version "1.0.11" - resolved "https://registry.yarnpkg.com/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz#ae40e9b57cdd3915408a2805ebd3a5585608dc81" - integrity sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA== - -convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -cosmiconfig@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypto@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/crypto/-/crypto-1.0.1.tgz#2af1b7cad8175d24c8a1b0778255794a21803037" - integrity sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig== - -css-declaration-sorter@^6.3.0: - version "6.3.1" - resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec" - integrity sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w== - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -cssfilter@0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae" - integrity sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw== - -cssnano-preset-default@^5.2.12: - version "5.2.12" - resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz#ebe6596ec7030e62c3eb2b3c09f533c0644a9a97" - integrity sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew== - dependencies: - css-declaration-sorter "^6.3.0" - cssnano-utils "^3.1.0" - postcss-calc "^8.2.3" - postcss-colormin "^5.3.0" - postcss-convert-values "^5.1.2" - postcss-discard-comments "^5.1.2" - postcss-discard-duplicates "^5.1.0" - postcss-discard-empty "^5.1.1" - postcss-discard-overridden "^5.1.0" - postcss-merge-longhand "^5.1.6" - postcss-merge-rules "^5.1.2" - postcss-minify-font-values "^5.1.0" - postcss-minify-gradients "^5.1.1" - postcss-minify-params "^5.1.3" - postcss-minify-selectors "^5.2.1" - postcss-normalize-charset "^5.1.0" - postcss-normalize-display-values "^5.1.0" - postcss-normalize-positions "^5.1.1" - postcss-normalize-repeat-style "^5.1.1" - postcss-normalize-string "^5.1.0" - postcss-normalize-timing-functions "^5.1.0" - postcss-normalize-unicode "^5.1.0" - postcss-normalize-url "^5.1.0" - postcss-normalize-whitespace "^5.1.1" - postcss-ordered-values "^5.1.3" - postcss-reduce-initial "^5.1.0" - postcss-reduce-transforms "^5.1.0" - postcss-svgo "^5.1.0" - postcss-unique-selectors "^5.1.1" - -cssnano-utils@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" - integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== - -cssnano@*, cssnano@^5.0.15: - version "5.1.13" - resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.13.tgz#83d0926e72955332dc4802a7070296e6258efc0a" - integrity sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ== - dependencies: - cssnano-preset-default "^5.2.12" - lilconfig "^2.0.3" - yaml "^1.10.2" - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -custom-card-helpers@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/custom-card-helpers/-/custom-card-helpers-1.9.0.tgz#dac7bb7a531101f4c83096b26a8be8ccad70d8c0" - integrity sha512-5IW4OXq3MiiCqDvqeu+MYsM1NmntKW1WfJhyJFsdP2tbzqEI4BOnqRz2qzdp08lE4QLVhYfRLwe0WAqgQVNeFg== - dependencies: - "@formatjs/intl-utils" "^3.8.4" - home-assistant-js-websocket "^6.0.1" - intl-messageformat "^9.11.1" - lit "^2.1.1" - rollup "^2.63.0" - superstruct "^0.15.3" - typescript "^4.5.4" - -date-fns-tz@^1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/date-fns-tz/-/date-fns-tz-1.3.7.tgz#e8e9d2aaceba5f1cc0e677631563081fdcb0e69a" - integrity sha512-1t1b8zyJo+UI8aR+g3iqr5fkUHWpd58VBx8J/ZSQ+w7YrGlw80Ag4sA86qkfCXRBLmMc4I2US+aPMd4uKvwj5g== - -date-fns@^2.29.2: - version "2.29.2" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.2.tgz#0d4b3d0f3dff0f920820a070920f0d9662c51931" - integrity sha512-0VNbwmWJDS/G3ySwFSJA3ayhbURMTJLtwM2DTxf9CWondCnh6DTNlO9JgRSq6ibf4eD0lfMJNBxUdEAHHix+bA== - -debug@^2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== - -deep-is@^0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -define-lazy-prop@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" - integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== - -define-properties@^1.1.3, define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" - integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== - dependencies: - esutils "^2.0.2" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -electron-to-chromium@^1.4.202: - version "1.4.247" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d" - integrity sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw== - -embla-carousel-wheel-gestures@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/embla-carousel-wheel-gestures/-/embla-carousel-wheel-gestures-3.0.0.tgz#a68ad0f76c2bec876354939b7edac00245433e19" - integrity sha512-OxMyRkDxiSHJy1DCInYjpq93q8Xz311ajCEUp/pvWH/odDc+KODLAtVI06U5NiPNIUJWj+IlZvLSVZUtpdTBEQ== - dependencies: - wheel-gestures "^2.2.5" - -embla-carousel@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/embla-carousel/-/embla-carousel-7.0.2.tgz#49660d75d26604de1ca1bb0c0be13e7cae65b0ff" - integrity sha512-6SHKbLJRvcjunb0VT1Aw7YwK0Dw8ecqEa+ppT1j+kHGEg5I4WvUd8SjRkc1NAuQK0PTBD0d567UCmzEAngeAxw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -emojis-list@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" - integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5: - version "1.20.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.2.tgz#8495a07bc56d342a3b8ea3ab01bd986700c2ccb3" - integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.2" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.2" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" - unbox-primitive "^1.0.2" - -es-shim-unscopables@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" - integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== - dependencies: - has "^1.0.3" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -eslint-config-airbnb-base@^15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" - integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== - dependencies: - confusing-browser-globals "^1.0.10" - object.assign "^4.1.2" - object.entries "^1.1.5" - semver "^6.3.0" - -eslint-config-prettier@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== - -eslint-import-resolver-node@^0.3.6: - version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" - integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== - dependencies: - debug "^3.2.7" - resolve "^1.20.0" - -eslint-module-utils@^2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz#4f3e41116aaf13a20792261e61d3a2e7e0583974" - integrity sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA== - dependencies: - debug "^3.2.7" - -eslint-plugin-import@^2.25.4: - version "2.26.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" - integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== - dependencies: - array-includes "^3.1.4" - array.prototype.flat "^1.2.5" - debug "^2.6.9" - doctrine "^2.1.0" - eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.3" - has "^1.0.3" - is-core-module "^2.8.1" - is-glob "^4.0.3" - minimatch "^3.1.2" - object.values "^1.1.5" - resolve "^1.22.0" - tsconfig-paths "^3.14.1" - -eslint-plugin-prettier@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" - integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== - dependencies: - prettier-linter-helpers "^1.0.0" - -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint@^8.23.0: - version "8.23.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.23.0.tgz#a184918d288820179c6041bb3ddcc99ce6eea040" - integrity sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA== - dependencies: - "@eslint/eslintrc" "^1.3.1" - "@humanwhocodes/config-array" "^0.10.4" - "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" - "@humanwhocodes/module-importer" "^1.0.1" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.4.0" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - functional-red-black-tree "^1.0.1" - glob-parent "^6.0.1" - globals "^13.15.0" - globby "^11.1.0" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - -espree@^9.4.0: - version "9.4.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.4.0.tgz#cd4bc3d6e9336c433265fc0aa016fc1aaf182f8a" - integrity sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw== - dependencies: - acorn "^8.8.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -estree-walker@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" - integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== - -estree-walker@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" - integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -eventemitter3@^4.0.4: - version "4.0.7" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" - integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-diff@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" - integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== - -fast-glob@^3.2.9: - version "3.2.12" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80" - integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== - dependencies: - reusify "^1.0.4" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -filter-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" - integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== - -find-cache-dir@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" - integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== - dependencies: - commondir "^1.0.1" - make-dir "^3.0.2" - pkg-dir "^4.1.0" - -find-up@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - -fs-extra@^10.0.0: - version "10.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" - integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - -functions-have-names@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" - integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" - integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3, glob@^7.1.6: - version "7.2.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" - integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.1.1" - once "^1.3.0" - path-is-absolute "^1.0.0" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -globals@^13.15.0: - version "13.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== - dependencies: - type-fest "^0.20.2" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-property-descriptors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" - integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== - dependencies: - get-intrinsic "^1.1.1" - -has-symbols@^1.0.2, has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-tostringtag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" - integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== - dependencies: - has-symbols "^1.0.2" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -home-assistant-js-websocket@^6.0.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-6.1.1.tgz#87ba846753c4fb58a2e5ace6bb15a82689fd0735" - integrity sha512-TnZFzF4mn5F/v0XKUTK2GMQXrn/+eQpgaSDSELl6U0HSwSbFwRhGWLz330YT+hiKMspDflamsye//RPL+zwhDw== - -home-assistant-js-websocket@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/home-assistant-js-websocket/-/home-assistant-js-websocket-8.0.0.tgz#7111da312ac9fafb47d9bdfbcca8ee8fca4cd64d" - integrity sha512-H/JZDnlfXW3OBZK29YhaF3U/2maFcJSvxb0jKQBiN5i4XR+7ApYKALBBh2afLeiFQfy8Ys9AzSBY0kIeBW4Kzw== - -icss-utils@^5.0.0, icss-utils@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" - integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== - -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - -immutable@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef" - integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - -intl-messageformat@^9.11.1: - version "9.13.0" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.13.0.tgz#97360b73bd82212e4f6005c712a4a16053165468" - integrity sha512-7sGC7QnSQGa5LZP7bXLDhVDtQOeKGeBFGHF2Y8LVBwYZoQZCgWeKoPGTa5GMG8g/TzDgeXuYJQis7Ggiw2xTOw== - dependencies: - "@formatjs/ecma402-abstract" "1.11.4" - "@formatjs/fast-memoize" "1.2.1" - "@formatjs/icu-messageformat-parser" "2.1.0" - tslib "^2.1.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-builtin-module@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.0.tgz#bb0310dfe881f144ca83f30100ceb10cf58835e0" - integrity sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw== - dependencies: - builtin-modules "^3.3.0" - -is-callable@^1.1.4, is-callable@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-core-module@^2.8.1, is-core-module@^2.9.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" - integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== - dependencies: - has "^1.0.3" - -is-date-object@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" - integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== - dependencies: - has-tostringtag "^1.0.0" - -is-docker@^2.0.0, is-docker@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" - integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== - -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-reference@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" - integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ== - dependencies: - "@types/estree" "*" - -is-regex@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" - integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -jest-worker@^26.2.1: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - -json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -keycharm@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/keycharm/-/keycharm-0.4.0.tgz#8d684ea9cc01379a07fbddee33ff32d97f5ae2a7" - integrity sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ== - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -lilconfig@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" - integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -lit-element@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/lit-element/-/lit-element-3.2.2.tgz#d148ab6bf4c53a33f707a5168e087725499e5f2b" - integrity sha512-6ZgxBR9KNroqKb6+htkyBwD90XGRiqKDHVrW/Eh0EZ+l+iC+u+v+w3/BA5NGi4nizAVHGYvQBHUDuSmLjPp7NQ== - dependencies: - "@lit/reactive-element" "^1.3.0" - lit-html "^2.2.0" - -lit-html@^2.2.0, lit-html@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/lit-html/-/lit-html-2.3.1.tgz#56f15104ea75c0a702904893e3409d0e89e2a2b9" - integrity sha512-FyKH6LTW6aBdkfNhNSHyZTnLgJSTe5hMk7HFtc/+DcN1w74C215q8B+Cfxc2OuIEpBNcEKxgF64qL8as30FDHA== - dependencies: - "@types/trusted-types" "^2.0.2" - -lit@^2.1.1, lit@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/lit/-/lit-2.3.1.tgz#2cf1c2042da1e44c7a7cc72dff2d72303fd26f48" - integrity sha512-TejktDR4mqG3qB32Y8Lm5Lye3c8SUehqz7qRsxe1PqGYL6me2Ef+jeQAEqh20BnnGncv4Yxy2njEIT0kzK1WCw== - dependencies: - "@lit/reactive-element" "^1.4.0" - lit-element "^3.2.0" - lit-html "^2.3.0" - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash-es@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== - -lodash.memoize@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" - integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -magic-string@^0.25.7: - version "0.25.9" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" - integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ== - dependencies: - sourcemap-codec "^1.4.8" - -make-dir@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@>=2.4.6: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" - integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== - -mini-svg-data-uri@^1.2.3: - version "1.4.4" - resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939" - integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg== - -minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimist@^1.2.0, minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -moment@^2.29.4: - version "2.29.4" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" - integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -normalize-url@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" - integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -object-inspect@^1.12.2, object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -object-keys@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" - integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== - -object.assign@^4.1.2, object.assign@^4.1.4: - version "4.1.4" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" - integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - has-symbols "^1.0.3" - object-keys "^1.1.1" - -object.entries@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" - integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - -object.values@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" - integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== - dependencies: - wrappy "1" - -open@^8.4.0: - version "8.4.0" - resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" - integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== - dependencies: - define-lazy-prop "^2.0.0" - is-docker "^2.1.1" - is-wsl "^2.2.0" - -opener@1: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-queue@^6.6.2: - version "6.6.2" - resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426" - integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ== - dependencies: - eventemitter3 "^4.0.4" - p-timeout "^3.2.0" - -p-timeout@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" - integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== - dependencies: - p-finally "^1.0.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pkg-dir@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -postcss-calc@^8.2.3: - version "8.2.4" - resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" - integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== - dependencies: - postcss-selector-parser "^6.0.9" - postcss-value-parser "^4.2.0" - -postcss-colormin@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" - integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - colord "^2.9.1" - postcss-value-parser "^4.2.0" - -postcss-convert-values@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz#31586df4e184c2e8890e8b34a0b9355313f503ab" - integrity sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g== - dependencies: - browserslist "^4.20.3" - postcss-value-parser "^4.2.0" - -postcss-discard-comments@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" - integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== - -postcss-discard-duplicates@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" - integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== - -postcss-discard-empty@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" - integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== - -postcss-discard-overridden@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" - integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== - -postcss-merge-longhand@^5.1.6: - version "5.1.6" - resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz#f378a8a7e55766b7b644f48e5d8c789ed7ed51ce" - integrity sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw== - dependencies: - postcss-value-parser "^4.2.0" - stylehacks "^5.1.0" - -postcss-merge-rules@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz#7049a14d4211045412116d79b751def4484473a5" - integrity sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - cssnano-utils "^3.1.0" - postcss-selector-parser "^6.0.5" - -postcss-minify-font-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" - integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-minify-gradients@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" - integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== - dependencies: - colord "^2.9.1" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-params@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz#ac41a6465be2db735099bbd1798d85079a6dc1f9" - integrity sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg== - dependencies: - browserslist "^4.16.6" - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-minify-selectors@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" - integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-modules-extract-imports@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" - integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== - -postcss-modules-local-by-default@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" - integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== - dependencies: - icss-utils "^5.0.0" - postcss-selector-parser "^6.0.2" - postcss-value-parser "^4.1.0" - -postcss-modules-scope@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" - integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== - dependencies: - postcss-selector-parser "^6.0.4" - -postcss-modules-values@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" - integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== - dependencies: - icss-utils "^5.0.0" - -postcss-normalize-charset@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" - integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== - -postcss-normalize-display-values@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" - integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-positions@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" - integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-repeat-style@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" - integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-string@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" - integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-timing-functions@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" - integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-normalize-unicode@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" - integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== - dependencies: - browserslist "^4.16.6" - postcss-value-parser "^4.2.0" - -postcss-normalize-url@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" - integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== - dependencies: - normalize-url "^6.0.1" - postcss-value-parser "^4.2.0" - -postcss-normalize-whitespace@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" - integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-ordered-values@^5.1.3: - version "5.1.3" - resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" - integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== - dependencies: - cssnano-utils "^3.1.0" - postcss-value-parser "^4.2.0" - -postcss-reduce-initial@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" - integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== - dependencies: - browserslist "^4.16.6" - caniuse-api "^3.0.0" - -postcss-reduce-transforms@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" - integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== - dependencies: - postcss-value-parser "^4.2.0" - -postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: - version "6.0.10" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" - integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-svgo@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" - integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== - dependencies: - postcss-value-parser "^4.2.0" - svgo "^2.7.0" - -postcss-unique-selectors@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" - integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== - dependencies: - postcss-selector-parser "^6.0.5" - -postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.4.5: - version "8.4.16" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" - integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== - dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prettier-linter-helpers@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" - integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== - dependencies: - fast-diff "^1.1.2" - -prettier@^2.6.0: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== - -propagating-hammerjs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/propagating-hammerjs/-/propagating-hammerjs-2.0.1.tgz#2ba5d491d8734944cc0ace9ad2ae9660232cde5d" - integrity sha512-PH3zG5whbSxMocphXJzVtvKr+vWAgfkqVvtuwjSJ/apmEACUoiw6auBAT5HYXpZOR0eGcTAfYG5Yl8h91O5Elg== - -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -query-string@^7.1.0: - version "7.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.1.tgz#754620669db978625a90f635f12617c271a088e1" - integrity sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w== - dependencies: - decode-uri-component "^0.2.0" - filter-obj "^1.1.0" - split-on-first "^1.0.0" - strict-uri-encode "^2.0.0" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-lru@^6.1.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-6.1.1.tgz#f8e5bf9010376c126c80c1a62827a526c0e60adf" - integrity sha512-S27GBT+F0NTRiehtbrgaSE1idUAJ5bX8dPAQTdylEyNlrdcH5X4Lz7Edz3DYzecbsCluD5zO8ZNEe04z3D3u6Q== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.21.0, resolve@^1.22.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rollup-plugin-git-info@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-git-info/-/rollup-plugin-git-info-1.0.0.tgz#f6d9f474718d9453eeb5049772a24ca4b9ac310e" - integrity sha512-AL2jzfDvOLze+Sakz3yuQjq9wY/wIj3j49yYnhuXtr6qP4qKzD039haPFa8uaLb0rzDBj2P5S8fWvJyYeIjOVw== - dependencies: - "@rollup/plugin-json" "^4.1.0" - -rollup-plugin-serve@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/rollup-plugin-serve/-/rollup-plugin-serve-2.0.1.tgz#79d2813baeae48888ebbc608418afc68f938eab8" - integrity sha512-/9lnzylTMwbXNQUwWAO0DIxW145wl+rmikwFXgujLJ5N9bRfWcjP+qd1XM5wxSiw8ZlBAlFy/n2zRvzcG21bBw== - dependencies: - mime ">=2.4.6" - opener "1" - -rollup-plugin-styles@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-styles/-/rollup-plugin-styles-4.0.0.tgz#472f104d33edda27fb089b63a315e31531611826" - integrity sha512-A2K2sao84OsTmDxXG83JTCdXWrmgvQkkI38XDat46rdtpGMRm9tSYqeCdlwwGDJF4kKIafhV1mUidqu8MxUGig== - dependencies: - "@rollup/pluginutils" "^4.1.2" - "@types/cssnano" "^5.0.0" - cosmiconfig "^7.0.1" - cssnano "^5.0.15" - fs-extra "^10.0.0" - icss-utils "^5.1.0" - mime-types "^2.1.34" - p-queue "^6.6.2" - postcss "^8.4.5" - postcss-modules-extract-imports "^3.0.0" - postcss-modules-local-by-default "^4.0.0" - postcss-modules-scope "^3.0.0" - postcss-modules-values "^4.0.0" - postcss-value-parser "^4.2.0" - query-string "^7.1.0" - resolve "^1.21.0" - source-map-js "^1.0.1" - tslib "^2.3.1" - -rollup-plugin-terser@^7.0.2: - version "7.0.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d" - integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ== - dependencies: - "@babel/code-frame" "^7.10.4" - jest-worker "^26.2.1" - serialize-javascript "^4.0.0" - terser "^5.0.0" - -rollup-plugin-typescript2@^0.33.0: - version "0.33.0" - resolved "https://registry.yarnpkg.com/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.33.0.tgz#2bb943f83e9d3a4c61a19aed5bee5bcff28d16a3" - integrity sha512-7ZXoZeX93kNb4/ICzOi2AlperVV6cAsNz8THqrbz+KNvpn47P2F/nFdK/BGhkoOsOwuYDuY57vccdZZrcd8/dA== - dependencies: - "@rollup/pluginutils" "^4.1.2" - find-cache-dir "^3.3.2" - fs-extra "^10.0.0" - tslib "^2.4.0" - -rollup-plugin-visualizer@^5.8.2: - version "5.8.2" - resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.8.2.tgz#25a4cc6431aba2eba8ecb7c99d8c3623d493e4bd" - integrity sha512-Fh7KoAa7FVVOojmyyX9ro7fBSR7mPG2cgfDbA877HM4IeJJtSZO+I/R3h/u6TB8wVP5J4pXPpTaRMSREyqCS3g== - dependencies: - open "^8.4.0" - source-map "^0.7.4" - yargs "^17.5.1" - -rollup@^2.63.0, rollup@^2.79.0: - version "2.79.0" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.0.tgz#9177992c9f09eb58c5e56cbfa641607a12b57ce2" - integrity sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA== - optionalDependencies: - fsevents "~2.3.2" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@^5.1.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -sass@^1.54.9: - version "1.54.9" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.54.9.tgz#b05f14ed572869218d1a76961de60cd647221762" - integrity sha512-xb1hjASzEH+0L0WI9oFjqhRi51t/gagWnxLiwUNMltA0Ab6jIDkAacgKiGYKM9Jhy109osM7woEEai6SXeJo5Q== - dependencies: - chokidar ">=3.0.0 <4.0.0" - immutable "^4.0.0" - source-map-js ">=0.6.2 <2.0.0" - -screenfull@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-6.0.2.tgz#3dbe4b8c4f8f49fb8e33caa8f69d0bca730ab238" - integrity sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw== - -semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.7: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -serialize-javascript@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" - integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== - dependencies: - randombytes "^2.1.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -side-drawer@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/side-drawer/-/side-drawer-3.1.0.tgz#8542c5379c1dd82cb2f59fd27de6fa8f778c55ba" - integrity sha512-muiYSSJehoKOKuYXXGUKFrUZTM4ndUzHEdWK1C9ClIMCjWtp7b5oxMACEr1DLYTr8MABlD71u2UheR4Ru8o2vQ== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.4: - version "0.7.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" - integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== - -sourcemap-codec@^1.4.8: - version "1.4.8" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" - integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== - -split-on-first@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" - integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -strict-uri-encode@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" - integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -string.prototype.trimstart@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" - integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" - integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -stylehacks@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" - integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== - dependencies: - browserslist "^4.16.6" - postcss-selector-parser "^6.0.4" - -superstruct@^0.15.3: - version "0.15.5" - resolved "https://registry.yarnpkg.com/superstruct/-/superstruct-0.15.5.tgz#0f0a8d3ce31313f0d84c6096cd4fa1bfdedc9dab" - integrity sha512-4AOeU+P5UuE/4nOUkmcQdW5y7i9ndt1cQd/3iUe+LTz3RxESf/W/5lg4B74HbDMMv8PHnPnGCQFH45kBcrQYoQ== - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -svgo@^2.7.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - -terser@^5.0.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.15.0.tgz#e16967894eeba6e1091509ec83f0c60e179f2425" - integrity sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -ts-toolbelt@^9.6.0: - version "9.6.0" - resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5" - integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w== - -tsconfig-paths@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" - integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== - dependencies: - "@types/json5" "^0.0.29" - json5 "^1.0.1" - minimist "^1.2.6" - strip-bom "^3.0.0" - -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@^2.1.0, tslib@^2.3.1, tslib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -typescript@^4.5.4, typescript@^4.8.3: - version "4.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.3.tgz#d59344522c4bc464a65a730ac695007fdb66dd88" - integrity sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig== - -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -update-browserslist-db@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz#16279639cff1d0f800b14792de43d97df2d11b7d" - integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -vis-data@^7.1.3: - version "7.1.4" - resolved "https://registry.yarnpkg.com/vis-data/-/vis-data-7.1.4.tgz#90e5e796a79e1901de14c0808fb32a1a0735c1dc" - integrity sha512-usy+ePX1XnArNvJ5BavQod7YRuGQE1pjFl+pu7IS6rCom2EBoG0o1ZzCqf3l5US6MW51kYkLR+efxRbnjxNl7w== - -vis-timeline@^7.7.0: - version "7.7.0" - resolved "https://registry.yarnpkg.com/vis-timeline/-/vis-timeline-7.7.0.tgz#50c13072b77d7b8a48029fccb3077be7119c614b" - integrity sha512-et5xobQTEp7i8lqcEjgMWoGE4s4qn+2VtEJ35uRZiL5Y3qRzi84bCTkUKAjOM/HTzVFiLTWET+DZZi1iHYriuA== - -vis-util@^5.0.2: - version "5.0.3" - resolved "https://registry.yarnpkg.com/vis-util/-/vis-util-5.0.3.tgz#6a9d67c6ed92f2bfa67e50c20166bb5f59ac07bf" - integrity sha512-Wf9STUcFrDzK4/Zr7B6epW2Kvm3ORNWF+WiwEz2dpf5RdWkLUXFSbLcuB88n1W6tCdFwVN+v3V4/Xmn9PeL39g== - -wheel-gestures@^2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/wheel-gestures/-/wheel-gestures-2.2.5.tgz#d4a7cdcd402500de26f65aabc8553b657515fb7d" - integrity sha512-01BlXRjBVt5Gy0VH2rRqMeccHruTGq2Gd1fx0UqCPN8KDeDCmlTXsRGc/hXJk6W+wnO9CNJp2xfntMNRArBxKQ== - -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -word-wrap@^1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== - -xss@^1.0.14: - version "1.0.14" - resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.14.tgz#4f3efbde75ad0d82e9921cc3c95e6590dd336694" - integrity sha512-og7TEJhXvn1a7kzZGQ7ETjdQVS2UfZyTlsEdDOqvQF7GoxNfY+0YLCzBy1kPdsDDx4QuNAonQPddpsn6Xl/7sw== - dependencies: - commander "^2.20.3" - cssfilter "0.0.10" - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0, yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@^21.0.0: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.5.1: - version "17.5.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== - -zod@^3.19.0: - version "3.19.0" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.19.0.tgz#a9303fcaed0950a3dc57bf35cc11f6282d4f52f2" - integrity sha512-Yw0qvUsCNGBe5YacikdMt5gVYeUuaEFVDIHKMfElrGSaBhwR3CQK6vOzgfAJOjTdGIhEeoaj8GtT+NDZrepZbw== +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 6 + cacheKey: 8 + +"@ampproject/remapping@npm:^2.1.0": + version: 2.2.0 + resolution: "@ampproject/remapping@npm:2.2.0" + dependencies: + "@jridgewell/gen-mapping": ^0.1.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: d74d170d06468913921d72430259424b7e4c826b5a7d39ff839a29d547efb97dc577caa8ba3fb5cf023624e9af9d09651afc3d4112a45e2050328abc9b3a2292 + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/code-frame@npm:7.18.6" + dependencies: + "@babel/highlight": ^7.18.6 + checksum: 195e2be3172d7684bf95cff69ae3b7a15a9841ea9d27d3c843662d50cdd7d6470fd9c8e64be84d031117e4a4083486effba39f9aef6bbb2c89f7f21bcfba33ba + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.20.5": + version: 7.20.14 + resolution: "@babel/compat-data@npm:7.20.14" + checksum: 6c9efe36232094e4ad0b70d165587f21ca718e5d011f7a52a77a18502a7524e90e2855aa5a2e086395bcfd21bd2c7c99128dcd8d9fdffe94316b72acf5c66f2c + languageName: node + linkType: hard + +"@babel/core@npm:^7.19.0": + version: 7.20.12 + resolution: "@babel/core@npm:7.20.12" + dependencies: + "@ampproject/remapping": ^2.1.0 + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.20.7 + "@babel/helper-compilation-targets": ^7.20.7 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helpers": ^7.20.7 + "@babel/parser": ^7.20.7 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.12 + "@babel/types": ^7.20.7 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.2 + semver: ^6.3.0 + checksum: 62e6c3e2149a70b5c9729ef5f0d3e2e97e9dcde89fc039c8d8e3463d5d7ba9b29ee84d10faf79b61532ac1645aa62f2bd42338320617e6e3a8a4d8e2a27076e7 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.20.7": + version: 7.20.14 + resolution: "@babel/generator@npm:7.20.14" + dependencies: + "@babel/types": ^7.20.7 + "@jridgewell/gen-mapping": ^0.3.2 + jsesc: ^2.5.1 + checksum: 5f6aa2d86af26e76d276923a5c34191124a119b16ee9ccc34aef654a7dec84fbd7d2daed2e6458a6a06bf87f3661deb77c9fea59b8f67faff5c90793c96d76d6 + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: 88ccd15ced475ef2243fdd3b2916a29ea54c5db3cd0cfabf9d1d29ff6e63b7f7cd1c27264137d7a40ac2e978b9b9a542c332e78f40eb72abe737a7400788fc1b + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-compilation-targets@npm:7.20.7" + dependencies: + "@babel/compat-data": ^7.20.5 + "@babel/helper-validator-option": ^7.18.6 + browserslist: ^4.21.3 + lru-cache: ^5.1.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 8c32c873ba86e2e1805b30e0807abd07188acbe00ebb97576f0b09061cc65007f1312b589eccb4349c5a8c7f8bb9f2ab199d41da7030bf103d9f347dcd3a3cf4 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.20.12": + version: 7.20.12 + resolution: "@babel/helper-create-class-features-plugin@npm:7.20.12" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.19.0 + "@babel/helper-member-expression-to-functions": ^7.20.7 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/helper-split-export-declaration": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 1e9ed4243b75278fa24deb40dc62bf537b79307987223a2d2d2ae5abf7ba6dc8435d6e3bb55d52ceb30d3e1eba88e7eb6a1885a8bb519e5cfc3e9dedb97d43e6 + languageName: node + linkType: hard + +"@babel/helper-environment-visitor@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/helper-environment-visitor@npm:7.18.9" + checksum: b25101f6162ddca2d12da73942c08ad203d7668e06663df685634a8fde54a98bc015f6f62938e8554457a592a024108d45b8f3e651fd6dcdb877275b73cc4420 + languageName: node + linkType: hard + +"@babel/helper-function-name@npm:^7.19.0": + version: 7.19.0 + resolution: "@babel/helper-function-name@npm:7.19.0" + dependencies: + "@babel/template": ^7.18.10 + "@babel/types": ^7.19.0 + checksum: eac1f5db428ba546270c2b8d750c24eb528b8fcfe50c81de2e0bdebf0e20f24bec688d4331533b782e4a907fad435244621ca2193cfcf80a86731299840e0f6e + languageName: node + linkType: hard + +"@babel/helper-hoist-variables@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-hoist-variables@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f + languageName: node + linkType: hard + +"@babel/helper-member-expression-to-functions@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-member-expression-to-functions@npm:7.20.7" + dependencies: + "@babel/types": ^7.20.7 + checksum: cec17aab7e964830b0146e575bd141127032319f26ed864a65b35abd75ad618d264d3e11449b9b4e29cfd95bb1a7e774afddd4884fdcc29c36ac9cbd2b66359f + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-module-imports@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: f393f8a3b3304b1b7a288a38c10989de754f01d29caf62ce7c4e5835daf0a27b81f3ac687d9d2780d39685aae7b55267324b512150e7b2be967b0c493b6a1def + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.20.11": + version: 7.20.11 + resolution: "@babel/helper-module-transforms@npm:7.20.11" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-simple-access": ^7.20.2 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/helper-validator-identifier": ^7.19.1 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.10 + "@babel/types": ^7.20.7 + checksum: 29319ebafa693d48756c6ba0d871677bb0037e0da084fbe221a17c38d57093fc8aa38543c07d76e788266a937976e37ab4901971ca7f237c5ab45f524b9ecca0 + languageName: node + linkType: hard + +"@babel/helper-optimise-call-expression@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-optimise-call-expression@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: e518fe8418571405e21644cfb39cf694f30b6c47b10b006609a92469ae8b8775cbff56f0b19732343e2ea910641091c5a2dc73b56ceba04e116a33b0f8bd2fbd + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2": + version: 7.20.2 + resolution: "@babel/helper-plugin-utils@npm:7.20.2" + checksum: f6cae53b7fdb1bf3abd50fa61b10b4470985b400cc794d92635da1e7077bb19729f626adc0741b69403d9b6e411cddddb9c0157a709cc7c4eeb41e663be5d74b + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-replace-supers@npm:7.20.7" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-member-expression-to-functions": ^7.20.7 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: b8e0087c9b0c1446e3c6f3f72b73b7e03559c6b570e2cfbe62c738676d9ebd8c369a708cf1a564ef88113b4330750a50232ee1131d303d478b7a5e65e46fbc7c + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.20.2": + version: 7.20.2 + resolution: "@babel/helper-simple-access@npm:7.20.2" + dependencies: + "@babel/types": ^7.20.2 + checksum: ad1e96ee2e5f654ffee2369a586e5e8d2722bf2d8b028a121b4c33ebae47253f64d420157b9f0a8927aea3a9e0f18c0103e74fdd531815cf3650a0a4adca11a1 + languageName: node + linkType: hard + +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0": + version: 7.20.0 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.20.0" + dependencies: + "@babel/types": ^7.20.0 + checksum: 34da8c832d1c8a546e45d5c1d59755459ffe43629436707079989599b91e8c19e50e73af7a4bd09c95402d389266731b0d9c5f69e372d8ebd3a709c05c80d7dd + languageName: node + linkType: hard + +"@babel/helper-split-export-declaration@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-split-export-declaration@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: c6d3dede53878f6be1d869e03e9ffbbb36f4897c7cc1527dc96c56d127d834ffe4520a6f7e467f5b6f3c2843ea0e81a7819d66ae02f707f6ac057f3d57943a2b + languageName: node + linkType: hard + +"@babel/helper-string-parser@npm:^7.19.4": + version: 7.19.4 + resolution: "@babel/helper-string-parser@npm:7.19.4" + checksum: b2f8a3920b30dfac81ec282ac4ad9598ea170648f8254b10f475abe6d944808fb006aab325d3eb5a8ad3bea8dfa888cfa6ef471050dae5748497c110ec060943 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/helper-validator-identifier@npm:7.19.1" + checksum: 0eca5e86a729162af569b46c6c41a63e18b43dbe09fda1d2a3c8924f7d617116af39cac5e4cd5d431bb760b4dca3c0970e0c444789b1db42bcf1fa41fbad0a3a + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-validator-option@npm:7.18.6" + checksum: f9cc6eb7cc5d759c5abf006402180f8d5e4251e9198197428a97e05d65eb2f8ae5a0ce73b1dfd2d35af41d0eb780627a64edf98a4e71f064eeeacef8de58f2cf + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.20.7": + version: 7.20.13 + resolution: "@babel/helpers@npm:7.20.13" + dependencies: + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.13 + "@babel/types": ^7.20.7 + checksum: d62076fa834f342798f8c3fd7aec0870cc1725d273d99e540cbaa8d6c3ed10258228dd14601c8e66bfeabbb9424c3b31090ecc467fe855f7bd72c4734df7fb09 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/highlight@npm:7.18.6" + dependencies: + "@babel/helper-validator-identifier": ^7.18.6 + chalk: ^2.0.0 + js-tokens: ^4.0.0 + checksum: 92d8ee61549de5ff5120e945e774728e5ccd57fd3b2ed6eace020ec744823d4a98e242be1453d21764a30a14769ecd62170fba28539b211799bbaf232bbb2789 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.20.13, @babel/parser@npm:^7.20.7": + version: 7.20.15 + resolution: "@babel/parser@npm:7.20.15" + bin: + parser: ./bin/babel-parser.js + checksum: 1d0f47ca67ff2652f1c0ff1570bed8deccbc4b53509e7cd73476af9cc7ed23480c99f1179bd6d0be01612368b92b39e206d330ad6054009d699934848a89298b + languageName: node + linkType: hard + +"@babel/plugin-proposal-class-properties@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 49a78a2773ec0db56e915d9797e44fd079ab8a9b2e1716e0df07c92532f2c65d76aeda9543883916b8e0ff13606afeffa67c5b93d05b607bc87653ad18a91422 + languageName: node + linkType: hard + +"@babel/plugin-proposal-decorators@npm:^7.19.0": + version: 7.20.13 + resolution: "@babel/plugin-proposal-decorators@npm:7.20.13" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.20.12 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/plugin-syntax-decorators": ^7.19.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 445723b410627d52ab2d195589eb9fe5fbd66a00ebfc9bedcf63b6cbfdfc42e163d77ac391f8738ab9f632779e6f2aa427fe468fbbd6661177ef0cdca735a7d5 + languageName: node + linkType: hard + +"@babel/plugin-syntax-decorators@npm:^7.19.0": + version: 7.19.0 + resolution: "@babel/plugin-syntax-decorators@npm:7.19.0" + dependencies: + "@babel/helper-plugin-utils": ^7.19.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 105a13d581a8643ba145d4d0d31f34a492b352defa5b155e785702da6ce9c3ff0c1843ba9bee176e35f6e38afa19dc7bd12c120220af0495de4b128f1dd27f6e + languageName: node + linkType: hard + +"@babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/template@npm:7.20.7" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 2eb1a0ab8d415078776bceb3473d07ab746e6bb4c2f6ca46ee70efb284d75c4a32bb0cd6f4f4946dec9711f9c0780e8e5d64b743208deac6f8e9858afadc349e + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.20.10, @babel/traverse@npm:^7.20.12, @babel/traverse@npm:^7.20.13, @babel/traverse@npm:^7.20.7": + version: 7.20.13 + resolution: "@babel/traverse@npm:7.20.13" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/generator": ^7.20.7 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.19.0 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.20.13 + "@babel/types": ^7.20.7 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: 30ca6e0bd18233fda48fa09315efd14dfc61dcf5b8fa3712b343bfc61b32bc63b5e85ea1773cc9576c9b293b96f46b4589aaeb0a52e1f3eeac4edc076d049fc7 + languageName: node + linkType: hard + +"@babel/types@npm:^7.18.6, @babel/types@npm:^7.19.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.20.7, @babel/types@npm:^7.8.3": + version: 7.20.7 + resolution: "@babel/types@npm:7.20.7" + dependencies: + "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: b39af241f0b72bba67fd6d0d23914f6faec8c0eba8015c181cbd5ea92e59fc91a52a1ab490d3520c7dbd19ddb9ebb76c476308f6388764f16d8201e37fae6811 + languageName: node + linkType: hard + +"@cycjimmy/jsmpeg-player@npm:^6.0.4": + version: 6.0.5 + resolution: "@cycjimmy/jsmpeg-player@npm:6.0.5" + checksum: c83c4cc2060f75d3933c889c92e6261fe207bb79e3af41538be3809a33188875363266b87cb7f70c34f3aaaf475615813ec0520b70c6eae0503f526857b4b4c0 + languageName: node + linkType: hard + +"@duetds/date-picker@npm:^1.4.0": + version: 1.4.0 + resolution: "@duetds/date-picker@npm:1.4.0" + dependencies: + "@stencil/core": ^2.3.0 + checksum: a7b82bbf207768b6f1ad1e38893ec21ef360d48f155d5c29863cc83414adaf2537f279dec2c146631967e9ea4761eb026d78e209aa1054d3a42e31deaad3a4fc + languageName: node + linkType: hard + +"@egjs/hammerjs@npm:^2.0.17": + version: 2.0.17 + resolution: "@egjs/hammerjs@npm:2.0.17" + dependencies: + "@types/hammerjs": ^2.0.36 + checksum: 8945137cec5837edd70af3f2e0ea621543eb0aa3b667e6269ec6485350f4d120c2434b37c7c30b1cf42a65275dd61c1f24626749c616696d3956ac0c008c4766 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^1.4.1": + version: 1.4.1 + resolution: "@eslint/eslintrc@npm:1.4.1" + dependencies: + ajv: ^6.12.4 + debug: ^4.3.2 + espree: ^9.4.0 + globals: ^13.19.0 + ignore: ^5.2.0 + import-fresh: ^3.2.1 + js-yaml: ^4.1.0 + minimatch: ^3.1.2 + strip-json-comments: ^3.1.1 + checksum: cd3e5a8683db604739938b1c1c8b77927dc04fce3e28e0c88e7f2cd4900b89466baf83dfbad76b2b9e4d2746abdd00dd3f9da544d3e311633d8693f327d04cd7 + languageName: node + linkType: hard + +"@formatjs/ecma402-abstract@npm:1.11.4": + version: 1.11.4 + resolution: "@formatjs/ecma402-abstract@npm:1.11.4" + dependencies: + "@formatjs/intl-localematcher": 0.2.25 + tslib: ^2.1.0 + checksum: 05dbe1c6457acfe9cdc0fc770940260e19fa588be6c655b1ff1697506348dac1eee9b249b64e6544531174d07a5a74a9e75f68430947cfdc074ebe8e3c86f86f + languageName: node + linkType: hard + +"@formatjs/fast-memoize@npm:1.2.1": + version: 1.2.1 + resolution: "@formatjs/fast-memoize@npm:1.2.1" + dependencies: + tslib: ^2.1.0 + checksum: 7df9e941142be16e5862afe7387926cec44ec136d2c2f9a7e1598cb6c8c23a65e420ed90251ec9b48df083f5473b10d6fbbee2e9fc7233d5bf1f27efffba59a7 + languageName: node + linkType: hard + +"@formatjs/icu-messageformat-parser@npm:2.1.0": + version: 2.1.0 + resolution: "@formatjs/icu-messageformat-parser@npm:2.1.0" + dependencies: + "@formatjs/ecma402-abstract": 1.11.4 + "@formatjs/icu-skeleton-parser": 1.3.6 + tslib: ^2.1.0 + checksum: 8dab4d102b334dd7ab25b85817b074f1b54845d02f9ef9fa5a1fa6a9723176ad28a59d845fdc0eeedb868b891e61a4c530384b123db29f5b14e1f3f8b207373f + languageName: node + linkType: hard + +"@formatjs/icu-skeleton-parser@npm:1.3.6": + version: 1.3.6 + resolution: "@formatjs/icu-skeleton-parser@npm:1.3.6" + dependencies: + "@formatjs/ecma402-abstract": 1.11.4 + tslib: ^2.1.0 + checksum: cce2d8bea54f0096c557dc03920bfe4785893e60962313fab9eeee41f0b411d38b9d45852882b19f261417d730362c8685bea6ba5ac1e2dee141f030cda624e9 + languageName: node + linkType: hard + +"@formatjs/intl-localematcher@npm:0.2.25": + version: 0.2.25 + resolution: "@formatjs/intl-localematcher@npm:0.2.25" + dependencies: + tslib: ^2.1.0 + checksum: ee00ddc23317dc47a58831aaca5112e101d8bb1f38adc0ecfe1a9d7e008d0bb1091519f07e1d7d805b0c1e28f2c3e75f697ae479e22423445814412c7669284c + languageName: node + linkType: hard + +"@formatjs/intl-utils@npm:^3.8.4": + version: 3.8.4 + resolution: "@formatjs/intl-utils@npm:3.8.4" + dependencies: + emojis-list: ^3.0.0 + checksum: e38a98d391ec68d8abc4bb1824426bd804dfb022ce61be004459faec69f7843ddb3733bae8d0dce4aafbfafad59cb10e67fc21d22d62f68d98251ce0bce8e704 + languageName: node + linkType: hard + +"@gar/promisify@npm:^1.1.3": + version: 1.1.3 + resolution: "@gar/promisify@npm:1.1.3" + checksum: 4059f790e2d07bf3c3ff3e0fec0daa8144fe35c1f6e0111c9921bd32106adaa97a4ab096ad7dab1e28ee6a9060083c4d1a4ada42a7f5f3f7a96b8812e2b757c1 + languageName: node + linkType: hard + +"@graphiteds/core@npm:^1.9.6": + version: 1.9.7 + resolution: "@graphiteds/core@npm:1.9.7" + dependencies: + "@duetds/date-picker": ^1.4.0 + "@popperjs/core": ^2.11.5 + "@stencil/core": ^2.20.0 + checksum: d9fa01a5e63fa4b26010893f85324ddfc1961c08ba1a934da5371941d648582e722a87f5af682084be24f28aa5b3aec3bda6a8ff8759b2f7246e5db13f528391 + languageName: node + linkType: hard + +"@humanwhocodes/config-array@npm:^0.11.8": + version: 0.11.8 + resolution: "@humanwhocodes/config-array@npm:0.11.8" + dependencies: + "@humanwhocodes/object-schema": ^1.2.1 + debug: ^4.1.1 + minimatch: ^3.0.5 + checksum: 0fd6b3c54f1674ce0a224df09b9c2f9846d20b9e54fabae1281ecfc04f2e6ad69bf19e1d6af6a28f88e8aa3990168b6cb9e1ef755868c3256a630605ec2cb1d3 + languageName: node + linkType: hard + +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 0fd22007db8034a2cdf2c764b140d37d9020bbfce8a49d3ec5c05290e77d4b0263b1b972b752df8c89e5eaa94073408f2b7d977aed131faf6cf396ebb5d7fb61 + languageName: node + linkType: hard + +"@humanwhocodes/object-schema@npm:^1.2.1": + version: 1.2.1 + resolution: "@humanwhocodes/object-schema@npm:1.2.1" + checksum: a824a1ec31591231e4bad5787641f59e9633827d0a2eaae131a288d33c9ef0290bd16fda8da6f7c0fcb014147865d12118df10db57f27f41e20da92369fcb3f1 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.1.0": + version: 0.1.1 + resolution: "@jridgewell/gen-mapping@npm:0.1.1" + dependencies: + "@jridgewell/set-array": ^1.0.0 + "@jridgewell/sourcemap-codec": ^1.4.10 + checksum: 3bcc21fe786de6ffbf35c399a174faab05eb23ce6a03e8769569de28abbf4facc2db36a9ddb0150545ae23a8d35a7cf7237b2aa9e9356a7c626fb4698287d5cc + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": + version: 0.3.2 + resolution: "@jridgewell/gen-mapping@npm:0.3.2" + dependencies: + "@jridgewell/set-array": ^1.0.1 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 1832707a1c476afebe4d0fbbd4b9434fdb51a4c3e009ab1e9938648e21b7a97049fa6009393bdf05cab7504108413441df26d8a3c12193996e65493a4efb6882 + languageName: node + linkType: hard + +"@jridgewell/resolve-uri@npm:3.1.0": + version: 3.1.0 + resolution: "@jridgewell/resolve-uri@npm:3.1.0" + checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 + languageName: node + linkType: hard + +"@jridgewell/set-array@npm:^1.0.0, @jridgewell/set-array@npm:^1.0.1": + version: 1.1.2 + resolution: "@jridgewell/set-array@npm:1.1.2" + checksum: 69a84d5980385f396ff60a175f7177af0b8da4ddb81824cb7016a9ef914eee9806c72b6b65942003c63f7983d4f39a5c6c27185bbca88eb4690b62075602e28e + languageName: node + linkType: hard + +"@jridgewell/source-map@npm:^0.3.2": + version: 0.3.2 + resolution: "@jridgewell/source-map@npm:0.3.2" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 1b83f0eb944e77b70559a394d5d3b3f98a81fcc186946aceb3ef42d036762b52ef71493c6c0a3b7c1d2f08785f53ba2df1277fe629a06e6109588ff4cdcf7482 + languageName: node + linkType: hard + +"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.14 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" + checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 + languageName: node + linkType: hard + +"@jridgewell/trace-mapping@npm:^0.3.9": + version: 0.3.17 + resolution: "@jridgewell/trace-mapping@npm:0.3.17" + dependencies: + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 9d703b859cff5cd83b7308fd457a431387db5db96bd781a63bf48e183418dd9d3d44e76b9e4ae13237f6abeeb25d739ec9215c1d5bfdd08f66f750a50074a339 + languageName: node + linkType: hard + +"@lit-labs/scoped-registry-mixin@npm:^1.0.1": + version: 1.0.1 + resolution: "@lit-labs/scoped-registry-mixin@npm:1.0.1" + dependencies: + "@lit/reactive-element": ^1.0.0 + lit: ^2.0.0 + checksum: 94a86c8329d27b0f7e3b6ec1928c0eb7efc64f6dc2ed2d5cce09184650e465ce63e77ca4fec95d508583ea0d793fe0495b75a7b0830f5c1f951caa28d7d51842 + languageName: node + linkType: hard + +"@lit-labs/ssr-dom-shim@npm:^1.0.0": + version: 1.0.0 + resolution: "@lit-labs/ssr-dom-shim@npm:1.0.0" + checksum: ccba6675ad631c6f7360af4e12e328e88d877a52ec992ae9254a1be9902ef699b7916d851b073803dd91244d0f709de2c0539a7db643889cfe341ff6e1a74eb5 + languageName: node + linkType: hard + +"@lit-labs/task@npm:^1.1.3": + version: 1.1.3 + resolution: "@lit-labs/task@npm:1.1.3" + dependencies: + "@lit/reactive-element": ^1.1.0 + checksum: 82b3680a8b6d5c447364857a0beb8e28237c05fdffe06456183196521000b4fcd696da374f17d28020248042bda2af7d4ddf9adbe40309c32e6672afa6e58935 + languageName: node + linkType: hard + +"@lit/reactive-element@npm:^1.0.0, @lit/reactive-element@npm:^1.1.0, @lit/reactive-element@npm:^1.3.0, @lit/reactive-element@npm:^1.6.0": + version: 1.6.1 + resolution: "@lit/reactive-element@npm:1.6.1" + dependencies: + "@lit-labs/ssr-dom-shim": ^1.0.0 + checksum: fab0bcfdade9c26af2ad5115fb564bcf8ba0732a3a8be86c157851c2771e3fdc65ab38f2cd60fa121946058bf6e487461fd217f87b01f96e88ee7a95d5d866ba + languageName: node + linkType: hard + +"@nodelib/fs.scandir@npm:2.1.5": + version: 2.1.5 + resolution: "@nodelib/fs.scandir@npm:2.1.5" + dependencies: + "@nodelib/fs.stat": 2.0.5 + run-parallel: ^1.1.9 + checksum: a970d595bd23c66c880e0ef1817791432dbb7acbb8d44b7e7d0e7a22f4521260d4a83f7f9fd61d44fda4610105577f8f58a60718105fb38352baed612fd79e59 + languageName: node + linkType: hard + +"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": + version: 2.0.5 + resolution: "@nodelib/fs.stat@npm:2.0.5" + checksum: 012480b5ca9d97bff9261571dbbec7bbc6033f69cc92908bc1ecfad0792361a5a1994bc48674b9ef76419d056a03efadfce5a6cf6dbc0a36559571a7a483f6f0 + languageName: node + linkType: hard + +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": + version: 1.2.8 + resolution: "@nodelib/fs.walk@npm:1.2.8" + dependencies: + "@nodelib/fs.scandir": 2.1.5 + fastq: ^1.6.0 + checksum: 190c643f156d8f8f277bf2a6078af1ffde1fd43f498f187c2db24d35b4b4b5785c02c7dc52e356497b9a1b65b13edc996de08de0b961c32844364da02986dc53 + languageName: node + linkType: hard + +"@npmcli/fs@npm:^2.1.0": + version: 2.1.2 + resolution: "@npmcli/fs@npm:2.1.2" + dependencies: + "@gar/promisify": ^1.1.3 + semver: ^7.3.5 + checksum: 405074965e72d4c9d728931b64d2d38e6ea12066d4fad651ac253d175e413c06fe4350970c783db0d749181da8fe49c42d3880bd1cbc12cd68e3a7964d820225 + languageName: node + linkType: hard + +"@npmcli/move-file@npm:^2.0.0": + version: 2.0.1 + resolution: "@npmcli/move-file@npm:2.0.1" + dependencies: + mkdirp: ^1.0.4 + rimraf: ^3.0.2 + checksum: 52dc02259d98da517fae4cb3a0a3850227bdae4939dda1980b788a7670636ca2b4a01b58df03dd5f65c1e3cb70c50fa8ce5762b582b3f499ec30ee5ce1fd9380 + languageName: node + linkType: hard + +"@popperjs/core@npm:^2.11.5": + version: 2.11.6 + resolution: "@popperjs/core@npm:2.11.6" + checksum: 47fb328cec1924559d759b48235c78574f2d71a8a6c4c03edb6de5d7074078371633b91e39bbf3f901b32aa8af9b9d8f82834856d2f5737a23475036b16817f0 + languageName: node + linkType: hard + +"@rollup/plugin-babel@npm:^5.3.1": + version: 5.3.1 + resolution: "@rollup/plugin-babel@npm:5.3.1" + dependencies: + "@babel/helper-module-imports": ^7.10.4 + "@rollup/pluginutils": ^3.1.0 + peerDependencies: + "@babel/core": ^7.0.0 + "@types/babel__core": ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + "@types/babel__core": + optional: true + checksum: 220d71e4647330f252ef33d5f29700aef2e8284a0b61acfcceb47617a7f96208aa1ed16eae75619424bf08811ede5241e271a6d031f07026dee6b3a2bdcdc638 + languageName: node + linkType: hard + +"@rollup/plugin-commonjs@npm:^22.0.2": + version: 22.0.2 + resolution: "@rollup/plugin-commonjs@npm:22.0.2" + dependencies: + "@rollup/pluginutils": ^3.1.0 + commondir: ^1.0.1 + estree-walker: ^2.0.1 + glob: ^7.1.6 + is-reference: ^1.2.1 + magic-string: ^0.25.7 + resolve: ^1.17.0 + peerDependencies: + rollup: ^2.68.0 + checksum: 70098a4b91afe3f164f5d27cba65edf148c5ed146ee0e07a964b66940681553ac77391083114cdcf9427e7f2706bf0d61eab310b3a2caeab83b7452c0292fcae + languageName: node + linkType: hard + +"@rollup/plugin-image@npm:^2.1.1": + version: 2.1.1 + resolution: "@rollup/plugin-image@npm:2.1.1" + dependencies: + "@rollup/pluginutils": ^3.1.0 + mini-svg-data-uri: ^1.2.3 + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + checksum: a629c8f22233ca159c23655fdbc3449dab3c939372178ed4462fc9c525cc4ecd8b11fae359eb94be4f769d26f48b85fb18eb16ce1fbc33ed16b6a7c1f84391f6 + languageName: node + linkType: hard + +"@rollup/plugin-json@npm:^4.1.0": + version: 4.1.0 + resolution: "@rollup/plugin-json@npm:4.1.0" + dependencies: + "@rollup/pluginutils": ^3.0.8 + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + checksum: 867bc9339b4ccf0b9ff3b2617a95b3b8920115163f86c8e3b1f068a14ca25949472d3c05b09a5ac38ca0fe2185756e34617eaeb219d4a2b6e2307c501c7d4552 + languageName: node + linkType: hard + +"@rollup/plugin-node-resolve@npm:^13.3.0": + version: 13.3.0 + resolution: "@rollup/plugin-node-resolve@npm:13.3.0" + dependencies: + "@rollup/pluginutils": ^3.1.0 + "@types/resolve": 1.17.1 + deepmerge: ^4.2.2 + is-builtin-module: ^3.1.0 + is-module: ^1.0.0 + resolve: ^1.19.0 + peerDependencies: + rollup: ^2.42.0 + checksum: ec5418e6b3c23a9e30683056b3010e9d325316dcfae93fbc673ae64dad8e56a2ce761c15c48f5e2dcfe0c822fdc4a4905ee6346e3dcf90603ba2260afef5a5e6 + languageName: node + linkType: hard + +"@rollup/plugin-replace@npm:^4.0.0": + version: 4.0.0 + resolution: "@rollup/plugin-replace@npm:4.0.0" + dependencies: + "@rollup/pluginutils": ^3.1.0 + magic-string: ^0.25.7 + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + checksum: b61701e612661a46da06ca88f390c3839b586708abc0329a476411b90aa8ac59fe94437ede4c3fe53fea268b3054ea9d7d4ee851d7bd0cb7a5f06906002067cb + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^3.0.8, @rollup/pluginutils@npm:^3.1.0": + version: 3.1.0 + resolution: "@rollup/pluginutils@npm:3.1.0" + dependencies: + "@types/estree": 0.0.39 + estree-walker: ^1.0.1 + picomatch: ^2.2.2 + peerDependencies: + rollup: ^1.20.0||^2.0.0 + checksum: 8be16e27863c219edbb25a4e6ec2fe0e1e451d9e917b6a43cf2ae5bc025a6b8faaa40f82a6e53b66d0de37b58ff472c6c3d57a83037ae635041f8df959d6d9aa + languageName: node + linkType: hard + +"@rollup/pluginutils@npm:^4.1.2": + version: 4.2.1 + resolution: "@rollup/pluginutils@npm:4.2.1" + dependencies: + estree-walker: ^2.0.1 + picomatch: ^2.2.2 + checksum: 6bc41f22b1a0f1efec3043899e4d3b6b1497b3dea4d94292d8f83b4cf07a1073ecbaedd562a22d11913ff7659f459677b01b09e9598a98936e746780ecc93a12 + languageName: node + linkType: hard + +"@stencil/core@npm:^2.20.0, @stencil/core@npm:^2.3.0": + version: 2.22.2 + resolution: "@stencil/core@npm:2.22.2" + bin: + stencil: bin/stencil + checksum: 139a7b63b81c7eaa5026e76e1c947a402f58d0a6d1d353a34883995ea7e480fa6991e71c0c3d812f7046d395b99d0f7f7b11af9277f04a3c62aba96cda18dd5d + languageName: node + linkType: hard + +"@tootallnate/once@npm:2": + version: 2.0.0 + resolution: "@tootallnate/once@npm:2.0.0" + checksum: ad87447820dd3f24825d2d947ebc03072b20a42bfc96cbafec16bff8bbda6c1a81fcb0be56d5b21968560c5359a0af4038a68ba150c3e1694fe4c109a063bed8 + languageName: node + linkType: hard + +"@trysound/sax@npm:0.2.0": + version: 0.2.0 + resolution: "@trysound/sax@npm:0.2.0" + checksum: 11226c39b52b391719a2a92e10183e4260d9651f86edced166da1d95f39a0a1eaa470e44d14ac685ccd6d3df7e2002433782872c0feeb260d61e80f21250e65c + languageName: node + linkType: hard + +"@ts-morph/common@npm:~0.12.3": + version: 0.12.3 + resolution: "@ts-morph/common@npm:0.12.3" + dependencies: + fast-glob: ^3.2.7 + minimatch: ^3.0.4 + mkdirp: ^1.0.4 + path-browserify: ^1.0.1 + checksum: d96ea9805d4f0300cc05c47daa9454438903b86ffb7116f5181a1eba71e881012a1adc2a867b3afbe4429ef29e3e0d6204175cbaf33ecdd7a7d09b5d8a37f12d + languageName: node + linkType: hard + +"@types/bluebird@npm:^3.5.36": + version: 3.5.38 + resolution: "@types/bluebird@npm:3.5.38" + checksum: 8d1b04261e8e58816ec84ffa616c9e641c9416d0ab10c915ddb9cd8a0e7070af16df4f2eec243a3809cbed8ecee2fb3f45600ae43b3ab0ceea563aa18ceb6ab1 + languageName: node + linkType: hard + +"@types/cssnano@npm:^5.0.0": + version: 5.1.0 + resolution: "@types/cssnano@npm:5.1.0" + dependencies: + cssnano: "*" + checksum: 4cce9e5d041a6276cd2e4bc3fce3e9ef5cd53531f86cedee2e9abdd12b050fb927e7a68a87043dbfa222f74ad3eced377d677a113b4772ec7297c737cdab0d74 + languageName: node + linkType: hard + +"@types/estree@npm:*": + version: 1.0.0 + resolution: "@types/estree@npm:1.0.0" + checksum: 910d97fb7092c6738d30a7430ae4786a38542023c6302b95d46f49420b797f21619cdde11fa92b338366268795884111c2eb10356e4bd2c8ad5b92941e9e6443 + languageName: node + linkType: hard + +"@types/estree@npm:0.0.39": + version: 0.0.39 + resolution: "@types/estree@npm:0.0.39" + checksum: 412fb5b9868f2c418126451821833414189b75cc6bf84361156feed733e3d92ec220b9d74a89e52722e03d5e241b2932732711b7497374a404fad49087adc248 + languageName: node + linkType: hard + +"@types/hammerjs@npm:^2.0.36": + version: 2.0.41 + resolution: "@types/hammerjs@npm:2.0.41" + checksum: d16fbd688fc9b18cc270abe8dea8d4c50ef7bd8375e593d92c233d299387933a6b003c8db69819344833052458bc5f9ef1b472001277a49f095928d184356006 + languageName: node + linkType: hard + +"@types/json-schema@npm:^7.0.9": + version: 7.0.11 + resolution: "@types/json-schema@npm:7.0.11" + checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d + languageName: node + linkType: hard + +"@types/json5@npm:^0.0.29": + version: 0.0.29 + resolution: "@types/json5@npm:0.0.29" + checksum: e60b153664572116dfea673c5bda7778dbff150498f44f998e34b5886d8afc47f16799280e4b6e241c0472aef1bc36add771c569c68fc5125fc2ae519a3eb9ac + languageName: node + linkType: hard + +"@types/lodash-es@npm:^4.17.5": + version: 4.17.6 + resolution: "@types/lodash-es@npm:4.17.6" + dependencies: + "@types/lodash": "*" + checksum: 9bd239dd525086e278821949ce12fbdd4f100a060fed9323fc7ad5661113e1641f28a7ebab617230ed3474680d8f4de705c1928b48252bb684be6ec9eed715db + languageName: node + linkType: hard + +"@types/lodash@npm:*": + version: 4.14.191 + resolution: "@types/lodash@npm:4.14.191" + checksum: ba0d5434e10690869f32d5ea49095250157cae502f10d57de0a723fd72229ce6c6a4979576f0f13e0aa9fbe3ce2457bfb9fa7d4ec3d6daba56730a51906d1491 + languageName: node + linkType: hard + +"@types/node@npm:*": + version: 18.11.19 + resolution: "@types/node@npm:18.11.19" + checksum: d7cd19fcfc59cbdd3f9ba0b4072cb7adc21bd575bd8eb7d7e698975e63564aaa83f03434f32b12331f84f73d0b369d9cbe2371e359d9d7f5c3361f4987f4f7da + languageName: node + linkType: hard + +"@types/parse-json@npm:^4.0.0": + version: 4.0.0 + resolution: "@types/parse-json@npm:4.0.0" + checksum: fd6bce2b674b6efc3db4c7c3d336bd70c90838e8439de639b909ce22f3720d21344f52427f1d9e57b265fcb7f6c018699b99e5e0c208a1a4823014269a6bf35b + languageName: node + linkType: hard + +"@types/resolve@npm:1.17.1": + version: 1.17.1 + resolution: "@types/resolve@npm:1.17.1" + dependencies: + "@types/node": "*" + checksum: dc6a6df507656004e242dcb02c784479deca516d5f4b58a1707e708022b269ae147e1da0521f3e8ad0d63638869d87e0adc023f0bd5454aa6f72ac66c7525cf5 + languageName: node + linkType: hard + +"@types/semver@npm:^7.3.12": + version: 7.3.13 + resolution: "@types/semver@npm:7.3.13" + checksum: 00c0724d54757c2f4bc60b5032fe91cda6410e48689633d5f35ece8a0a66445e3e57fa1d6e07eb780f792e82ac542948ec4d0b76eb3484297b79bd18b8cf1cb0 + languageName: node + linkType: hard + +"@types/trusted-types@npm:^2.0.2": + version: 2.0.2 + resolution: "@types/trusted-types@npm:2.0.2" + checksum: 3371eef5f1c50e1c3c07a127c1207b262ba65b83dd167a1c460fc1b135a3fb0c97b9f508efebd383f239cc5dd5b7169093686a692a501fde9c3f7208657d9b0d + languageName: node + linkType: hard + +"@typescript-eslint/eslint-plugin@npm:^5.36.2": + version: 5.51.0 + resolution: "@typescript-eslint/eslint-plugin@npm:5.51.0" + dependencies: + "@typescript-eslint/scope-manager": 5.51.0 + "@typescript-eslint/type-utils": 5.51.0 + "@typescript-eslint/utils": 5.51.0 + debug: ^4.3.4 + grapheme-splitter: ^1.0.4 + ignore: ^5.2.0 + natural-compare-lite: ^1.4.0 + regexpp: ^3.2.0 + semver: ^7.3.7 + tsutils: ^3.21.0 + peerDependencies: + "@typescript-eslint/parser": ^5.0.0 + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 5351d8cec13bd9867ce4aaf7052aa31c9ca867fc89c620fc0fe5718ac2cbc165903275db59974324d98e45df0d33a73a4367d236668772912731031a672cfdcd + languageName: node + linkType: hard + +"@typescript-eslint/parser@npm:^5.36.2": + version: 5.51.0 + resolution: "@typescript-eslint/parser@npm:5.51.0" + dependencies: + "@typescript-eslint/scope-manager": 5.51.0 + "@typescript-eslint/types": 5.51.0 + "@typescript-eslint/typescript-estree": 5.51.0 + debug: ^4.3.4 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 096ec819132839febd4f390c4bbf31687e06191092c244dbd189a64cd7383fbaba728f2765e8809cd9834c0069163ab38b0e5f0f6360157d831647d4c295f8cd + languageName: node + linkType: hard + +"@typescript-eslint/scope-manager@npm:5.51.0": + version: 5.51.0 + resolution: "@typescript-eslint/scope-manager@npm:5.51.0" + dependencies: + "@typescript-eslint/types": 5.51.0 + "@typescript-eslint/visitor-keys": 5.51.0 + checksum: b3c9f48b6b7a7ae2ebcad4745ef91e4727776b2cf56d31be6456b1aa063aa649539e20f9fffa83cad9ccaaa9c492f2354a1c15526a2b789e235ec58b3a82d22c + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:5.51.0": + version: 5.51.0 + resolution: "@typescript-eslint/type-utils@npm:5.51.0" + dependencies: + "@typescript-eslint/typescript-estree": 5.51.0 + "@typescript-eslint/utils": 5.51.0 + debug: ^4.3.4 + tsutils: ^3.21.0 + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: ab9747b0c629cfaaab903eed8ce1e39d34d69a402ce5faf2f1fff2bbb461bdbe034044b1368ba67ba8e5c1c512172e07d83c8a563635d8de811bf148d95c7dec + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:5.51.0": + version: 5.51.0 + resolution: "@typescript-eslint/types@npm:5.51.0" + checksum: b31021a0866f41ba5d71b6c4c7e20cc9b99d49c93bb7db63b55b2e51542fb75b4e27662ee86350da3c1318029e278a5a807facaf4cb5aeea724be8b0e021e836 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:5.51.0": + version: 5.51.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.51.0" + dependencies: + "@typescript-eslint/types": 5.51.0 + "@typescript-eslint/visitor-keys": 5.51.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.3.7 + tsutils: ^3.21.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: aec23e5cab48ee72fefa6d1ac266639ebabf6cebec1e0207ad47011d3a48186ac9a632c8e34c3bac896155f54895a497230c11d789fd81263b08eb267d7113ce + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:5.51.0": + version: 5.51.0 + resolution: "@typescript-eslint/utils@npm:5.51.0" + dependencies: + "@types/json-schema": ^7.0.9 + "@types/semver": ^7.3.12 + "@typescript-eslint/scope-manager": 5.51.0 + "@typescript-eslint/types": 5.51.0 + "@typescript-eslint/typescript-estree": 5.51.0 + eslint-scope: ^5.1.1 + eslint-utils: ^3.0.0 + semver: ^7.3.7 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: c6e28c942fbac5500f0e8ed67ef304b484ba296486e55306f78fb090dc9d5bb1f25a0bedc065e14680041eadce5e95fa10aab618cb0c316599ec987e6ea72442 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.51.0": + version: 5.51.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.51.0" + dependencies: + "@typescript-eslint/types": 5.51.0 + eslint-visitor-keys: ^3.3.0 + checksum: b49710f3c6b3b62a846a163afffd81be5eb2b1f44e25bec51ff3c9f4c3b579d74aa4cbd3753b4fc09ea3dbc64a7062f9c658c08d22bb2740a599cb703d876220 + languageName: node + linkType: hard + +"abbrev@npm:^1.0.0": + version: 1.1.1 + resolution: "abbrev@npm:1.1.1" + checksum: a4a97ec07d7ea112c517036882b2ac22f3109b7b19077dc656316d07d308438aac28e4d9746dc4d84bf6b1e75b4a7b0a5f3cb30592419f128ca9a8cee3bcfa17 + languageName: node + linkType: hard + +"acorn-jsx@npm:^5.3.2": + version: 5.3.2 + resolution: "acorn-jsx@npm:5.3.2" + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: c3d3b2a89c9a056b205b69530a37b972b404ee46ec8e5b341666f9513d3163e2a4f214a71f4dfc7370f5a9c07472d2fd1c11c91c3f03d093e37637d95da98950 + languageName: node + linkType: hard + +"acorn@npm:^8.5.0, acorn@npm:^8.8.0": + version: 8.8.2 + resolution: "acorn@npm:8.8.2" + bin: + acorn: bin/acorn + checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + languageName: node + linkType: hard + +"agent-base@npm:6, agent-base@npm:^6.0.2": + version: 6.0.2 + resolution: "agent-base@npm:6.0.2" + dependencies: + debug: 4 + checksum: f52b6872cc96fd5f622071b71ef200e01c7c4c454ee68bc9accca90c98cfb39f2810e3e9aa330435835eedc8c23f4f8a15267f67c6e245d2b33757575bdac49d + languageName: node + linkType: hard + +"agentkeepalive@npm:^4.2.1": + version: 4.2.1 + resolution: "agentkeepalive@npm:4.2.1" + dependencies: + debug: ^4.1.0 + depd: ^1.1.2 + humanize-ms: ^1.2.1 + checksum: 39cb49ed8cf217fd6da058a92828a0a84e0b74c35550f82ee0a10e1ee403c4b78ade7948be2279b188b7a7303f5d396ea2738b134731e464bf28de00a4f72a18 + languageName: node + linkType: hard + +"aggregate-error@npm:^3.0.0": + version: 3.1.0 + resolution: "aggregate-error@npm:3.1.0" + dependencies: + clean-stack: ^2.0.0 + indent-string: ^4.0.0 + checksum: 1101a33f21baa27a2fa8e04b698271e64616b886795fd43c31068c07533c7b3facfcaf4e9e0cab3624bd88f729a592f1c901a1a229c9e490eafce411a8644b79 + languageName: node + linkType: hard + +"ajv@npm:^6.10.0, ajv@npm:^6.12.4": + version: 6.12.6 + resolution: "ajv@npm:6.12.6" + dependencies: + fast-deep-equal: ^3.1.1 + fast-json-stable-stringify: ^2.0.0 + json-schema-traverse: ^0.4.1 + uri-js: ^4.2.2 + checksum: 874972efe5c4202ab0a68379481fbd3d1b5d0a7bd6d3cc21d40d3536ebff3352a2a1fabb632d4fd2cc7fe4cbdcd5ed6782084c9bbf7f32a1536d18f9da5007d4 + languageName: node + linkType: hard + +"ansi-regex@npm:^5.0.1": + version: 5.0.1 + resolution: "ansi-regex@npm:5.0.1" + checksum: 2aa4bb54caf2d622f1afdad09441695af2a83aa3fe8b8afa581d205e57ed4261c183c4d3877cee25794443fde5876417d859c108078ab788d6af7e4fe52eb66b + languageName: node + linkType: hard + +"ansi-styles@npm:^3.2.1": + version: 3.2.1 + resolution: "ansi-styles@npm:3.2.1" + dependencies: + color-convert: ^1.9.0 + checksum: d85ade01c10e5dd77b6c89f34ed7531da5830d2cb5882c645f330079975b716438cd7ebb81d0d6e6b4f9c577f19ae41ab55f07f19786b02f9dfd9e0377395665 + languageName: node + linkType: hard + +"ansi-styles@npm:^4.0.0, ansi-styles@npm:^4.1.0": + version: 4.3.0 + resolution: "ansi-styles@npm:4.3.0" + dependencies: + color-convert: ^2.0.1 + checksum: 513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4 + languageName: node + linkType: hard + +"anymatch@npm:~3.1.2": + version: 3.1.3 + resolution: "anymatch@npm:3.1.3" + dependencies: + normalize-path: ^3.0.0 + picomatch: ^2.0.4 + checksum: 3e044fd6d1d26545f235a9fe4d7a534e2029d8e59fa7fd9f2a6eb21230f6b5380ea1eaf55136e60cbf8e613544b3b766e7a6fa2102e2a3a117505466e3025dc2 + languageName: node + linkType: hard + +"aproba@npm:^1.0.3 || ^2.0.0": + version: 2.0.0 + resolution: "aproba@npm:2.0.0" + checksum: 5615cadcfb45289eea63f8afd064ab656006361020e1735112e346593856f87435e02d8dcc7ff0d11928bc7d425f27bc7c2a84f6c0b35ab0ff659c814c138a24 + languageName: node + linkType: hard + +"are-we-there-yet@npm:^3.0.0": + version: 3.0.1 + resolution: "are-we-there-yet@npm:3.0.1" + dependencies: + delegates: ^1.0.0 + readable-stream: ^3.6.0 + checksum: 52590c24860fa7173bedeb69a4c05fb573473e860197f618b9a28432ee4379049336727ae3a1f9c4cb083114601c1140cee578376164d0e651217a9843f9fe83 + languageName: node + linkType: hard + +"argparse@npm:^2.0.1": + version: 2.0.1 + resolution: "argparse@npm:2.0.1" + checksum: 83644b56493e89a254bae05702abf3a1101b4fa4d0ca31df1c9985275a5a5bd47b3c27b7fa0b71098d41114d8ca000e6ed90cad764b306f8a503665e4d517ced + languageName: node + linkType: hard + +"array-includes@npm:^3.1.6": + version: 3.1.6 + resolution: "array-includes@npm:3.1.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + get-intrinsic: ^1.1.3 + is-string: ^1.0.7 + checksum: f22f8cd8ba8a6448d91eebdc69f04e4e55085d09232b5216ee2d476dab3ef59984e8d1889e662c6a0ed939dcb1b57fd05b2c0209c3370942fc41b752c82a2ca5 + languageName: node + linkType: hard + +"array-union@npm:^2.1.0": + version: 2.1.0 + resolution: "array-union@npm:2.1.0" + checksum: 5bee12395cba82da674931df6d0fea23c4aa4660cb3b338ced9f828782a65caa232573e6bf3968f23e0c5eb301764a382cef2f128b170a9dc59de0e36c39f98d + languageName: node + linkType: hard + +"array.prototype.flat@npm:^1.3.1": + version: 1.3.1 + resolution: "array.prototype.flat@npm:1.3.1" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + es-shim-unscopables: ^1.0.0 + checksum: 5a8415949df79bf6e01afd7e8839bbde5a3581300e8ad5d8449dea52639e9e59b26a467665622783697917b43bf39940a6e621877c7dd9b3d1c1f97484b9b88b + languageName: node + linkType: hard + +"array.prototype.flatmap@npm:^1.3.1": + version: 1.3.1 + resolution: "array.prototype.flatmap@npm:1.3.1" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + es-shim-unscopables: ^1.0.0 + checksum: 8c1c43a4995f12cf12523436da28515184c753807b3f0bc2ca6c075f71c470b099e2090cc67dba8e5280958fea401c1d0c59e1db0143272aef6cd1103921a987 + languageName: node + linkType: hard + +"available-typed-arrays@npm:^1.0.5": + version: 1.0.5 + resolution: "available-typed-arrays@npm:1.0.5" + checksum: 20eb47b3cefd7db027b9bbb993c658abd36d4edd3fe1060e83699a03ee275b0c9b216cc076ff3f2db29073225fb70e7613987af14269ac1fe2a19803ccc97f1a + languageName: node + linkType: hard + +"balanced-match@npm:^1.0.0": + version: 1.0.2 + resolution: "balanced-match@npm:1.0.2" + checksum: 9706c088a283058a8a99e0bf91b0a2f75497f185980d9ffa8b304de1d9e58ebda7c72c07ebf01dadedaac5b2907b2c6f566f660d62bd336c3468e960403b9d65 + languageName: node + linkType: hard + +"binary-extensions@npm:^2.0.0": + version: 2.2.0 + resolution: "binary-extensions@npm:2.2.0" + checksum: ccd267956c58d2315f5d3ea6757cf09863c5fc703e50fbeb13a7dc849b812ef76e3cf9ca8f35a0c48498776a7478d7b4a0418e1e2b8cb9cb9731f2922aaad7f8 + languageName: node + linkType: hard + +"boolbase@npm:^1.0.0": + version: 1.0.0 + resolution: "boolbase@npm:1.0.0" + checksum: 3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0 + languageName: node + linkType: hard + +"brace-expansion@npm:^1.1.7": + version: 1.1.11 + resolution: "brace-expansion@npm:1.1.11" + dependencies: + balanced-match: ^1.0.0 + concat-map: 0.0.1 + checksum: faf34a7bb0c3fcf4b59c7808bc5d2a96a40988addf2e7e09dfbb67a2251800e0d14cd2bfc1aa79174f2f5095c54ff27f46fb1289fe2d77dac755b5eb3434cc07 + languageName: node + linkType: hard + +"brace-expansion@npm:^2.0.1": + version: 2.0.1 + resolution: "brace-expansion@npm:2.0.1" + dependencies: + balanced-match: ^1.0.0 + checksum: a61e7cd2e8a8505e9f0036b3b6108ba5e926b4b55089eeb5550cd04a471fe216c96d4fe7e4c7f995c728c554ae20ddfc4244cad10aef255e72b62930afd233d1 + languageName: node + linkType: hard + +"braces@npm:^3.0.2, braces@npm:~3.0.2": + version: 3.0.2 + resolution: "braces@npm:3.0.2" + dependencies: + fill-range: ^7.0.1 + checksum: e2a8e769a863f3d4ee887b5fe21f63193a891c68b612ddb4b68d82d1b5f3ff9073af066c343e9867a393fe4c2555dcb33e89b937195feb9c1613d259edfcd459 + languageName: node + linkType: hard + +"browserslist@npm:^4.0.0, browserslist@npm:^4.16.6, browserslist@npm:^4.21.3, browserslist@npm:^4.21.4": + version: 4.21.5 + resolution: "browserslist@npm:4.21.5" + dependencies: + caniuse-lite: ^1.0.30001449 + electron-to-chromium: ^1.4.284 + node-releases: ^2.0.8 + update-browserslist-db: ^1.0.10 + bin: + browserslist: cli.js + checksum: 9755986b22e73a6a1497fd8797aedd88e04270be33ce66ed5d85a1c8a798292a65e222b0f251bafa1c2522261e237d73b08b58689d4920a607e5a53d56dc4706 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb + languageName: node + linkType: hard + +"builtin-modules@npm:^3.3.0": + version: 3.3.0 + resolution: "builtin-modules@npm:3.3.0" + checksum: db021755d7ed8be048f25668fe2117620861ef6703ea2c65ed2779c9e3636d5c3b82325bd912244293959ff3ae303afa3471f6a15bf5060c103e4cc3a839749d + languageName: node + linkType: hard + +"cacache@npm:^16.1.0": + version: 16.1.3 + resolution: "cacache@npm:16.1.3" + dependencies: + "@npmcli/fs": ^2.1.0 + "@npmcli/move-file": ^2.0.0 + chownr: ^2.0.0 + fs-minipass: ^2.1.0 + glob: ^8.0.1 + infer-owner: ^1.0.4 + lru-cache: ^7.7.1 + minipass: ^3.1.6 + minipass-collect: ^1.0.2 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + mkdirp: ^1.0.4 + p-map: ^4.0.0 + promise-inflight: ^1.0.1 + rimraf: ^3.0.2 + ssri: ^9.0.0 + tar: ^6.1.11 + unique-filename: ^2.0.0 + checksum: d91409e6e57d7d9a3a25e5dcc589c84e75b178ae8ea7de05cbf6b783f77a5fae938f6e8fda6f5257ed70000be27a681e1e44829251bfffe4c10216002f8f14e6 + languageName: node + linkType: hard + +"call-bind@npm:^1.0.0, call-bind@npm:^1.0.2": + version: 1.0.2 + resolution: "call-bind@npm:1.0.2" + dependencies: + function-bind: ^1.1.1 + get-intrinsic: ^1.0.2 + checksum: f8e31de9d19988a4b80f3e704788c4a2d6b6f3d17cfec4f57dc29ced450c53a49270dc66bf0fbd693329ee948dd33e6c90a329519aef17474a4d961e8d6426b0 + languageName: node + linkType: hard + +"callsites@npm:^3.0.0": + version: 3.1.0 + resolution: "callsites@npm:3.1.0" + checksum: 072d17b6abb459c2ba96598918b55868af677154bec7e73d222ef95a8fdb9bbf7dae96a8421085cdad8cd190d86653b5b6dc55a4484f2e5b2e27d5e0c3fc15b3 + languageName: node + linkType: hard + +"caniuse-api@npm:^3.0.0": + version: 3.0.0 + resolution: "caniuse-api@npm:3.0.0" + dependencies: + browserslist: ^4.0.0 + caniuse-lite: ^1.0.0 + lodash.memoize: ^4.1.2 + lodash.uniq: ^4.5.0 + checksum: db2a229383b20d0529b6b589dde99d7b6cb56ba371366f58cbbfa2929c9f42c01f873e2b6ef641d4eda9f0b4118de77dbb2805814670bdad4234bf08e720b0b4 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001449": + version: 1.0.30001450 + resolution: "caniuse-lite@npm:1.0.30001450" + checksum: 511b360bfc907b2e437699364cf96b83507bc45043926450056642332bcd6f65a1e72540c828534ae15e0ac906e3e9af46cb2bb84458dd580bc31478e9dce282 + languageName: node + linkType: hard + +"chalk@npm:^2.0.0": + version: 2.4.2 + resolution: "chalk@npm:2.4.2" + dependencies: + ansi-styles: ^3.2.1 + escape-string-regexp: ^1.0.5 + supports-color: ^5.3.0 + checksum: ec3661d38fe77f681200f878edbd9448821924e0f93a9cefc0e26a33b145f1027a2084bf19967160d11e1f03bfe4eaffcabf5493b89098b2782c3fe0b03d80c2 + languageName: node + linkType: hard + +"chalk@npm:^4.0.0": + version: 4.1.2 + resolution: "chalk@npm:4.1.2" + dependencies: + ansi-styles: ^4.1.0 + supports-color: ^7.1.0 + checksum: fe75c9d5c76a7a98d45495b91b2172fa3b7a09e0cc9370e5c8feb1c567b85c4288e2b3fded7cfdd7359ac28d6b3844feb8b82b8686842e93d23c827c417e83fc + languageName: node + linkType: hard + +"chokidar@npm:>=3.0.0 <4.0.0": + version: 3.5.3 + resolution: "chokidar@npm:3.5.3" + dependencies: + anymatch: ~3.1.2 + braces: ~3.0.2 + fsevents: ~2.3.2 + glob-parent: ~5.1.2 + is-binary-path: ~2.1.0 + is-glob: ~4.0.1 + normalize-path: ~3.0.0 + readdirp: ~3.6.0 + dependenciesMeta: + fsevents: + optional: true + checksum: b49fcde40176ba007ff361b198a2d35df60d9bb2a5aab228279eb810feae9294a6b4649ab15981304447afe1e6ffbf4788ad5db77235dc770ab777c6e771980c + languageName: node + linkType: hard + +"chownr@npm:^2.0.0": + version: 2.0.0 + resolution: "chownr@npm:2.0.0" + checksum: c57cf9dd0791e2f18a5ee9c1a299ae6e801ff58fee96dc8bfd0dcb4738a6ce58dd252a3605b1c93c6418fe4f9d5093b28ffbf4d66648cb2a9c67eaef9679be2f + languageName: node + linkType: hard + +"clean-stack@npm:^2.0.0": + version: 2.2.0 + resolution: "clean-stack@npm:2.2.0" + checksum: 2ac8cd2b2f5ec986a3c743935ec85b07bc174d5421a5efc8017e1f146a1cf5f781ae962618f416352103b32c9cd7e203276e8c28241bbe946160cab16149fb68 + languageName: node + linkType: hard + +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + +"code-block-writer@npm:^11.0.0": + version: 11.0.3 + resolution: "code-block-writer@npm:11.0.3" + checksum: f0a2605f19963d7087267c9b0fd0b05a6638a50e7b29b70f97aa01a514f59475b0626f8aa092188df853ee6d96745426dfa132d6a677795df462c6ce32c21639 + languageName: node + linkType: hard + +"color-convert@npm:^1.9.0": + version: 1.9.3 + resolution: "color-convert@npm:1.9.3" + dependencies: + color-name: 1.1.3 + checksum: fd7a64a17cde98fb923b1dd05c5f2e6f7aefda1b60d67e8d449f9328b4e53b228a428fd38bfeaeb2db2ff6b6503a776a996150b80cdf224062af08a5c8a3a203 + languageName: node + linkType: hard + +"color-convert@npm:^2.0.1": + version: 2.0.1 + resolution: "color-convert@npm:2.0.1" + dependencies: + color-name: ~1.1.4 + checksum: 79e6bdb9fd479a205c71d89574fccfb22bd9053bd98c6c4d870d65c132e5e904e6034978e55b43d69fcaa7433af2016ee203ce76eeba9cfa554b373e7f7db336 + languageName: node + linkType: hard + +"color-name@npm:1.1.3": + version: 1.1.3 + resolution: "color-name@npm:1.1.3" + checksum: 09c5d3e33d2105850153b14466501f2bfb30324a2f76568a408763a3b7433b0e50e5b4ab1947868e65cb101bb7cb75029553f2c333b6d4b8138a73fcc133d69d + languageName: node + linkType: hard + +"color-name@npm:~1.1.4": + version: 1.1.4 + resolution: "color-name@npm:1.1.4" + checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610 + languageName: node + linkType: hard + +"color-support@npm:^1.1.3": + version: 1.1.3 + resolution: "color-support@npm:1.1.3" + bin: + color-support: bin.js + checksum: 9b7356817670b9a13a26ca5af1c21615463b500783b739b7634a0c2047c16cef4b2865d7576875c31c3cddf9dd621fa19285e628f20198b233a5cfdda6d0793b + languageName: node + linkType: hard + +"colord@npm:^2.9.1": + version: 2.9.3 + resolution: "colord@npm:2.9.3" + checksum: 95d909bfbcfd8d5605cbb5af56f2d1ce2b323990258fd7c0d2eb0e6d3bb177254d7fb8213758db56bb4ede708964f78c6b992b326615f81a18a6aaf11d64c650 + languageName: node + linkType: hard + +"commander@npm:^2.20.0, commander@npm:^2.20.3": + version: 2.20.3 + resolution: "commander@npm:2.20.3" + checksum: ab8c07884e42c3a8dbc5dd9592c606176c7eb5c1ca5ff274bcf907039b2c41de3626f684ea75ccf4d361ba004bbaff1f577d5384c155f3871e456bdf27becf9e + languageName: node + linkType: hard + +"commander@npm:^6.2.1": + version: 6.2.1 + resolution: "commander@npm:6.2.1" + checksum: d7090410c0de6bc5c67d3ca41c41760d6d268f3c799e530aafb73b7437d1826bbf0d2a3edac33f8b57cc9887b4a986dce307fa5557e109be40eadb7c43b21742 + languageName: node + linkType: hard + +"commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 53501cbeee61d5157546c0bef0fedb6cdfc763a882136284bed9a07225f09a14b82d2a84e7637edfd1a679fb35ed9502fd58ef1d091e6287f60d790147f68ddc + languageName: node + linkType: hard + +"commondir@npm:^1.0.1": + version: 1.0.1 + resolution: "commondir@npm:1.0.1" + checksum: 59715f2fc456a73f68826285718503340b9f0dd89bfffc42749906c5cf3d4277ef11ef1cca0350d0e79204f00f1f6d83851ececc9095dc88512a697ac0b9bdcb + languageName: node + linkType: hard + +"component-emitter@npm:^1.3.0": + version: 1.3.0 + resolution: "component-emitter@npm:1.3.0" + checksum: b3c46de38ffd35c57d1c02488355be9f218e582aec72d72d1b8bbec95a3ac1b38c96cd6e03ff015577e68f550fbb361a3bfdbd9bb248be9390b7b3745691be6b + languageName: node + linkType: hard + +"concat-map@npm:0.0.1": + version: 0.0.1 + resolution: "concat-map@npm:0.0.1" + checksum: 902a9f5d8967a3e2faf138d5cb784b9979bad2e6db5357c5b21c568df4ebe62bcb15108af1b2253744844eb964fc023fbd9afbbbb6ddd0bcc204c6fb5b7bf3af + languageName: node + linkType: hard + +"confusing-browser-globals@npm:^1.0.10": + version: 1.0.11 + resolution: "confusing-browser-globals@npm:1.0.11" + checksum: 3afc635abd37e566477f610e7978b15753f0e84025c25d49236f1f14d480117185516bdd40d2a2167e6bed8048641a9854964b9c067e3dcdfa6b5d0ad3c3a5ef + languageName: node + linkType: hard + +"console-control-strings@npm:^1.1.0": + version: 1.1.0 + resolution: "console-control-strings@npm:1.1.0" + checksum: 8755d76787f94e6cf79ce4666f0c5519906d7f5b02d4b884cf41e11dcd759ed69c57da0670afd9236d229a46e0f9cf519db0cd829c6dca820bb5a5c3def584ed + languageName: node + linkType: hard + +"convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 + languageName: node + linkType: hard + +"cosmiconfig@npm:^7.0.1": + version: 7.1.0 + resolution: "cosmiconfig@npm:7.1.0" + dependencies: + "@types/parse-json": ^4.0.0 + import-fresh: ^3.2.1 + parse-json: ^5.0.0 + path-type: ^4.0.0 + yaml: ^1.10.0 + checksum: c53bf7befc1591b2651a22414a5e786cd5f2eeaa87f3678a3d49d6069835a9d8d1aef223728e98aa8fec9a95bf831120d245096db12abe019fecb51f5696c96f + languageName: node + linkType: hard + +"cross-spawn@npm:^7.0.2": + version: 7.0.3 + resolution: "cross-spawn@npm:7.0.3" + dependencies: + path-key: ^3.1.0 + shebang-command: ^2.0.0 + which: ^2.0.1 + checksum: 671cc7c7288c3a8406f3c69a3ae2fc85555c04169e9d611def9a675635472614f1c0ed0ef80955d5b6d4e724f6ced67f0ad1bb006c2ea643488fcfef994d7f52 + languageName: node + linkType: hard + +"crypto@npm:^1.0.1": + version: 1.0.1 + resolution: "crypto@npm:1.0.1" + checksum: 087fe3165bd94c333a49e6ed66a0193911f63eac38a24f379b3001a5fe260a59c413646e53a0f67875ba13902b2686d81dc703cb2c147a4ec727dcdc04e5645e + languageName: node + linkType: hard + +"css-declaration-sorter@npm:^6.3.1": + version: 6.3.1 + resolution: "css-declaration-sorter@npm:6.3.1" + peerDependencies: + postcss: ^8.0.9 + checksum: ff0d9989ee21ec4c42430b9bb86c43f973ed5024d68f30edc1e3fb07a22828ce3c3e5b922019f2ccbff606722e43c407c5c76e3cddac523ac4afcb31e4b2601c + languageName: node + linkType: hard + +"css-select@npm:^4.1.3": + version: 4.3.0 + resolution: "css-select@npm:4.3.0" + dependencies: + boolbase: ^1.0.0 + css-what: ^6.0.1 + domhandler: ^4.3.1 + domutils: ^2.8.0 + nth-check: ^2.0.1 + checksum: d6202736839194dd7f910320032e7cfc40372f025e4bf21ca5bf6eb0a33264f322f50ba9c0adc35dadd342d3d6fae5ca244779a4873afbfa76561e343f2058e0 + languageName: node + linkType: hard + +"css-tree@npm:^1.1.2, css-tree@npm:^1.1.3": + version: 1.1.3 + resolution: "css-tree@npm:1.1.3" + dependencies: + mdn-data: 2.0.14 + source-map: ^0.6.1 + checksum: 79f9b81803991b6977b7fcb1588799270438274d89066ce08f117f5cdb5e20019b446d766c61506dd772c839df84caa16042d6076f20c97187f5abe3b50e7d1f + languageName: node + linkType: hard + +"css-what@npm:^6.0.1": + version: 6.1.0 + resolution: "css-what@npm:6.1.0" + checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe + languageName: node + linkType: hard + +"cssesc@npm:^3.0.0": + version: 3.0.0 + resolution: "cssesc@npm:3.0.0" + bin: + cssesc: bin/cssesc + checksum: f8c4ababffbc5e2ddf2fa9957dda1ee4af6048e22aeda1869d0d00843223c1b13ad3f5d88b51caa46c994225eacb636b764eb807a8883e2fb6f99b4f4e8c48b2 + languageName: node + linkType: hard + +"cssfilter@npm:0.0.10": + version: 0.0.10 + resolution: "cssfilter@npm:0.0.10" + checksum: bc2c52bbb3426c3f2e4832edb6f8573e6cfa65b40b540932762d1e018f0f0157725e2991b77344bbc8266c6bbf4daa2803b0707cfb1bd0877505bf83a68e4b04 + languageName: node + linkType: hard + +"cssnano-preset-default@npm:^5.2.13": + version: 5.2.13 + resolution: "cssnano-preset-default@npm:5.2.13" + dependencies: + css-declaration-sorter: ^6.3.1 + cssnano-utils: ^3.1.0 + postcss-calc: ^8.2.3 + postcss-colormin: ^5.3.0 + postcss-convert-values: ^5.1.3 + postcss-discard-comments: ^5.1.2 + postcss-discard-duplicates: ^5.1.0 + postcss-discard-empty: ^5.1.1 + postcss-discard-overridden: ^5.1.0 + postcss-merge-longhand: ^5.1.7 + postcss-merge-rules: ^5.1.3 + postcss-minify-font-values: ^5.1.0 + postcss-minify-gradients: ^5.1.1 + postcss-minify-params: ^5.1.4 + postcss-minify-selectors: ^5.2.1 + postcss-normalize-charset: ^5.1.0 + postcss-normalize-display-values: ^5.1.0 + postcss-normalize-positions: ^5.1.1 + postcss-normalize-repeat-style: ^5.1.1 + postcss-normalize-string: ^5.1.0 + postcss-normalize-timing-functions: ^5.1.0 + postcss-normalize-unicode: ^5.1.1 + postcss-normalize-url: ^5.1.0 + postcss-normalize-whitespace: ^5.1.1 + postcss-ordered-values: ^5.1.3 + postcss-reduce-initial: ^5.1.1 + postcss-reduce-transforms: ^5.1.0 + postcss-svgo: ^5.1.0 + postcss-unique-selectors: ^5.1.1 + peerDependencies: + postcss: ^8.2.15 + checksum: f773de44f67f71e7301e1f4b4664b894c3a48bba4dadc16c559acd0b14ceafed228bdc76fe19d500b0ded9394732377069daadff2184465fa369f8dfd72d47e2 + languageName: node + linkType: hard + +"cssnano-utils@npm:^3.1.0": + version: 3.1.0 + resolution: "cssnano-utils@npm:3.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: 975c84ce9174cf23bb1da1e9faed8421954607e9ea76440cd3bb0c1bea7e17e490d800fca5ae2812d1d9e9d5524eef23ede0a3f52497d7ccc628e5d7321536f2 + languageName: node + linkType: hard + +"cssnano@npm:*, cssnano@npm:^5.0.15": + version: 5.1.14 + resolution: "cssnano@npm:5.1.14" + dependencies: + cssnano-preset-default: ^5.2.13 + lilconfig: ^2.0.3 + yaml: ^1.10.2 + peerDependencies: + postcss: ^8.2.15 + checksum: 73463c723c5e598b37b8b4d2f014145bd72133e6581349a1b154904e0830e58de17afb1e801ed3ea3b18e386883964ce4d0299e43d4dc37d339214a956c6697f + languageName: node + linkType: hard + +"csso@npm:^4.2.0": + version: 4.2.0 + resolution: "csso@npm:4.2.0" + dependencies: + css-tree: ^1.1.2 + checksum: 380ba9663da3bcea58dee358a0d8c4468bb6539be3c439dc266ac41c047217f52fd698fb7e4b6b6ccdfb8cf53ef4ceed8cc8ceccb8dfca2aa628319826b5b998 + languageName: node + linkType: hard + +"custom-card-helpers@npm:^1.9.0": + version: 1.9.0 + resolution: "custom-card-helpers@npm:1.9.0" + dependencies: + "@formatjs/intl-utils": ^3.8.4 + home-assistant-js-websocket: ^6.0.1 + intl-messageformat: ^9.11.1 + lit: ^2.1.1 + rollup: ^2.63.0 + superstruct: ^0.15.3 + typescript: ^4.5.4 + checksum: 5cefdad2487c1d9e6a190e1837637817c8f7ac86c970c2117fac9b57493f2b235d99af301a7bafcf0a53d1c42b7e75b2accda63de6381607d4beb6d175aa07f4 + languageName: node + linkType: hard + +"date-fns-tz@npm:^1.3.7": + version: 1.3.8 + resolution: "date-fns-tz@npm:1.3.8" + peerDependencies: + date-fns: ">=2.0.0" + checksum: dbf17cb88de7c7b71a9f86b815ac62ebdbf725c030e78d2421309456b5001a85284a5b6763f4af0ba660183f12e876c165e725a0723f642edb7846407db03133 + languageName: node + linkType: hard + +"date-fns@npm:^2.29.2": + version: 2.29.3 + resolution: "date-fns@npm:2.29.3" + checksum: e01cf5b62af04e05dfff921bb9c9933310ed0e1ae9a81eb8653452e64dc841acf7f6e01e1a5ae5644d0337e9a7f936175fd2cb6819dc122fdd9c5e86c56be484 + languageName: node + linkType: hard + +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": + version: 4.3.4 + resolution: "debug@npm:4.3.4" + dependencies: + ms: 2.1.2 + peerDependenciesMeta: + supports-color: + optional: true + checksum: 3dbad3f94ea64f34431a9cbf0bafb61853eda57bff2880036153438f50fb5a84f27683ba0d8e5426bf41a8c6ff03879488120cf5b3a761e77953169c0600a708 + languageName: node + linkType: hard + +"debug@npm:^3.2.7": + version: 3.2.7 + resolution: "debug@npm:3.2.7" + dependencies: + ms: ^2.1.1 + checksum: b3d8c5940799914d30314b7c3304a43305fd0715581a919dacb8b3176d024a782062368405b47491516d2091d6462d4d11f2f4974a405048094f8bfebfa3071c + languageName: node + linkType: hard + +"decode-uri-component@npm:^0.2.2": + version: 0.2.2 + resolution: "decode-uri-component@npm:0.2.2" + checksum: 95476a7d28f267292ce745eac3524a9079058bbb35767b76e3ee87d42e34cd0275d2eb19d9d08c3e167f97556e8a2872747f5e65cbebcac8b0c98d83e285f139 + languageName: node + linkType: hard + +"deep-is@npm:^0.1.3": + version: 0.1.4 + resolution: "deep-is@npm:0.1.4" + checksum: edb65dd0d7d1b9c40b2f50219aef30e116cedd6fc79290e740972c132c09106d2e80aa0bc8826673dd5a00222d4179c84b36a790eef63a4c4bca75a37ef90804 + languageName: node + linkType: hard + +"deepmerge@npm:^4.2.2": + version: 4.3.0 + resolution: "deepmerge@npm:4.3.0" + checksum: c7980eb5c5be040b371f1df0d566473875cfabed9f672ccc177b81ba8eee5686ce2478de2f1d0076391621cbe729e5eacda397179a59ef0f68901849647db126 + languageName: node + linkType: hard + +"define-lazy-prop@npm:^2.0.0": + version: 2.0.0 + resolution: "define-lazy-prop@npm:2.0.0" + checksum: 0115fdb065e0490918ba271d7339c42453d209d4cb619dfe635870d906731eff3e1ade8028bb461ea27ce8264ec5e22c6980612d332895977e89c1bbc80fcee2 + languageName: node + linkType: hard + +"define-properties@npm:^1.1.3, define-properties@npm:^1.1.4": + version: 1.1.4 + resolution: "define-properties@npm:1.1.4" + dependencies: + has-property-descriptors: ^1.0.0 + object-keys: ^1.1.1 + checksum: ce0aef3f9eb193562b5cfb79b2d2c86b6a109dfc9fdcb5f45d680631a1a908c06824ddcdb72b7573b54e26ace07f0a23420aaba0d5c627b34d2c1de8ef527e2b + languageName: node + linkType: hard + +"delegates@npm:^1.0.0": + version: 1.0.0 + resolution: "delegates@npm:1.0.0" + checksum: a51744d9b53c164ba9c0492471a1a2ffa0b6727451bdc89e31627fdf4adda9d51277cfcbfb20f0a6f08ccb3c436f341df3e92631a3440226d93a8971724771fd + languageName: node + linkType: hard + +"depd@npm:^1.1.2": + version: 1.1.2 + resolution: "depd@npm:1.1.2" + checksum: 6b406620d269619852885ce15965272b829df6f409724415e0002c8632ab6a8c0a08ec1f0bd2add05dc7bd7507606f7e2cc034fa24224ab829580040b835ecd9 + languageName: node + linkType: hard + +"dir-glob@npm:^3.0.1": + version: 3.0.1 + resolution: "dir-glob@npm:3.0.1" + dependencies: + path-type: ^4.0.0 + checksum: fa05e18324510d7283f55862f3161c6759a3f2f8dbce491a2fc14c8324c498286c54282c1f0e933cb930da8419b30679389499b919122952a4f8592362ef4615 + languageName: node + linkType: hard + +"doctrine@npm:^2.1.0": + version: 2.1.0 + resolution: "doctrine@npm:2.1.0" + dependencies: + esutils: ^2.0.2 + checksum: a45e277f7feaed309fe658ace1ff286c6e2002ac515af0aaf37145b8baa96e49899638c7cd47dccf84c3d32abfc113246625b3ac8f552d1046072adee13b0dc8 + languageName: node + linkType: hard + +"doctrine@npm:^3.0.0": + version: 3.0.0 + resolution: "doctrine@npm:3.0.0" + dependencies: + esutils: ^2.0.2 + checksum: fd7673ca77fe26cd5cba38d816bc72d641f500f1f9b25b83e8ce28827fe2da7ad583a8da26ab6af85f834138cf8dae9f69b0cd6ab925f52ddab1754db44d99ce + languageName: node + linkType: hard + +"dom-serializer@npm:^1.0.1": + version: 1.4.1 + resolution: "dom-serializer@npm:1.4.1" + dependencies: + domelementtype: ^2.0.1 + domhandler: ^4.2.0 + entities: ^2.0.0 + checksum: fbb0b01f87a8a2d18e6e5a388ad0f7ec4a5c05c06d219377da1abc7bb0f674d804f4a8a94e3f71ff15f6cb7dcfc75704a54b261db672b9b3ab03da6b758b0b22 + languageName: node + linkType: hard + +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0": + version: 2.3.0 + resolution: "domelementtype@npm:2.3.0" + checksum: ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 + languageName: node + linkType: hard + +"domhandler@npm:^4.2.0, domhandler@npm:^4.3.1": + version: 4.3.1 + resolution: "domhandler@npm:4.3.1" + dependencies: + domelementtype: ^2.2.0 + checksum: 4c665ceed016e1911bf7d1dadc09dc888090b64dee7851cccd2fcf5442747ec39c647bb1cb8c8919f8bbdd0f0c625a6bafeeed4b2d656bbecdbae893f43ffaaa + languageName: node + linkType: hard + +"domutils@npm:^2.8.0": + version: 2.8.0 + resolution: "domutils@npm:2.8.0" + dependencies: + dom-serializer: ^1.0.1 + domelementtype: ^2.2.0 + domhandler: ^4.2.0 + checksum: abf7434315283e9aadc2a24bac0e00eab07ae4313b40cc239f89d84d7315ebdfd2fb1b5bf750a96bc1b4403d7237c7b2ebf60459be394d625ead4ca89b934391 + languageName: node + linkType: hard + +"electron-to-chromium@npm:^1.4.284": + version: 1.4.288 + resolution: "electron-to-chromium@npm:1.4.288" + checksum: 312e1210f62c6bb593d1f57a15a1c64729e6a5edeacecc5d28dfb21f1a78ed3fac11805a0749a4e68f1fe1536229bf9d7bca36e1f34303369248a749a78aec1f + languageName: node + linkType: hard + +"embla-carousel-wheel-gestures@npm:^3.0.0": + version: 3.0.0 + resolution: "embla-carousel-wheel-gestures@npm:3.0.0" + dependencies: + wheel-gestures: ^2.2.5 + peerDependencies: + embla-carousel: ^7.0.0 + checksum: 109c1d10fa6f892fe4c627212ee5d24ae2d2d0c28210af16a9770901c2b65faf32eb1a5c95e87451aec9407ec8e3248e49789d6998b7079c83fd13b50532431d + languageName: node + linkType: hard + +"embla-carousel@npm:^7.0.2": + version: 7.0.9 + resolution: "embla-carousel@npm:7.0.9" + checksum: dabb371a44c2d67f21aa7a0ff3f452a5c2678b79e80cb251e0a83b707c4b684fbdbe010927142cdcae3d1c9711a269e05655bd05c39608337fd1b6f5d2c20371 + languageName: node + linkType: hard + +"emoji-regex@npm:^8.0.0": + version: 8.0.0 + resolution: "emoji-regex@npm:8.0.0" + checksum: d4c5c39d5a9868b5fa152f00cada8a936868fd3367f33f71be515ecee4c803132d11b31a6222b2571b1e5f7e13890156a94880345594d0ce7e3c9895f560f192 + languageName: node + linkType: hard + +"emojis-list@npm:^3.0.0": + version: 3.0.0 + resolution: "emojis-list@npm:3.0.0" + checksum: ddaaa02542e1e9436c03970eeed445f4ed29a5337dfba0fe0c38dfdd2af5da2429c2a0821304e8a8d1cadf27fdd5b22ff793571fa803ae16852a6975c65e8e70 + languageName: node + linkType: hard + +"encoding@npm:^0.1.13": + version: 0.1.13 + resolution: "encoding@npm:0.1.13" + dependencies: + iconv-lite: ^0.6.2 + checksum: bb98632f8ffa823996e508ce6a58ffcf5856330fde839ae42c9e1f436cc3b5cc651d4aeae72222916545428e54fd0f6aa8862fd8d25bdbcc4589f1e3f3715e7f + languageName: node + linkType: hard + +"entities@npm:^2.0.0": + version: 2.2.0 + resolution: "entities@npm:2.2.0" + checksum: 19010dacaf0912c895ea262b4f6128574f9ccf8d4b3b65c7e8334ad0079b3706376360e28d8843ff50a78aabcb8f08f0a32dbfacdc77e47ed77ca08b713669b3 + languageName: node + linkType: hard + +"env-paths@npm:^2.2.0": + version: 2.2.1 + resolution: "env-paths@npm:2.2.1" + checksum: 65b5df55a8bab92229ab2b40dad3b387fad24613263d103a97f91c9fe43ceb21965cd3392b1ccb5d77088021e525c4e0481adb309625d0cb94ade1d1fb8dc17e + languageName: node + linkType: hard + +"err-code@npm:^2.0.2": + version: 2.0.3 + resolution: "err-code@npm:2.0.3" + checksum: 8b7b1be20d2de12d2255c0bc2ca638b7af5171142693299416e6a9339bd7d88fc8d7707d913d78e0993176005405a236b066b45666b27b797252c771156ace54 + languageName: node + linkType: hard + +"error-ex@npm:^1.3.1": + version: 1.3.2 + resolution: "error-ex@npm:1.3.2" + dependencies: + is-arrayish: ^0.2.1 + checksum: c1c2b8b65f9c91b0f9d75f0debaa7ec5b35c266c2cac5de412c1a6de86d4cbae04ae44e510378cb14d032d0645a36925d0186f8bb7367bcc629db256b743a001 + languageName: node + linkType: hard + +"es-abstract@npm:^1.19.0, es-abstract@npm:^1.20.4": + version: 1.21.1 + resolution: "es-abstract@npm:1.21.1" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + es-set-tostringtag: ^2.0.1 + es-to-primitive: ^1.2.1 + function-bind: ^1.1.1 + function.prototype.name: ^1.1.5 + get-intrinsic: ^1.1.3 + get-symbol-description: ^1.0.0 + globalthis: ^1.0.3 + gopd: ^1.0.1 + has: ^1.0.3 + has-property-descriptors: ^1.0.0 + has-proto: ^1.0.1 + has-symbols: ^1.0.3 + internal-slot: ^1.0.4 + is-array-buffer: ^3.0.1 + is-callable: ^1.2.7 + is-negative-zero: ^2.0.2 + is-regex: ^1.1.4 + is-shared-array-buffer: ^1.0.2 + is-string: ^1.0.7 + is-typed-array: ^1.1.10 + is-weakref: ^1.0.2 + object-inspect: ^1.12.2 + object-keys: ^1.1.1 + object.assign: ^4.1.4 + regexp.prototype.flags: ^1.4.3 + safe-regex-test: ^1.0.0 + string.prototype.trimend: ^1.0.6 + string.prototype.trimstart: ^1.0.6 + typed-array-length: ^1.0.4 + unbox-primitive: ^1.0.2 + which-typed-array: ^1.1.9 + checksum: 23ff60d42d17a55d150e7bcedbdb065d4077a8b98c436e0e2e1ef4dd532a6d78a56028673de0bd8ed464a43c46ba781c50d9af429b6a17e44dbd14c7d7fb7926 + languageName: node + linkType: hard + +"es-set-tostringtag@npm:^2.0.1": + version: 2.0.1 + resolution: "es-set-tostringtag@npm:2.0.1" + dependencies: + get-intrinsic: ^1.1.3 + has: ^1.0.3 + has-tostringtag: ^1.0.0 + checksum: ec416a12948cefb4b2a5932e62093a7cf36ddc3efd58d6c58ca7ae7064475ace556434b869b0bbeb0c365f1032a8ccd577211101234b69837ad83ad204fff884 + languageName: node + linkType: hard + +"es-shim-unscopables@npm:^1.0.0": + version: 1.0.0 + resolution: "es-shim-unscopables@npm:1.0.0" + dependencies: + has: ^1.0.3 + checksum: 83e95cadbb6ee44d3644dfad60dcad7929edbc42c85e66c3e99aefd68a3a5c5665f2686885cddb47dfeabfd77bd5ea5a7060f2092a955a729bbd8834f0d86fa1 + languageName: node + linkType: hard + +"es-to-primitive@npm:^1.2.1": + version: 1.2.1 + resolution: "es-to-primitive@npm:1.2.1" + dependencies: + is-callable: ^1.1.4 + is-date-object: ^1.0.1 + is-symbol: ^1.0.2 + checksum: 4ead6671a2c1402619bdd77f3503991232ca15e17e46222b0a41a5d81aebc8740a77822f5b3c965008e631153e9ef0580540007744521e72de8e33599fca2eed + languageName: node + linkType: hard + +"escalade@npm:^3.1.1": + version: 3.1.1 + resolution: "escalade@npm:3.1.1" + checksum: a3e2a99f07acb74b3ad4989c48ca0c3140f69f923e56d0cba0526240ee470b91010f9d39001f2a4a313841d237ede70a729e92125191ba5d21e74b106800b133 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^1.0.5": + version: 1.0.5 + resolution: "escape-string-regexp@npm:1.0.5" + checksum: 6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 + languageName: node + linkType: hard + +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 98b48897d93060f2322108bf29db0feba7dd774be96cd069458d1453347b25ce8682ecc39859d4bca2203cc0ab19c237bcc71755eff49a0f8d90beadeeba5cc5 + languageName: node + linkType: hard + +"eslint-config-airbnb-base@npm:^15.0.0": + version: 15.0.0 + resolution: "eslint-config-airbnb-base@npm:15.0.0" + dependencies: + confusing-browser-globals: ^1.0.10 + object.assign: ^4.1.2 + object.entries: ^1.1.5 + semver: ^6.3.0 + peerDependencies: + eslint: ^7.32.0 || ^8.2.0 + eslint-plugin-import: ^2.25.2 + checksum: 38626bad2ce2859fccac86b30cd2b86c9b7d8d71d458331860861dc05290a5b198bded2f4fb89efcb9046ec48f8ab4c4fb00365ba8916f27b172671da28b93ea + languageName: node + linkType: hard + +"eslint-config-prettier@npm:^8.5.0": + version: 8.6.0 + resolution: "eslint-config-prettier@npm:8.6.0" + peerDependencies: + eslint: ">=7.0.0" + bin: + eslint-config-prettier: bin/cli.js + checksum: ff0d0dfc839a556355422293428637e8d35693de58dabf8638bf0b6529131a109d0b2ade77521aa6e54573bb842d7d9d322e465dd73dd61c7590fa3834c3fa81 + languageName: node + linkType: hard + +"eslint-import-resolver-node@npm:^0.3.7": + version: 0.3.7 + resolution: "eslint-import-resolver-node@npm:0.3.7" + dependencies: + debug: ^3.2.7 + is-core-module: ^2.11.0 + resolve: ^1.22.1 + checksum: 3379aacf1d2c6952c1b9666c6fa5982c3023df695430b0d391c0029f6403a7775414873d90f397e98ba6245372b6c8960e16e74d9e4a3b0c0a4582f3bdbe3d6e + languageName: node + linkType: hard + +"eslint-module-utils@npm:^2.7.4": + version: 2.7.4 + resolution: "eslint-module-utils@npm:2.7.4" + dependencies: + debug: ^3.2.7 + peerDependenciesMeta: + eslint: + optional: true + checksum: 5da13645daff145a5c922896b258f8bba560722c3767254e458d894ff5fbb505d6dfd945bffa932a5b0ae06714da2379bd41011c4c20d2d59cc83e23895360f7 + languageName: node + linkType: hard + +"eslint-plugin-import@npm:^2.25.4": + version: 2.27.5 + resolution: "eslint-plugin-import@npm:2.27.5" + dependencies: + array-includes: ^3.1.6 + array.prototype.flat: ^1.3.1 + array.prototype.flatmap: ^1.3.1 + debug: ^3.2.7 + doctrine: ^2.1.0 + eslint-import-resolver-node: ^0.3.7 + eslint-module-utils: ^2.7.4 + has: ^1.0.3 + is-core-module: ^2.11.0 + is-glob: ^4.0.3 + minimatch: ^3.1.2 + object.values: ^1.1.6 + resolve: ^1.22.1 + semver: ^6.3.0 + tsconfig-paths: ^3.14.1 + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + checksum: f500571a380167e25d72a4d925ef9a7aae8899eada57653e5f3051ec3d3c16d08271fcefe41a30a9a2f4fefc232f066253673ee4ea77b30dba65ae173dade85d + languageName: node + linkType: hard + +"eslint-plugin-prettier@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-plugin-prettier@npm:4.2.1" + dependencies: + prettier-linter-helpers: ^1.0.0 + peerDependencies: + eslint: ">=7.28.0" + prettier: ">=2.0.0" + peerDependenciesMeta: + eslint-config-prettier: + optional: true + checksum: b9e839d2334ad8ec7a5589c5cb0f219bded260839a857d7a486997f9870e95106aa59b8756ff3f37202085ebab658de382b0267cae44c3a7f0eb0bcc03a4f6d6 + languageName: node + linkType: hard + +"eslint-scope@npm:^5.1.1": + version: 5.1.1 + resolution: "eslint-scope@npm:5.1.1" + dependencies: + esrecurse: ^4.3.0 + estraverse: ^4.1.1 + checksum: 47e4b6a3f0cc29c7feedee6c67b225a2da7e155802c6ea13bbef4ac6b9e10c66cd2dcb987867ef176292bf4e64eccc680a49e35e9e9c669f4a02bac17e86abdb + languageName: node + linkType: hard + +"eslint-scope@npm:^7.1.1": + version: 7.1.1 + resolution: "eslint-scope@npm:7.1.1" + dependencies: + esrecurse: ^4.3.0 + estraverse: ^5.2.0 + checksum: 9f6e974ab2db641ca8ab13508c405b7b859e72afe9f254e8131ff154d2f40c99ad4545ce326fd9fde3212ff29707102562a4834f1c48617b35d98c71a97fbf3e + languageName: node + linkType: hard + +"eslint-utils@npm:^3.0.0": + version: 3.0.0 + resolution: "eslint-utils@npm:3.0.0" + dependencies: + eslint-visitor-keys: ^2.0.0 + peerDependencies: + eslint: ">=5" + checksum: 0668fe02f5adab2e5a367eee5089f4c39033af20499df88fe4e6aba2015c20720404d8c3d6349b6f716b08fdf91b9da4e5d5481f265049278099c4c836ccb619 + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^2.0.0": + version: 2.1.0 + resolution: "eslint-visitor-keys@npm:2.1.0" + checksum: e3081d7dd2611a35f0388bbdc2f5da60b3a3c5b8b6e928daffff7391146b434d691577aa95064c8b7faad0b8a680266bcda0a42439c18c717b80e6718d7e267d + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.3.0": + version: 3.3.0 + resolution: "eslint-visitor-keys@npm:3.3.0" + checksum: d59e68a7c5a6d0146526b0eec16ce87fbf97fe46b8281e0d41384224375c4e52f5ffb9e16d48f4ea50785cde93f766b0c898e31ab89978d88b0e1720fbfb7808 + languageName: node + linkType: hard + +"eslint@npm:^8.23.0": + version: 8.33.0 + resolution: "eslint@npm:8.33.0" + dependencies: + "@eslint/eslintrc": ^1.4.1 + "@humanwhocodes/config-array": ^0.11.8 + "@humanwhocodes/module-importer": ^1.0.1 + "@nodelib/fs.walk": ^1.2.8 + ajv: ^6.10.0 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.3.2 + doctrine: ^3.0.0 + escape-string-regexp: ^4.0.0 + eslint-scope: ^7.1.1 + eslint-utils: ^3.0.0 + eslint-visitor-keys: ^3.3.0 + espree: ^9.4.0 + esquery: ^1.4.0 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + find-up: ^5.0.0 + glob-parent: ^6.0.2 + globals: ^13.19.0 + grapheme-splitter: ^1.0.4 + ignore: ^5.2.0 + import-fresh: ^3.0.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + is-path-inside: ^3.0.3 + js-sdsl: ^4.1.4 + js-yaml: ^4.1.0 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.1.2 + natural-compare: ^1.4.0 + optionator: ^0.9.1 + regexpp: ^3.2.0 + strip-ansi: ^6.0.1 + strip-json-comments: ^3.1.0 + text-table: ^0.2.0 + bin: + eslint: bin/eslint.js + checksum: 727e63ab8b7acf281442323c5971f6afdd5b656fbcebc4476cf54e35af51b2f180617433fc5e1952f0449ca3f43a905527f9407ea4b8a7ea7562fc9c3f278d4c + languageName: node + linkType: hard + +"espree@npm:^9.4.0": + version: 9.4.1 + resolution: "espree@npm:9.4.1" + dependencies: + acorn: ^8.8.0 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^3.3.0 + checksum: 4d266b0cf81c7dfe69e542c7df0f246e78d29f5b04dda36e514eb4c7af117ee6cfbd3280e560571ed82ff6c9c3f0003c05b82583fc7a94006db7497c4fe4270e + languageName: node + linkType: hard + +"esquery@npm:^1.4.0": + version: 1.4.0 + resolution: "esquery@npm:1.4.0" + dependencies: + estraverse: ^5.1.0 + checksum: a0807e17abd7fbe5fbd4fab673038d6d8a50675cdae6b04fbaa520c34581be0c5fa24582990e8acd8854f671dd291c78bb2efb9e0ed5b62f33bac4f9cf820210 + languageName: node + linkType: hard + +"esrecurse@npm:^4.3.0": + version: 4.3.0 + resolution: "esrecurse@npm:4.3.0" + dependencies: + estraverse: ^5.2.0 + checksum: ebc17b1a33c51cef46fdc28b958994b1dc43cd2e86237515cbc3b4e5d2be6a811b2315d0a1a4d9d340b6d2308b15322f5c8291059521cc5f4802f65e7ec32837 + languageName: node + linkType: hard + +"estraverse@npm:^4.1.1": + version: 4.3.0 + resolution: "estraverse@npm:4.3.0" + checksum: a6299491f9940bb246124a8d44b7b7a413a8336f5436f9837aaa9330209bd9ee8af7e91a654a3545aee9c54b3308e78ee360cef1d777d37cfef77d2fa33b5827 + languageName: node + linkType: hard + +"estraverse@npm:^5.1.0, estraverse@npm:^5.2.0": + version: 5.3.0 + resolution: "estraverse@npm:5.3.0" + checksum: 072780882dc8416ad144f8fe199628d2b3e7bbc9989d9ed43795d2c90309a2047e6bc5979d7e2322a341163d22cfad9e21f4110597fe487519697389497e4e2b + languageName: node + linkType: hard + +"estree-walker@npm:^1.0.1": + version: 1.0.1 + resolution: "estree-walker@npm:1.0.1" + checksum: 7e70da539691f6db03a08e7ce94f394ce2eef4180e136d251af299d41f92fb2d28ebcd9a6e393e3728d7970aeb5358705ddf7209d52fbcb2dd4693f95dcf925f + languageName: node + linkType: hard + +"estree-walker@npm:^2.0.1": + version: 2.0.2 + resolution: "estree-walker@npm:2.0.2" + checksum: 6151e6f9828abe2259e57f5fd3761335bb0d2ebd76dc1a01048ccee22fabcfef3c0859300f6d83ff0d1927849368775ec5a6d265dde2f6de5a1be1721cd94efc + languageName: node + linkType: hard + +"esutils@npm:^2.0.2": + version: 2.0.3 + resolution: "esutils@npm:2.0.3" + checksum: 22b5b08f74737379a840b8ed2036a5fb35826c709ab000683b092d9054e5c2a82c27818f12604bfc2a9a76b90b6834ef081edbc1c7ae30d1627012e067c6ec87 + languageName: node + linkType: hard + +"eventemitter3@npm:^4.0.4": + version: 4.0.7 + resolution: "eventemitter3@npm:4.0.7" + checksum: 1875311c42fcfe9c707b2712c32664a245629b42bb0a5a84439762dd0fd637fc54d078155ea83c2af9e0323c9ac13687e03cfba79b03af9f40c89b4960099374 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: e21a9d8d84f53493b6aa15efc9cfd53dd5b714a1f23f67fb5dc8f574af80df889b3bce25dc081887c6d25457cce704e636395333abad896ccdec03abaf1f3f9d + languageName: node + linkType: hard + +"fast-diff@npm:^1.1.2": + version: 1.2.0 + resolution: "fast-diff@npm:1.2.0" + checksum: 1b5306eaa9e826564d9e5ffcd6ebd881eb5f770b3f977fcbf38f05c824e42172b53c79920e8429c54eb742ce15a0caf268b0fdd5b38f6de52234c4a8368131ae + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9": + version: 3.2.12 + resolution: "fast-glob@npm:3.2.12" + dependencies: + "@nodelib/fs.stat": ^2.0.2 + "@nodelib/fs.walk": ^1.2.3 + glob-parent: ^5.1.2 + merge2: ^1.3.0 + micromatch: ^4.0.4 + checksum: 0b1990f6ce831c7e28c4d505edcdaad8e27e88ab9fa65eedadb730438cfc7cde4910d6c975d6b7b8dc8a73da4773702ebcfcd6e3518e73938bb1383badfe01c2 + languageName: node + linkType: hard + +"fast-json-stable-stringify@npm:^2.0.0": + version: 2.1.0 + resolution: "fast-json-stable-stringify@npm:2.1.0" + checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb + languageName: node + linkType: hard + +"fast-levenshtein@npm:^2.0.6": + version: 2.0.6 + resolution: "fast-levenshtein@npm:2.0.6" + checksum: 92cfec0a8dfafd9c7a15fba8f2cc29cd0b62b85f056d99ce448bbcd9f708e18ab2764bda4dd5158364f4145a7c72788538994f0d1787b956ef0d1062b0f7c24c + languageName: node + linkType: hard + +"fastq@npm:^1.6.0": + version: 1.15.0 + resolution: "fastq@npm:1.15.0" + dependencies: + reusify: ^1.0.4 + checksum: 0170e6bfcd5d57a70412440b8ef600da6de3b2a6c5966aeaf0a852d542daff506a0ee92d6de7679d1de82e644bce69d7a574a6c93f0b03964b5337eed75ada1a + languageName: node + linkType: hard + +"file-entry-cache@npm:^6.0.1": + version: 6.0.1 + resolution: "file-entry-cache@npm:6.0.1" + dependencies: + flat-cache: ^3.0.4 + checksum: f49701feaa6314c8127c3c2f6173cfefff17612f5ed2daaafc6da13b5c91fd43e3b2a58fd0d63f9f94478a501b167615931e7200e31485e320f74a33885a9c74 + languageName: node + linkType: hard + +"fill-range@npm:^7.0.1": + version: 7.0.1 + resolution: "fill-range@npm:7.0.1" + dependencies: + to-regex-range: ^5.0.1 + checksum: cc283f4e65b504259e64fd969bcf4def4eb08d85565e906b7d36516e87819db52029a76b6363d0f02d0d532f0033c9603b9e2d943d56ee3b0d4f7ad3328ff917 + languageName: node + linkType: hard + +"filter-obj@npm:^1.1.0": + version: 1.1.0 + resolution: "filter-obj@npm:1.1.0" + checksum: cf2104a7c45ff48e7f505b78a3991c8f7f30f28bd8106ef582721f321f1c6277f7751aacd5d83026cb079d9d5091082f588d14a72e7c5d720ece79118fa61e10 + languageName: node + linkType: hard + +"find-cache-dir@npm:^3.3.2": + version: 3.3.2 + resolution: "find-cache-dir@npm:3.3.2" + dependencies: + commondir: ^1.0.1 + make-dir: ^3.0.2 + pkg-dir: ^4.1.0 + checksum: 1e61c2e64f5c0b1c535bd85939ae73b0e5773142713273818cc0b393ee3555fb0fd44e1a5b161b8b6c3e03e98c2fcc9c227d784850a13a90a8ab576869576817 + languageName: node + linkType: hard + +"find-up@npm:^4.0.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: ^5.0.0 + path-exists: ^4.0.0 + checksum: 4c172680e8f8c1f78839486e14a43ef82e9decd0e74145f40707cc42e7420506d5ec92d9a11c22bd2c48fb0c384ea05dd30e10dd152fefeec6f2f75282a8b844 + languageName: node + linkType: hard + +"find-up@npm:^5.0.0": + version: 5.0.0 + resolution: "find-up@npm:5.0.0" + dependencies: + locate-path: ^6.0.0 + path-exists: ^4.0.0 + checksum: 07955e357348f34660bde7920783204ff5a26ac2cafcaa28bace494027158a97b9f56faaf2d89a6106211a8174db650dd9f503f9c0d526b1202d5554a00b9095 + languageName: node + linkType: hard + +"flat-cache@npm:^3.0.4": + version: 3.0.4 + resolution: "flat-cache@npm:3.0.4" + dependencies: + flatted: ^3.1.0 + rimraf: ^3.0.2 + checksum: 4fdd10ecbcbf7d520f9040dd1340eb5dfe951e6f0ecf2252edeec03ee68d989ec8b9a20f4434270e71bcfd57800dc09b3344fca3966b2eb8f613072c7d9a2365 + languageName: node + linkType: hard + +"flatted@npm:^3.1.0": + version: 3.2.7 + resolution: "flatted@npm:3.2.7" + checksum: 427633049d55bdb80201c68f7eb1cbd533e03eac541f97d3aecab8c5526f12a20ccecaeede08b57503e772c769e7f8680b37e8d482d1e5f8d7e2194687f9ea35 + languageName: node + linkType: hard + +"for-each@npm:^0.3.3": + version: 0.3.3 + resolution: "for-each@npm:0.3.3" + dependencies: + is-callable: ^1.1.3 + checksum: 6c48ff2bc63362319c65e2edca4a8e1e3483a2fabc72fbe7feaf8c73db94fc7861bd53bc02c8a66a0c1dd709da6b04eec42e0abdd6b40ce47305ae92a25e5d28 + languageName: node + linkType: hard + +"frigate-hass-card@workspace:.": + version: 0.0.0-use.local + resolution: "frigate-hass-card@workspace:." + dependencies: + "@babel/core": ^7.19.0 + "@babel/plugin-proposal-class-properties": ^7.18.6 + "@babel/plugin-proposal-decorators": ^7.19.0 + "@cycjimmy/jsmpeg-player": ^6.0.4 + "@egjs/hammerjs": ^2.0.17 + "@graphiteds/core": ^1.9.6 + "@lit-labs/scoped-registry-mixin": ^1.0.1 + "@lit-labs/task": ^1.1.3 + "@rollup/plugin-babel": ^5.3.1 + "@rollup/plugin-commonjs": ^22.0.2 + "@rollup/plugin-image": ^2.1.1 + "@rollup/plugin-json": ^4.1.0 + "@rollup/plugin-node-resolve": ^13.3.0 + "@rollup/plugin-replace": ^4.0.0 + "@types/bluebird": ^3.5.36 + "@types/lodash-es": ^4.17.5 + "@typescript-eslint/eslint-plugin": ^5.36.2 + "@typescript-eslint/parser": ^5.36.2 + component-emitter: ^1.3.0 + crypto: ^1.0.1 + custom-card-helpers: ^1.9.0 + date-fns: ^2.29.2 + date-fns-tz: ^1.3.7 + embla-carousel: ^7.0.2 + embla-carousel-wheel-gestures: ^3.0.0 + eslint: ^8.23.0 + eslint-config-airbnb-base: ^15.0.0 + eslint-config-prettier: ^8.5.0 + eslint-plugin-import: ^2.25.4 + eslint-plugin-prettier: ^4.2.1 + home-assistant-js-websocket: ^8.0.0 + keycharm: ^0.4.0 + lit: ^2.3.1 + lodash-es: ^4.17.21 + moment: ^2.29.4 + prettier: ^2.6.0 + propagating-hammerjs: ^2.0.1 + quick-lru: ^6.1.0 + rollup: ^2.79.0 + rollup-plugin-git-info: ^1.0.0 + rollup-plugin-serve: ^2.0.1 + rollup-plugin-styles: ^4.0.0 + rollup-plugin-terser: ^7.0.2 + rollup-plugin-typescript2: ^0.33.0 + rollup-plugin-visualizer: ^5.8.2 + sass: ^1.54.9 + screenfull: ^6.0.2 + side-drawer: ^3.1.0 + ts-prune: ^0.10.3 + ts-toolbelt: ^9.6.0 + typescript: ^4.8.3 + uuid: ^8.3.2 + vis-data: ^7.1.4 + vis-timeline: ^7.7.0 + vis-util: ^5.0.2 + xss: ^1.0.14 + zod: ^3.20.2 + languageName: unknown + linkType: soft + +"fs-extra@npm:^10.0.0": + version: 10.1.0 + resolution: "fs-extra@npm:10.1.0" + dependencies: + graceful-fs: ^4.2.0 + jsonfile: ^6.0.1 + universalify: ^2.0.0 + checksum: dc94ab37096f813cc3ca12f0f1b5ad6744dfed9ed21e953d72530d103cea193c2f81584a39e9dee1bea36de5ee66805678c0dddc048e8af1427ac19c00fffc50 + languageName: node + linkType: hard + +"fs-minipass@npm:^2.0.0, fs-minipass@npm:^2.1.0": + version: 2.1.0 + resolution: "fs-minipass@npm:2.1.0" + dependencies: + minipass: ^3.0.0 + checksum: 1b8d128dae2ac6cc94230cc5ead341ba3e0efaef82dab46a33d171c044caaa6ca001364178d42069b2809c35a1c3c35079a32107c770e9ffab3901b59af8c8b1 + languageName: node + linkType: hard + +"fs.realpath@npm:^1.0.0": + version: 1.0.0 + resolution: "fs.realpath@npm:1.0.0" + checksum: 99ddea01a7e75aa276c250a04eedeffe5662bce66c65c07164ad6264f9de18fb21be9433ead460e54cff20e31721c811f4fb5d70591799df5f85dce6d6746fd0 + languageName: node + linkType: hard + +"fsevents@npm:~2.3.2": + version: 2.3.2 + resolution: "fsevents@npm:2.3.2" + dependencies: + node-gyp: latest + checksum: 97ade64e75091afee5265e6956cb72ba34db7819b4c3e94c431d4be2b19b8bb7a2d4116da417950c3425f17c8fe693d25e20212cac583ac1521ad066b77ae31f + conditions: os=darwin + languageName: node + linkType: hard + +"fsevents@patch:fsevents@~2.3.2#~builtin": + version: 2.3.2 + resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" + dependencies: + node-gyp: latest + conditions: os=darwin + languageName: node + linkType: hard + +"function-bind@npm:^1.1.1": + version: 1.1.1 + resolution: "function-bind@npm:1.1.1" + checksum: b32fbaebb3f8ec4969f033073b43f5c8befbb58f1a79e12f1d7490358150359ebd92f49e72ff0144f65f2c48ea2a605bff2d07965f548f6474fd8efd95bf361a + languageName: node + linkType: hard + +"function.prototype.name@npm:^1.1.5": + version: 1.1.5 + resolution: "function.prototype.name@npm:1.1.5" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + es-abstract: ^1.19.0 + functions-have-names: ^1.2.2 + checksum: acd21d733a9b649c2c442f067567743214af5fa248dbeee69d8278ce7df3329ea5abac572be9f7470b4ec1cd4d8f1040e3c5caccf98ebf2bf861a0deab735c27 + languageName: node + linkType: hard + +"functions-have-names@npm:^1.2.2": + version: 1.2.3 + resolution: "functions-have-names@npm:1.2.3" + checksum: c3f1f5ba20f4e962efb71344ce0a40722163e85bee2101ce25f88214e78182d2d2476aa85ef37950c579eb6cf6ee811c17b3101bb84004bb75655f3e33f3fdb5 + languageName: node + linkType: hard + +"gauge@npm:^4.0.3": + version: 4.0.4 + resolution: "gauge@npm:4.0.4" + dependencies: + aproba: ^1.0.3 || ^2.0.0 + color-support: ^1.1.3 + console-control-strings: ^1.1.0 + has-unicode: ^2.0.1 + signal-exit: ^3.0.7 + string-width: ^4.2.3 + strip-ansi: ^6.0.1 + wide-align: ^1.1.5 + checksum: 788b6bfe52f1dd8e263cda800c26ac0ca2ff6de0b6eee2fe0d9e3abf15e149b651bd27bf5226be10e6e3edb5c4e5d5985a5a1a98137e7a892f75eff76467ad2d + languageName: node + linkType: hard + +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec + languageName: node + linkType: hard + +"get-caller-file@npm:^2.0.5": + version: 2.0.5 + resolution: "get-caller-file@npm:2.0.5" + checksum: b9769a836d2a98c3ee734a88ba712e62703f1df31b94b784762c433c27a386dd6029ff55c2a920c392e33657d80191edbf18c61487e198844844516f843496b9 + languageName: node + linkType: hard + +"get-intrinsic@npm:^1.0.2, get-intrinsic@npm:^1.1.1, get-intrinsic@npm:^1.1.3": + version: 1.2.0 + resolution: "get-intrinsic@npm:1.2.0" + dependencies: + function-bind: ^1.1.1 + has: ^1.0.3 + has-symbols: ^1.0.3 + checksum: 78fc0487b783f5c58cf2dccafc3ae656ee8d2d8062a8831ce4a95e7057af4587a1d4882246c033aca0a7b4965276f4802b45cc300338d1b77a73d3e3e3f4877d + languageName: node + linkType: hard + +"get-symbol-description@npm:^1.0.0": + version: 1.0.0 + resolution: "get-symbol-description@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.1 + checksum: 9ceff8fe968f9270a37a1f73bf3f1f7bda69ca80f4f80850670e0e7b9444ff99323f7ac52f96567f8b5f5fbe7ac717a0d81d3407c7313e82810c6199446a5247 + languageName: node + linkType: hard + +"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": + version: 5.1.2 + resolution: "glob-parent@npm:5.1.2" + dependencies: + is-glob: ^4.0.1 + checksum: f4f2bfe2425296e8a47e36864e4f42be38a996db40420fe434565e4480e3322f18eb37589617a98640c5dc8fdec1a387007ee18dbb1f3f5553409c34d17f425e + languageName: node + linkType: hard + +"glob-parent@npm:^6.0.2": + version: 6.0.2 + resolution: "glob-parent@npm:6.0.2" + dependencies: + is-glob: ^4.0.3 + checksum: c13ee97978bef4f55106b71e66428eb1512e71a7466ba49025fc2aec59a5bfb0954d5abd58fc5ee6c9b076eef4e1f6d3375c2e964b88466ca390da4419a786a8 + languageName: node + linkType: hard + +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.6": + version: 7.2.3 + resolution: "glob@npm:7.2.3" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^3.1.1 + once: ^1.3.0 + path-is-absolute: ^1.0.0 + checksum: 29452e97b38fa704dabb1d1045350fb2467cf0277e155aa9ff7077e90ad81d1ea9d53d3ee63bd37c05b09a065e90f16aec4a65f5b8de401d1dac40bc5605d133 + languageName: node + linkType: hard + +"glob@npm:^8.0.1": + version: 8.1.0 + resolution: "glob@npm:8.1.0" + dependencies: + fs.realpath: ^1.0.0 + inflight: ^1.0.4 + inherits: 2 + minimatch: ^5.0.1 + once: ^1.3.0 + checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 + languageName: node + linkType: hard + +"globals@npm:^11.1.0": + version: 11.12.0 + resolution: "globals@npm:11.12.0" + checksum: 67051a45eca3db904aee189dfc7cd53c20c7d881679c93f6146ddd4c9f4ab2268e68a919df740d39c71f4445d2b38ee360fc234428baea1dbdfe68bbcb46979e + languageName: node + linkType: hard + +"globals@npm:^13.19.0": + version: 13.20.0 + resolution: "globals@npm:13.20.0" + dependencies: + type-fest: ^0.20.2 + checksum: ad1ecf914bd051325faad281d02ea2c0b1df5d01bd94d368dcc5513340eac41d14b3c61af325768e3c7f8d44576e72780ec0b6f2d366121f8eec6e03c3a3b97a + languageName: node + linkType: hard + +"globalthis@npm:^1.0.3": + version: 1.0.3 + resolution: "globalthis@npm:1.0.3" + dependencies: + define-properties: ^1.1.3 + checksum: fbd7d760dc464c886d0196166d92e5ffb4c84d0730846d6621a39fbbc068aeeb9c8d1421ad330e94b7bca4bb4ea092f5f21f3d36077812af5d098b4dc006c998 + languageName: node + linkType: hard + +"globby@npm:^11.1.0": + version: 11.1.0 + resolution: "globby@npm:11.1.0" + dependencies: + array-union: ^2.1.0 + dir-glob: ^3.0.1 + fast-glob: ^3.2.9 + ignore: ^5.2.0 + merge2: ^1.4.1 + slash: ^3.0.0 + checksum: b4be8885e0cfa018fc783792942d53926c35c50b3aefd3fdcfb9d22c627639dc26bd2327a40a0b74b074100ce95bb7187bfeae2f236856aa3de183af7a02aea6 + languageName: node + linkType: hard + +"gopd@npm:^1.0.1": + version: 1.0.1 + resolution: "gopd@npm:1.0.1" + dependencies: + get-intrinsic: ^1.1.3 + checksum: a5ccfb8806e0917a94e0b3de2af2ea4979c1da920bc381667c260e00e7cafdbe844e2cb9c5bcfef4e5412e8bf73bab837285bc35c7ba73aaaf0134d4583393a6 + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.6": + version: 4.2.10 + resolution: "graceful-fs@npm:4.2.10" + checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da + languageName: node + linkType: hard + +"grapheme-splitter@npm:^1.0.4": + version: 1.0.4 + resolution: "grapheme-splitter@npm:1.0.4" + checksum: 0c22ec54dee1b05cd480f78cf14f732cb5b108edc073572c4ec205df4cd63f30f8db8025afc5debc8835a8ddeacf648a1c7992fe3dcd6ad38f9a476d84906620 + languageName: node + linkType: hard + +"has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": + version: 1.0.2 + resolution: "has-bigints@npm:1.0.2" + checksum: 390e31e7be7e5c6fe68b81babb73dfc35d413604d7ee5f56da101417027a4b4ce6a27e46eff97ad040c835b5d228676eae99a9b5c3bc0e23c8e81a49241ff45b + languageName: node + linkType: hard + +"has-flag@npm:^3.0.0": + version: 3.0.0 + resolution: "has-flag@npm:3.0.0" + checksum: 4a15638b454bf086c8148979aae044dd6e39d63904cd452d970374fa6a87623423da485dfb814e7be882e05c096a7ccf1ebd48e7e7501d0208d8384ff4dea73b + languageName: node + linkType: hard + +"has-flag@npm:^4.0.0": + version: 4.0.0 + resolution: "has-flag@npm:4.0.0" + checksum: 261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad + languageName: node + linkType: hard + +"has-property-descriptors@npm:^1.0.0": + version: 1.0.0 + resolution: "has-property-descriptors@npm:1.0.0" + dependencies: + get-intrinsic: ^1.1.1 + checksum: a6d3f0a266d0294d972e354782e872e2fe1b6495b321e6ef678c9b7a06a40408a6891817350c62e752adced73a94ac903c54734fee05bf65b1905ee1368194bb + languageName: node + linkType: hard + +"has-proto@npm:^1.0.1": + version: 1.0.1 + resolution: "has-proto@npm:1.0.1" + checksum: febc5b5b531de8022806ad7407935e2135f1cc9e64636c3916c6842bd7995994ca3b29871ecd7954bd35f9e2986c17b3b227880484d22259e2f8e6ce63fd383e + languageName: node + linkType: hard + +"has-symbols@npm:^1.0.2, has-symbols@npm:^1.0.3": + version: 1.0.3 + resolution: "has-symbols@npm:1.0.3" + checksum: a054c40c631c0d5741a8285010a0777ea0c068f99ed43e5d6eb12972da223f8af553a455132fdb0801bdcfa0e0f443c0c03a68d8555aa529b3144b446c3f2410 + languageName: node + linkType: hard + +"has-tostringtag@npm:^1.0.0": + version: 1.0.0 + resolution: "has-tostringtag@npm:1.0.0" + dependencies: + has-symbols: ^1.0.2 + checksum: cc12eb28cb6ae22369ebaad3a8ab0799ed61270991be88f208d508076a1e99abe4198c965935ce85ea90b60c94ddda73693b0920b58e7ead048b4a391b502c1c + languageName: node + linkType: hard + +"has-unicode@npm:^2.0.1": + version: 2.0.1 + resolution: "has-unicode@npm:2.0.1" + checksum: 1eab07a7436512db0be40a710b29b5dc21fa04880b7f63c9980b706683127e3c1b57cb80ea96d47991bdae2dfe479604f6a1ba410106ee1046a41d1bd0814400 + languageName: node + linkType: hard + +"has@npm:^1.0.3": + version: 1.0.3 + resolution: "has@npm:1.0.3" + dependencies: + function-bind: ^1.1.1 + checksum: b9ad53d53be4af90ce5d1c38331e712522417d017d5ef1ebd0507e07c2fbad8686fffb8e12ddecd4c39ca9b9b47431afbb975b8abf7f3c3b82c98e9aad052792 + languageName: node + linkType: hard + +"home-assistant-js-websocket@npm:^6.0.1": + version: 6.1.1 + resolution: "home-assistant-js-websocket@npm:6.1.1" + checksum: b46dd44ac1b393ecfdda152743fe44dcd6534463d1df631c448a71e71166c04d4fea2a0827dbb2e55c85fcd8d9ccdea4f168713bf18e832c7f688b9b2fdc8d72 + languageName: node + linkType: hard + +"home-assistant-js-websocket@npm:^8.0.0": + version: 8.0.1 + resolution: "home-assistant-js-websocket@npm:8.0.1" + checksum: e8b2204d58b2b1fbdf26ca1ad196fcc02ec5d18e6d867179f27246a9f2d4fe5f91de9dbbe7b82806c19dcb0af0e2b77fb48d393668b5c8c0844c201a16832023 + languageName: node + linkType: hard + +"http-cache-semantics@npm:^4.1.0": + version: 4.1.1 + resolution: "http-cache-semantics@npm:4.1.1" + checksum: 83ac0bc60b17a3a36f9953e7be55e5c8f41acc61b22583060e8dedc9dd5e3607c823a88d0926f9150e571f90946835c7fe150732801010845c72cd8bbff1a236 + languageName: node + linkType: hard + +"http-proxy-agent@npm:^5.0.0": + version: 5.0.0 + resolution: "http-proxy-agent@npm:5.0.0" + dependencies: + "@tootallnate/once": 2 + agent-base: 6 + debug: 4 + checksum: e2ee1ff1656a131953839b2a19cd1f3a52d97c25ba87bd2559af6ae87114abf60971e498021f9b73f9fd78aea8876d1fb0d4656aac8a03c6caa9fc175f22b786 + languageName: node + linkType: hard + +"https-proxy-agent@npm:^5.0.0": + version: 5.0.1 + resolution: "https-proxy-agent@npm:5.0.1" + dependencies: + agent-base: 6 + debug: 4 + checksum: 571fccdf38184f05943e12d37d6ce38197becdd69e58d03f43637f7fa1269cf303a7d228aa27e5b27bbd3af8f09fd938e1c91dcfefff2df7ba77c20ed8dfc765 + languageName: node + linkType: hard + +"humanize-ms@npm:^1.2.1": + version: 1.2.1 + resolution: "humanize-ms@npm:1.2.1" + dependencies: + ms: ^2.0.0 + checksum: 9c7a74a2827f9294c009266c82031030eae811ca87b0da3dceb8d6071b9bde22c9f3daef0469c3c533cc67a97d8a167cd9fc0389350e5f415f61a79b171ded16 + languageName: node + linkType: hard + +"iconv-lite@npm:^0.6.2": + version: 0.6.3 + resolution: "iconv-lite@npm:0.6.3" + dependencies: + safer-buffer: ">= 2.1.2 < 3.0.0" + checksum: 3f60d47a5c8fc3313317edfd29a00a692cc87a19cac0159e2ce711d0ebc9019064108323b5e493625e25594f11c6236647d8e256fbe7a58f4a3b33b89e6d30bf + languageName: node + linkType: hard + +"icss-utils@npm:^5.0.0, icss-utils@npm:^5.1.0": + version: 5.1.0 + resolution: "icss-utils@npm:5.1.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 5c324d283552b1269cfc13a503aaaa172a280f914e5b81544f3803bc6f06a3b585fb79f66f7c771a2c052db7982c18bf92d001e3b47282e3abbbb4c4cc488d68 + languageName: node + linkType: hard + +"ignore@npm:^5.2.0": + version: 5.2.4 + resolution: "ignore@npm:5.2.4" + checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef + languageName: node + linkType: hard + +"immutable@npm:^4.0.0": + version: 4.2.4 + resolution: "immutable@npm:4.2.4" + checksum: 3be84eded37b05e65cad57bfba630bc1bf170c498b7472144bc02d2650cc9baef79daf03574a9c2e41d195ebb55a1c12c9b312f41ee324b653927b24ad8bcaa7 + languageName: node + linkType: hard + +"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1": + version: 3.3.0 + resolution: "import-fresh@npm:3.3.0" + dependencies: + parent-module: ^1.0.0 + resolve-from: ^4.0.0 + checksum: 2cacfad06e652b1edc50be650f7ec3be08c5e5a6f6d12d035c440a42a8cc028e60a5b99ca08a77ab4d6b1346da7d971915828f33cdab730d3d42f08242d09baa + languageName: node + linkType: hard + +"imurmurhash@npm:^0.1.4": + version: 0.1.4 + resolution: "imurmurhash@npm:0.1.4" + checksum: 7cae75c8cd9a50f57dadd77482359f659eaebac0319dd9368bcd1714f55e65badd6929ca58569da2b6494ef13fdd5598cd700b1eba23f8b79c5f19d195a3ecf7 + languageName: node + linkType: hard + +"indent-string@npm:^4.0.0": + version: 4.0.0 + resolution: "indent-string@npm:4.0.0" + checksum: 824cfb9929d031dabf059bebfe08cf3137365e112019086ed3dcff6a0a7b698cb80cf67ccccde0e25b9e2d7527aa6cc1fed1ac490c752162496caba3e6699612 + languageName: node + linkType: hard + +"infer-owner@npm:^1.0.4": + version: 1.0.4 + resolution: "infer-owner@npm:1.0.4" + checksum: 181e732764e4a0611576466b4b87dac338972b839920b2a8cde43642e4ed6bd54dc1fb0b40874728f2a2df9a1b097b8ff83b56d5f8f8e3927f837fdcb47d8a89 + languageName: node + linkType: hard + +"inflight@npm:^1.0.4": + version: 1.0.6 + resolution: "inflight@npm:1.0.6" + dependencies: + once: ^1.3.0 + wrappy: 1 + checksum: f4f76aa072ce19fae87ce1ef7d221e709afb59d445e05d47fba710e85470923a75de35bfae47da6de1b18afc3ce83d70facf44cfb0aff89f0a3f45c0a0244dfd + languageName: node + linkType: hard + +"inherits@npm:2, inherits@npm:^2.0.3": + version: 2.0.4 + resolution: "inherits@npm:2.0.4" + checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 + languageName: node + linkType: hard + +"internal-slot@npm:^1.0.4": + version: 1.0.4 + resolution: "internal-slot@npm:1.0.4" + dependencies: + get-intrinsic: ^1.1.3 + has: ^1.0.3 + side-channel: ^1.0.4 + checksum: 8974588d06bab4f675573a3b52975370facf6486df51bc0567a982c7024fa29495f10b76c0d4dc742dd951d1b72024fdc1e31bb0bedf1678dc7aacacaf5a4f73 + languageName: node + linkType: hard + +"intl-messageformat@npm:^9.11.1": + version: 9.13.0 + resolution: "intl-messageformat@npm:9.13.0" + dependencies: + "@formatjs/ecma402-abstract": 1.11.4 + "@formatjs/fast-memoize": 1.2.1 + "@formatjs/icu-messageformat-parser": 2.1.0 + tslib: ^2.1.0 + checksum: effb840ae6e213adceab9951dbb2d18d1a354c82e35dbd213011daf23bcc79e2a18b6ef79157ae3b69c1e6d898fe66b54dd184ac180ee0646e26a70276e60038 + languageName: node + linkType: hard + +"ip@npm:^2.0.0": + version: 2.0.0 + resolution: "ip@npm:2.0.0" + checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349 + languageName: node + linkType: hard + +"is-array-buffer@npm:^3.0.1": + version: 3.0.1 + resolution: "is-array-buffer@npm:3.0.1" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + is-typed-array: ^1.1.10 + checksum: f26ab87448e698285daf707e52a533920449f7abf63714140ffab9d5571aa5a71ac2fa2677e8b793ad0d5d3e40078d4d2c8a0ab39c957e3cfc6513bb6c9dfdc9 + languageName: node + linkType: hard + +"is-arrayish@npm:^0.2.1": + version: 0.2.1 + resolution: "is-arrayish@npm:0.2.1" + checksum: eef4417e3c10e60e2c810b6084942b3ead455af16c4509959a27e490e7aee87cfb3f38e01bbde92220b528a0ee1a18d52b787e1458ee86174d8c7f0e58cd488f + languageName: node + linkType: hard + +"is-bigint@npm:^1.0.1": + version: 1.0.4 + resolution: "is-bigint@npm:1.0.4" + dependencies: + has-bigints: ^1.0.1 + checksum: c56edfe09b1154f8668e53ebe8252b6f185ee852a50f9b41e8d921cb2bed425652049fbe438723f6cb48a63ca1aa051e948e7e401e093477c99c84eba244f666 + languageName: node + linkType: hard + +"is-binary-path@npm:~2.1.0": + version: 2.1.0 + resolution: "is-binary-path@npm:2.1.0" + dependencies: + binary-extensions: ^2.0.0 + checksum: 84192eb88cff70d320426f35ecd63c3d6d495da9d805b19bc65b518984b7c0760280e57dbf119b7e9be6b161784a5a673ab2c6abe83abb5198a432232ad5b35c + languageName: node + linkType: hard + +"is-boolean-object@npm:^1.1.0": + version: 1.1.2 + resolution: "is-boolean-object@npm:1.1.2" + dependencies: + call-bind: ^1.0.2 + has-tostringtag: ^1.0.0 + checksum: c03b23dbaacadc18940defb12c1c0e3aaece7553ef58b162a0f6bba0c2a7e1551b59f365b91e00d2dbac0522392d576ef322628cb1d036a0fe51eb466db67222 + languageName: node + linkType: hard + +"is-builtin-module@npm:^3.1.0": + version: 3.2.1 + resolution: "is-builtin-module@npm:3.2.1" + dependencies: + builtin-modules: ^3.3.0 + checksum: e8f0ffc19a98240bda9c7ada84d846486365af88d14616e737d280d378695c8c448a621dcafc8332dbf0fcd0a17b0763b845400709963fa9151ddffece90ae88 + languageName: node + linkType: hard + +"is-callable@npm:^1.1.3, is-callable@npm:^1.1.4, is-callable@npm:^1.2.7": + version: 1.2.7 + resolution: "is-callable@npm:1.2.7" + checksum: 61fd57d03b0d984e2ed3720fb1c7a897827ea174bd44402878e059542ea8c4aeedee0ea0985998aa5cc2736b2fa6e271c08587addb5b3959ac52cf665173d1ac + languageName: node + linkType: hard + +"is-core-module@npm:^2.11.0, is-core-module@npm:^2.9.0": + version: 2.11.0 + resolution: "is-core-module@npm:2.11.0" + dependencies: + has: ^1.0.3 + checksum: f96fd490c6b48eb4f6d10ba815c6ef13f410b0ba6f7eb8577af51697de523e5f2cd9de1c441b51d27251bf0e4aebc936545e33a5d26d5d51f28d25698d4a8bab + languageName: node + linkType: hard + +"is-date-object@npm:^1.0.1": + version: 1.0.5 + resolution: "is-date-object@npm:1.0.5" + dependencies: + has-tostringtag: ^1.0.0 + checksum: baa9077cdf15eb7b58c79398604ca57379b2fc4cf9aa7a9b9e295278648f628c9b201400c01c5e0f7afae56507d741185730307cbe7cad3b9f90a77e5ee342fc + languageName: node + linkType: hard + +"is-docker@npm:^2.0.0, is-docker@npm:^2.1.1": + version: 2.2.1 + resolution: "is-docker@npm:2.2.1" + bin: + is-docker: cli.js + checksum: 3fef7ddbf0be25958e8991ad941901bf5922ab2753c46980b60b05c1bf9c9c2402d35e6dc32e4380b980ef5e1970a5d9d5e5aa2e02d77727c3b6b5e918474c56 + languageName: node + linkType: hard + +"is-extglob@npm:^2.1.1": + version: 2.1.1 + resolution: "is-extglob@npm:2.1.1" + checksum: df033653d06d0eb567461e58a7a8c9f940bd8c22274b94bf7671ab36df5719791aae15eef6d83bbb5e23283967f2f984b8914559d4449efda578c775c4be6f85 + languageName: node + linkType: hard + +"is-fullwidth-code-point@npm:^3.0.0": + version: 3.0.0 + resolution: "is-fullwidth-code-point@npm:3.0.0" + checksum: 44a30c29457c7fb8f00297bce733f0a64cd22eca270f83e58c105e0d015e45c019491a4ab2faef91ab51d4738c670daff901c799f6a700e27f7314029e99e348 + languageName: node + linkType: hard + +"is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": + version: 4.0.3 + resolution: "is-glob@npm:4.0.3" + dependencies: + is-extglob: ^2.1.1 + checksum: d381c1319fcb69d341cc6e6c7cd588e17cd94722d9a32dbd60660b993c4fb7d0f19438674e68dfec686d09b7c73139c9166b47597f846af387450224a8101ab4 + languageName: node + linkType: hard + +"is-lambda@npm:^1.0.1": + version: 1.0.1 + resolution: "is-lambda@npm:1.0.1" + checksum: 93a32f01940220532e5948538699ad610d5924ac86093fcee83022252b363eb0cc99ba53ab084a04e4fb62bf7b5731f55496257a4c38adf87af9c4d352c71c35 + languageName: node + linkType: hard + +"is-module@npm:^1.0.0": + version: 1.0.0 + resolution: "is-module@npm:1.0.0" + checksum: 8cd5390730c7976fb4e8546dd0b38865ee6f7bacfa08dfbb2cc07219606755f0b01709d9361e01f13009bbbd8099fa2927a8ed665118a6105d66e40f1b838c3f + languageName: node + linkType: hard + +"is-negative-zero@npm:^2.0.2": + version: 2.0.2 + resolution: "is-negative-zero@npm:2.0.2" + checksum: f3232194c47a549da60c3d509c9a09be442507616b69454716692e37ae9f37c4dea264fb208ad0c9f3efd15a796a46b79df07c7e53c6227c32170608b809149a + languageName: node + linkType: hard + +"is-number-object@npm:^1.0.4": + version: 1.0.7 + resolution: "is-number-object@npm:1.0.7" + dependencies: + has-tostringtag: ^1.0.0 + checksum: d1e8d01bb0a7134c74649c4e62da0c6118a0bfc6771ea3c560914d52a627873e6920dd0fd0ebc0e12ad2ff4687eac4c308f7e80320b973b2c8a2c8f97a7524f7 + languageName: node + linkType: hard + +"is-number@npm:^7.0.0": + version: 7.0.0 + resolution: "is-number@npm:7.0.0" + checksum: 456ac6f8e0f3111ed34668a624e45315201dff921e5ac181f8ec24923b99e9f32ca1a194912dc79d539c97d33dba17dc635202ff0b2cf98326f608323276d27a + languageName: node + linkType: hard + +"is-path-inside@npm:^3.0.3": + version: 3.0.3 + resolution: "is-path-inside@npm:3.0.3" + checksum: abd50f06186a052b349c15e55b182326f1936c89a78bf6c8f2b707412517c097ce04bc49a0ca221787bc44e1049f51f09a2ffb63d22899051988d3a618ba13e9 + languageName: node + linkType: hard + +"is-reference@npm:^1.2.1": + version: 1.2.1 + resolution: "is-reference@npm:1.2.1" + dependencies: + "@types/estree": "*" + checksum: e7b48149f8abda2c10849ea51965904d6a714193d68942ad74e30522231045acf06cbfae5a4be2702fede5d232e61bf50b3183acdc056e6e3afe07fcf4f4b2bc + languageName: node + linkType: hard + +"is-regex@npm:^1.1.4": + version: 1.1.4 + resolution: "is-regex@npm:1.1.4" + dependencies: + call-bind: ^1.0.2 + has-tostringtag: ^1.0.0 + checksum: 362399b33535bc8f386d96c45c9feb04cf7f8b41c182f54174c1a45c9abbbe5e31290bbad09a458583ff6bf3b2048672cdb1881b13289569a7c548370856a652 + languageName: node + linkType: hard + +"is-shared-array-buffer@npm:^1.0.2": + version: 1.0.2 + resolution: "is-shared-array-buffer@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + checksum: 9508929cf14fdc1afc9d61d723c6e8d34f5e117f0bffda4d97e7a5d88c3a8681f633a74f8e3ad1fe92d5113f9b921dc5ca44356492079612f9a247efbce7032a + languageName: node + linkType: hard + +"is-string@npm:^1.0.5, is-string@npm:^1.0.7": + version: 1.0.7 + resolution: "is-string@npm:1.0.7" + dependencies: + has-tostringtag: ^1.0.0 + checksum: 323b3d04622f78d45077cf89aab783b2f49d24dc641aa89b5ad1a72114cfeff2585efc8c12ef42466dff32bde93d839ad321b26884cf75e5a7892a938b089989 + languageName: node + linkType: hard + +"is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": + version: 1.0.4 + resolution: "is-symbol@npm:1.0.4" + dependencies: + has-symbols: ^1.0.2 + checksum: 92805812ef590738d9de49d677cd17dfd486794773fb6fa0032d16452af46e9b91bb43ffe82c983570f015b37136f4b53b28b8523bfb10b0ece7a66c31a54510 + languageName: node + linkType: hard + +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.9": + version: 1.1.10 + resolution: "is-typed-array@npm:1.1.10" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-tostringtag: ^1.0.0 + checksum: aac6ecb59d4c56a1cdeb69b1f129154ef462bbffe434cb8a8235ca89b42f258b7ae94073c41b3cb7bce37f6a1733ad4499f07882d5d5093a7ba84dfc4ebb8017 + languageName: node + linkType: hard + +"is-weakref@npm:^1.0.2": + version: 1.0.2 + resolution: "is-weakref@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + checksum: 95bd9a57cdcb58c63b1c401c60a474b0f45b94719c30f548c891860f051bc2231575c290a6b420c6bc6e7ed99459d424c652bd5bf9a1d5259505dc35b4bf83de + languageName: node + linkType: hard + +"is-wsl@npm:^2.2.0": + version: 2.2.0 + resolution: "is-wsl@npm:2.2.0" + dependencies: + is-docker: ^2.0.0 + checksum: 20849846ae414997d290b75e16868e5261e86ff5047f104027026fd61d8b5a9b0b3ade16239f35e1a067b3c7cc02f70183cb661010ed16f4b6c7c93dad1b19d8 + languageName: node + linkType: hard + +"isexe@npm:^2.0.0": + version: 2.0.0 + resolution: "isexe@npm:2.0.0" + checksum: 26bf6c5480dda5161c820c5b5c751ae1e766c587b1f951ea3fcfc973bafb7831ae5b54a31a69bd670220e42e99ec154475025a468eae58ea262f813fdc8d1c62 + languageName: node + linkType: hard + +"jest-worker@npm:^26.2.1": + version: 26.6.2 + resolution: "jest-worker@npm:26.6.2" + dependencies: + "@types/node": "*" + merge-stream: ^2.0.0 + supports-color: ^7.0.0 + checksum: f9afa3b88e3f12027901e4964ba3ff048285b5783b5225cab28fac25b4058cea8ad54001e9a1577ee2bed125fac3ccf5c80dc507b120300cc1bbcb368796533e + languageName: node + linkType: hard + +"js-sdsl@npm:^4.1.4": + version: 4.3.0 + resolution: "js-sdsl@npm:4.3.0" + checksum: ce908257cf6909e213af580af3a691a736f5ee8b16315454768f917a682a4ea0c11bde1b241bbfaecedc0eb67b72101b2c2df2ffaed32aed5d539fca816f054e + languageName: node + linkType: hard + +"js-tokens@npm:^4.0.0": + version: 4.0.0 + resolution: "js-tokens@npm:4.0.0" + checksum: 8a95213a5a77deb6cbe94d86340e8d9ace2b93bc367790b260101d2f36a2eaf4e4e22d9fa9cf459b38af3a32fb4190e638024cf82ec95ef708680e405ea7cc78 + languageName: node + linkType: hard + +"js-yaml@npm:^4.1.0": + version: 4.1.0 + resolution: "js-yaml@npm:4.1.0" + dependencies: + argparse: ^2.0.1 + bin: + js-yaml: bin/js-yaml.js + checksum: c7830dfd456c3ef2c6e355cc5a92e6700ceafa1d14bba54497b34a99f0376cecbb3e9ac14d3e5849b426d5a5140709a66237a8c991c675431271c4ce5504151a + languageName: node + linkType: hard + +"jsesc@npm:^2.5.1": + version: 2.5.2 + resolution: "jsesc@npm:2.5.2" + bin: + jsesc: bin/jsesc + checksum: 4dc190771129e12023f729ce20e1e0bfceac84d73a85bc3119f7f938843fe25a4aeccb54b6494dce26fcf263d815f5f31acdefac7cc9329efb8422a4f4d9fa9d + languageName: node + linkType: hard + +"json-parse-even-better-errors@npm:^2.3.0": + version: 2.3.1 + resolution: "json-parse-even-better-errors@npm:2.3.1" + checksum: 798ed4cf3354a2d9ccd78e86d2169515a0097a5c133337807cdf7f1fc32e1391d207ccfc276518cc1d7d8d4db93288b8a50ba4293d212ad1336e52a8ec0a941f + languageName: node + linkType: hard + +"json-schema-traverse@npm:^0.4.1": + version: 0.4.1 + resolution: "json-schema-traverse@npm:0.4.1" + checksum: 7486074d3ba247769fda17d5181b345c9fb7d12e0da98b22d1d71a5db9698d8b4bd900a3ec1a4ffdd60846fc2556274a5c894d0c48795f14cb03aeae7b55260b + languageName: node + linkType: hard + +"json-stable-stringify-without-jsonify@npm:^1.0.1": + version: 1.0.1 + resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" + checksum: cff44156ddce9c67c44386ad5cddf91925fe06b1d217f2da9c4910d01f358c6e3989c4d5a02683c7a5667f9727ff05831f7aa8ae66c8ff691c556f0884d49215 + languageName: node + linkType: hard + +"json5@npm:^1.0.1": + version: 1.0.2 + resolution: "json5@npm:1.0.2" + dependencies: + minimist: ^1.2.0 + bin: + json5: lib/cli.js + checksum: 866458a8c58a95a49bef3adba929c625e82532bcff1fe93f01d29cb02cac7c3fe1f4b79951b7792c2da9de0b32871a8401a6e3c5b36778ad852bf5b8a61165d7 + languageName: node + linkType: hard + +"json5@npm:^2.1.3, json5@npm:^2.2.2": + version: 2.2.3 + resolution: "json5@npm:2.2.3" + bin: + json5: lib/cli.js + checksum: 2a7436a93393830bce797d4626275152e37e877b265e94ca69c99e3d20c2b9dab021279146a39cdb700e71b2dd32a4cebd1514cd57cee102b1af906ce5040349 + languageName: node + linkType: hard + +"jsonfile@npm:^6.0.1": + version: 6.1.0 + resolution: "jsonfile@npm:6.1.0" + dependencies: + graceful-fs: ^4.1.6 + universalify: ^2.0.0 + dependenciesMeta: + graceful-fs: + optional: true + checksum: 7af3b8e1ac8fe7f1eccc6263c6ca14e1966fcbc74b618d3c78a0a2075579487547b94f72b7a1114e844a1e15bb00d440e5d1720bfc4612d790a6f285d5ea8354 + languageName: node + linkType: hard + +"keycharm@npm:^0.4.0": + version: 0.4.0 + resolution: "keycharm@npm:0.4.0" + checksum: b6a601e04d7354af4f94facb4174bc46d0788b30b7d19ab8af37dacffb43f6f7ae4ab1f2a2a635de28b4da107b2f1b7219efc789887f7897ed53f209c97cce85 + languageName: node + linkType: hard + +"levn@npm:^0.4.1": + version: 0.4.1 + resolution: "levn@npm:0.4.1" + dependencies: + prelude-ls: ^1.2.1 + type-check: ~0.4.0 + checksum: 12c5021c859bd0f5248561bf139121f0358285ec545ebf48bb3d346820d5c61a4309535c7f387ed7d84361cf821e124ce346c6b7cef8ee09a67c1473b46d0fc4 + languageName: node + linkType: hard + +"lilconfig@npm:^2.0.3": + version: 2.0.6 + resolution: "lilconfig@npm:2.0.6" + checksum: 40a3cd72f103b1be5975f2ac1850810b61d4053e20ab09be8d3aeddfe042187e1ba70b4651a7e70f95efa1642e7dc8b2ae395b317b7d7753b241b43cef7c0f7d + languageName: node + linkType: hard + +"lines-and-columns@npm:^1.1.6": + version: 1.2.4 + resolution: "lines-and-columns@npm:1.2.4" + checksum: 0c37f9f7fa212b38912b7145e1cd16a5f3cd34d782441c3e6ca653485d326f58b3caccda66efce1c5812bde4961bbde3374fae4b0d11bf1226152337f3894aa5 + languageName: node + linkType: hard + +"lit-element@npm:^3.2.0": + version: 3.2.2 + resolution: "lit-element@npm:3.2.2" + dependencies: + "@lit/reactive-element": ^1.3.0 + lit-html: ^2.2.0 + checksum: e1df57c02ad5ae4c42b49a5066b3b623bccf7fa6610bbc3f65bc22cbbe1546ecd6a7f717b1f01863929262f416c4b8b7a39ff166114215f93cc22f4e5b3825c3 + languageName: node + linkType: hard + +"lit-html@npm:^2.2.0, lit-html@npm:^2.6.0": + version: 2.6.1 + resolution: "lit-html@npm:2.6.1" + dependencies: + "@types/trusted-types": ^2.0.2 + checksum: 32291eb5bcaa7fcc1d433522a9a62c3c3d240e49bb8c57ccea5c10014823d94f62f17e2d5361781cb6ee0b3c9078ef839de6164d5374371d650471904515e48e + languageName: node + linkType: hard + +"lit@npm:^2.0.0, lit@npm:^2.1.1, lit@npm:^2.3.1": + version: 2.6.1 + resolution: "lit@npm:2.6.1" + dependencies: + "@lit/reactive-element": ^1.6.0 + lit-element: ^3.2.0 + lit-html: ^2.6.0 + checksum: 74f2813152a48f195784f01b6b4ff7e4e7d8fa90ac272b725d1953fa3e1d6c9dca60b77da435f34144fcdd38705480889a8ea986835676932a8559c897cea517 + languageName: node + linkType: hard + +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: ^4.1.0 + checksum: 83e51725e67517287d73e1ded92b28602e3ae5580b301fe54bfb76c0c723e3f285b19252e375712316774cf52006cb236aed5704692c32db0d5d089b69696e30 + languageName: node + linkType: hard + +"locate-path@npm:^6.0.0": + version: 6.0.0 + resolution: "locate-path@npm:6.0.0" + dependencies: + p-locate: ^5.0.0 + checksum: 72eb661788a0368c099a184c59d2fee760b3831c9c1c33955e8a19ae4a21b4116e53fa736dc086cdeb9fce9f7cc508f2f92d2d3aae516f133e16a2bb59a39f5a + languageName: node + linkType: hard + +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 + languageName: node + linkType: hard + +"lodash.memoize@npm:^4.1.2": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 9ff3942feeccffa4f1fafa88d32f0d24fdc62fd15ded5a74a5f950ff5f0c6f61916157246744c620173dddf38d37095a92327d5fd3861e2063e736a5c207d089 + languageName: node + linkType: hard + +"lodash.merge@npm:^4.6.2": + version: 4.6.2 + resolution: "lodash.merge@npm:4.6.2" + checksum: ad580b4bdbb7ca1f7abf7e1bce63a9a0b98e370cf40194b03380a46b4ed799c9573029599caebc1b14e3f24b111aef72b96674a56cfa105e0f5ac70546cdc005 + languageName: node + linkType: hard + +"lodash.uniq@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.uniq@npm:4.5.0" + checksum: a4779b57a8d0f3c441af13d9afe7ecff22dd1b8ce1129849f71d9bbc8e8ee4e46dfb4b7c28f7ad3d67481edd6e51126e4e2a6ee276e25906d10f7140187c392d + languageName: node + linkType: hard + +"lodash@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash@npm:4.17.21" + checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 + languageName: node + linkType: hard + +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: ^3.0.2 + checksum: c154ae1cbb0c2206d1501a0e94df349653c92c8cbb25236d7e85190bcaf4567a03ac6eb43166fabfa36fd35623694da7233e88d9601fbf411a9a481d85dbd2cb + languageName: node + linkType: hard + +"lru-cache@npm:^6.0.0": + version: 6.0.0 + resolution: "lru-cache@npm:6.0.0" + dependencies: + yallist: ^4.0.0 + checksum: f97f499f898f23e4585742138a22f22526254fdba6d75d41a1c2526b3b6cc5747ef59c5612ba7375f42aca4f8461950e925ba08c991ead0651b4918b7c978297 + languageName: node + linkType: hard + +"lru-cache@npm:^7.7.1": + version: 7.14.1 + resolution: "lru-cache@npm:7.14.1" + checksum: d72c6713c6a6d86836a7a6523b3f1ac6764768cca47ec99341c3e76db06aacd4764620e5e2cda719a36848785a52a70e531822dc2b33fb071fa709683746c104 + languageName: node + linkType: hard + +"magic-string@npm:^0.25.7": + version: 0.25.9 + resolution: "magic-string@npm:0.25.9" + dependencies: + sourcemap-codec: ^1.4.8 + checksum: 9a0e55a15c7303fc360f9572a71cffba1f61451bc92c5602b1206c9d17f492403bf96f946dfce7483e66822d6b74607262e24392e87b0ac27b786e69a40e9b1a + languageName: node + linkType: hard + +"make-dir@npm:^3.0.2": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: ^6.0.0 + checksum: 484200020ab5a1fdf12f393fe5f385fc8e4378824c940fba1729dcd198ae4ff24867bc7a5646331e50cead8abff5d9270c456314386e629acec6dff4b8016b78 + languageName: node + linkType: hard + +"make-fetch-happen@npm:^10.0.3": + version: 10.2.1 + resolution: "make-fetch-happen@npm:10.2.1" + dependencies: + agentkeepalive: ^4.2.1 + cacache: ^16.1.0 + http-cache-semantics: ^4.1.0 + http-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.0 + is-lambda: ^1.0.1 + lru-cache: ^7.7.1 + minipass: ^3.1.6 + minipass-collect: ^1.0.2 + minipass-fetch: ^2.0.3 + minipass-flush: ^1.0.5 + minipass-pipeline: ^1.2.4 + negotiator: ^0.6.3 + promise-retry: ^2.0.1 + socks-proxy-agent: ^7.0.0 + ssri: ^9.0.0 + checksum: 2332eb9a8ec96f1ffeeea56ccefabcb4193693597b132cd110734d50f2928842e22b84cfa1508e921b8385cdfd06dda9ad68645fed62b50fff629a580f5fb72c + languageName: node + linkType: hard + +"mdn-data@npm:2.0.14": + version: 2.0.14 + resolution: "mdn-data@npm:2.0.14" + checksum: 9d0128ed425a89f4cba8f787dca27ad9408b5cb1b220af2d938e2a0629d17d879a34d2cb19318bdb26c3f14c77dd5dfbae67211f5caaf07b61b1f2c5c8c7dc16 + languageName: node + linkType: hard + +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 6fa4dcc8d86629705cea944a4b88ef4cb0e07656ebf223fa287443256414283dd25d91c1cd84c77987f2aec5927af1a9db6085757cb43d90eb170ebf4b47f4f4 + languageName: node + linkType: hard + +"merge2@npm:^1.3.0, merge2@npm:^1.4.1": + version: 1.4.1 + resolution: "merge2@npm:1.4.1" + checksum: 7268db63ed5169466540b6fb947aec313200bcf6d40c5ab722c22e242f651994619bcd85601602972d3c85bd2cc45a358a4c61937e9f11a061919a1da569b0c2 + languageName: node + linkType: hard + +"micromatch@npm:^4.0.4": + version: 4.0.5 + resolution: "micromatch@npm:4.0.5" + dependencies: + braces: ^3.0.2 + picomatch: ^2.3.1 + checksum: 02a17b671c06e8fefeeb6ef996119c1e597c942e632a21ef589154f23898c9c6a9858526246abb14f8bca6e77734aa9dcf65476fca47cedfb80d9577d52843fc + languageName: node + linkType: hard + +"mime-db@npm:1.52.0": + version: 1.52.0 + resolution: "mime-db@npm:1.52.0" + checksum: 0d99a03585f8b39d68182803b12ac601d9c01abfa28ec56204fa330bc9f3d1c5e14beb049bafadb3dbdf646dfb94b87e24d4ec7b31b7279ef906a8ea9b6a513f + languageName: node + linkType: hard + +"mime-types@npm:^2.1.34": + version: 2.1.35 + resolution: "mime-types@npm:2.1.35" + dependencies: + mime-db: 1.52.0 + checksum: 89a5b7f1def9f3af5dad6496c5ed50191ae4331cc5389d7c521c8ad28d5fdad2d06fd81baf38fed813dc4e46bb55c8145bb0ff406330818c9cf712fb2e9b3836 + languageName: node + linkType: hard + +"mime@npm:>=2.4.6": + version: 3.0.0 + resolution: "mime@npm:3.0.0" + bin: + mime: cli.js + checksum: f43f9b7bfa64534e6b05bd6062961681aeb406a5b53673b53b683f27fcc4e739989941836a355eef831f4478923651ecc739f4a5f6e20a76487b432bfd4db928 + languageName: node + linkType: hard + +"mini-svg-data-uri@npm:^1.2.3": + version: 1.4.4 + resolution: "mini-svg-data-uri@npm:1.4.4" + bin: + mini-svg-data-uri: cli.js + checksum: 997f1fbd8d59a70f03761e18626d335197a3479cb9d1ff75678e4b64b864d32a0b8fc18115eabde035e5299b8b4a354a78e57dd6ac10f9d604162a6170898d09 + languageName: node + linkType: hard + +"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": + version: 3.1.2 + resolution: "minimatch@npm:3.1.2" + dependencies: + brace-expansion: ^1.1.7 + checksum: c154e566406683e7bcb746e000b84d74465b3a832c45d59912b9b55cd50dee66e5c4b1e5566dba26154040e51672f9aa450a9aef0c97cfc7336b78b7afb9540a + languageName: node + linkType: hard + +"minimatch@npm:^5.0.1": + version: 5.1.6 + resolution: "minimatch@npm:5.1.6" + dependencies: + brace-expansion: ^2.0.1 + checksum: 7564208ef81d7065a370f788d337cd80a689e981042cb9a1d0e6580b6c6a8c9279eba80010516e258835a988363f99f54a6f711a315089b8b42694f5da9d0d77 + languageName: node + linkType: hard + +"minimist@npm:^1.2.0, minimist@npm:^1.2.6": + version: 1.2.7 + resolution: "minimist@npm:1.2.7" + checksum: 7346574a1038ca23c32e02252f603801f09384dd1d78b69a943a4e8c2c28730b80e96193882d3d3b22a063445f460e48316b29b8a25addca2d7e5e8f75478bec + languageName: node + linkType: hard + +"minipass-collect@npm:^1.0.2": + version: 1.0.2 + resolution: "minipass-collect@npm:1.0.2" + dependencies: + minipass: ^3.0.0 + checksum: 14df761028f3e47293aee72888f2657695ec66bd7d09cae7ad558da30415fdc4752bbfee66287dcc6fd5e6a2fa3466d6c484dc1cbd986525d9393b9523d97f10 + languageName: node + linkType: hard + +"minipass-fetch@npm:^2.0.3": + version: 2.1.2 + resolution: "minipass-fetch@npm:2.1.2" + dependencies: + encoding: ^0.1.13 + minipass: ^3.1.6 + minipass-sized: ^1.0.3 + minizlib: ^2.1.2 + dependenciesMeta: + encoding: + optional: true + checksum: 3f216be79164e915fc91210cea1850e488793c740534985da017a4cbc7a5ff50506956d0f73bb0cb60e4fe91be08b6b61ef35101706d3ef5da2c8709b5f08f91 + languageName: node + linkType: hard + +"minipass-flush@npm:^1.0.5": + version: 1.0.5 + resolution: "minipass-flush@npm:1.0.5" + dependencies: + minipass: ^3.0.0 + checksum: 56269a0b22bad756a08a94b1ffc36b7c9c5de0735a4dd1ab2b06c066d795cfd1f0ac44a0fcae13eece5589b908ecddc867f04c745c7009be0b566421ea0944cf + languageName: node + linkType: hard + +"minipass-pipeline@npm:^1.2.4": + version: 1.2.4 + resolution: "minipass-pipeline@npm:1.2.4" + dependencies: + minipass: ^3.0.0 + checksum: b14240dac0d29823c3d5911c286069e36d0b81173d7bdf07a7e4a91ecdef92cdff4baaf31ea3746f1c61e0957f652e641223970870e2353593f382112257971b + languageName: node + linkType: hard + +"minipass-sized@npm:^1.0.3": + version: 1.0.3 + resolution: "minipass-sized@npm:1.0.3" + dependencies: + minipass: ^3.0.0 + checksum: 79076749fcacf21b5d16dd596d32c3b6bf4d6e62abb43868fac21674078505c8b15eaca4e47ed844985a4514854f917d78f588fcd029693709417d8f98b2bd60 + languageName: node + linkType: hard + +"minipass@npm:^3.0.0, minipass@npm:^3.1.1, minipass@npm:^3.1.6": + version: 3.3.6 + resolution: "minipass@npm:3.3.6" + dependencies: + yallist: ^4.0.0 + checksum: a30d083c8054cee83cdcdc97f97e4641a3f58ae743970457b1489ce38ee1167b3aaf7d815cd39ec7a99b9c40397fd4f686e83750e73e652b21cb516f6d845e48 + languageName: node + linkType: hard + +"minipass@npm:^4.0.0": + version: 4.0.2 + resolution: "minipass@npm:4.0.2" + checksum: 2e4f4caaf85a45c01c6042adc43b5111a6113f62e300acf1db3b193ac3ec6e955bc893d6d8a08635598771a55a0f60487dcac59a1ac39557eb524295d4778c9e + languageName: node + linkType: hard + +"minizlib@npm:^2.1.1, minizlib@npm:^2.1.2": + version: 2.1.2 + resolution: "minizlib@npm:2.1.2" + dependencies: + minipass: ^3.0.0 + yallist: ^4.0.0 + checksum: f1fdeac0b07cf8f30fcf12f4b586795b97be856edea22b5e9072707be51fc95d41487faec3f265b42973a304fe3a64acd91a44a3826a963e37b37bafde0212c3 + languageName: node + linkType: hard + +"mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": + version: 1.0.4 + resolution: "mkdirp@npm:1.0.4" + bin: + mkdirp: bin/cmd.js + checksum: a96865108c6c3b1b8e1d5e9f11843de1e077e57737602de1b82030815f311be11f96f09cce59bd5b903d0b29834733e5313f9301e3ed6d6f6fba2eae0df4298f + languageName: node + linkType: hard + +"moment@npm:^2.29.4": + version: 2.29.4 + resolution: "moment@npm:2.29.4" + checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e + languageName: node + linkType: hard + +"ms@npm:2.1.2": + version: 2.1.2 + resolution: "ms@npm:2.1.2" + checksum: 673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f + languageName: node + linkType: hard + +"ms@npm:^2.0.0, ms@npm:^2.1.1": + version: 2.1.3 + resolution: "ms@npm:2.1.3" + checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d + languageName: node + linkType: hard + +"nanoid@npm:^3.3.4": + version: 3.3.4 + resolution: "nanoid@npm:3.3.4" + bin: + nanoid: bin/nanoid.cjs + checksum: 2fddd6dee994b7676f008d3ffa4ab16035a754f4bb586c61df5a22cf8c8c94017aadd360368f47d653829e0569a92b129979152ff97af23a558331e47e37cd9c + languageName: node + linkType: hard + +"natural-compare-lite@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare-lite@npm:1.4.0" + checksum: 5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225 + languageName: node + linkType: hard + +"natural-compare@npm:^1.4.0": + version: 1.4.0 + resolution: "natural-compare@npm:1.4.0" + checksum: 23ad088b08f898fc9b53011d7bb78ec48e79de7627e01ab5518e806033861bef68d5b0cd0e2205c2f36690ac9571ff6bcb05eb777ced2eeda8d4ac5b44592c3d + languageName: node + linkType: hard + +"negotiator@npm:^0.6.3": + version: 0.6.3 + resolution: "negotiator@npm:0.6.3" + checksum: b8ffeb1e262eff7968fc90a2b6767b04cfd9842582a9d0ece0af7049537266e7b2506dfb1d107a32f06dd849ab2aea834d5830f7f4d0e5cb7d36e1ae55d021d9 + languageName: node + linkType: hard + +"node-gyp@npm:latest": + version: 9.3.1 + resolution: "node-gyp@npm:9.3.1" + dependencies: + env-paths: ^2.2.0 + glob: ^7.1.4 + graceful-fs: ^4.2.6 + make-fetch-happen: ^10.0.3 + nopt: ^6.0.0 + npmlog: ^6.0.0 + rimraf: ^3.0.2 + semver: ^7.3.5 + tar: ^6.1.2 + which: ^2.0.2 + bin: + node-gyp: bin/node-gyp.js + checksum: b860e9976fa645ca0789c69e25387401b4396b93c8375489b5151a6c55cf2640a3b6183c212b38625ef7c508994930b72198338e3d09b9d7ade5acc4aaf51ea7 + languageName: node + linkType: hard + +"node-releases@npm:^2.0.8": + version: 2.0.10 + resolution: "node-releases@npm:2.0.10" + checksum: d784ecde25696a15d449c4433077f5cce620ed30a1656c4abf31282bfc691a70d9618bae6868d247a67914d1be5cc4fde22f65a05f4398cdfb92e0fc83cadfbc + languageName: node + linkType: hard + +"nopt@npm:^6.0.0": + version: 6.0.0 + resolution: "nopt@npm:6.0.0" + dependencies: + abbrev: ^1.0.0 + bin: + nopt: bin/nopt.js + checksum: 82149371f8be0c4b9ec2f863cc6509a7fd0fa729929c009f3a58e4eb0c9e4cae9920e8f1f8eb46e7d032fec8fb01bede7f0f41a67eb3553b7b8e14fa53de1dac + languageName: node + linkType: hard + +"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": + version: 3.0.0 + resolution: "normalize-path@npm:3.0.0" + checksum: 88eeb4da891e10b1318c4b2476b6e2ecbeb5ff97d946815ffea7794c31a89017c70d7f34b3c2ebf23ef4e9fc9fb99f7dffe36da22011b5b5c6ffa34f4873ec20 + languageName: node + linkType: hard + +"normalize-url@npm:^6.0.1": + version: 6.1.0 + resolution: "normalize-url@npm:6.1.0" + checksum: 4a4944631173e7d521d6b80e4c85ccaeceb2870f315584fa30121f505a6dfd86439c5e3fdd8cd9e0e291290c41d0c3599f0cb12ab356722ed242584c30348e50 + languageName: node + linkType: hard + +"npmlog@npm:^6.0.0": + version: 6.0.2 + resolution: "npmlog@npm:6.0.2" + dependencies: + are-we-there-yet: ^3.0.0 + console-control-strings: ^1.1.0 + gauge: ^4.0.3 + set-blocking: ^2.0.0 + checksum: ae238cd264a1c3f22091cdd9e2b106f684297d3c184f1146984ecbe18aaa86343953f26b9520dedd1b1372bc0316905b736c1932d778dbeb1fcf5a1001390e2a + languageName: node + linkType: hard + +"nth-check@npm:^2.0.1": + version: 2.1.1 + resolution: "nth-check@npm:2.1.1" + dependencies: + boolbase: ^1.0.0 + checksum: 5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3 + languageName: node + linkType: hard + +"object-inspect@npm:^1.12.2, object-inspect@npm:^1.9.0": + version: 1.12.3 + resolution: "object-inspect@npm:1.12.3" + checksum: dabfd824d97a5f407e6d5d24810d888859f6be394d8b733a77442b277e0808860555176719c5905e765e3743a7cada6b8b0a3b85e5331c530fd418cc8ae991db + languageName: node + linkType: hard + +"object-keys@npm:^1.1.1": + version: 1.1.1 + resolution: "object-keys@npm:1.1.1" + checksum: b363c5e7644b1e1b04aa507e88dcb8e3a2f52b6ffd0ea801e4c7a62d5aa559affe21c55a07fd4b1fd55fc03a33c610d73426664b20032405d7b92a1414c34d6a + languageName: node + linkType: hard + +"object.assign@npm:^4.1.2, object.assign@npm:^4.1.4": + version: 4.1.4 + resolution: "object.assign@npm:4.1.4" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + has-symbols: ^1.0.3 + object-keys: ^1.1.1 + checksum: 76cab513a5999acbfe0ff355f15a6a125e71805fcf53de4e9d4e082e1989bdb81d1e329291e1e4e0ae7719f0e4ef80e88fb2d367ae60500d79d25a6224ac8864 + languageName: node + linkType: hard + +"object.entries@npm:^1.1.5": + version: 1.1.6 + resolution: "object.entries@npm:1.1.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 0f8c47517e6a9a980241eafe3b73de11e59511883173c2b93d67424a008e47e11b77c80e431ad1d8a806f6108b225a1cab9223e53e555776c612a24297117d28 + languageName: node + linkType: hard + +"object.values@npm:^1.1.6": + version: 1.1.6 + resolution: "object.values@npm:1.1.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: f6fff9fd817c24cfd8107f50fb33061d81cd11bacc4e3dbb3852e9ff7692fde4dbce823d4333ea27cd9637ef1b6690df5fbb61f1ed314fa2959598dc3ae23d8e + languageName: node + linkType: hard + +"once@npm:^1.3.0": + version: 1.4.0 + resolution: "once@npm:1.4.0" + dependencies: + wrappy: 1 + checksum: cd0a88501333edd640d95f0d2700fbde6bff20b3d4d9bdc521bdd31af0656b5706570d6c6afe532045a20bb8dc0849f8332d6f2a416e0ba6d3d3b98806c7db68 + languageName: node + linkType: hard + +"open@npm:^8.4.0": + version: 8.4.0 + resolution: "open@npm:8.4.0" + dependencies: + define-lazy-prop: ^2.0.0 + is-docker: ^2.1.1 + is-wsl: ^2.2.0 + checksum: e9545bec64cdbf30a0c35c1bdc310344adf8428a117f7d8df3c0af0a0a24c513b304916a6d9b11db0190ff7225c2d578885080b761ed46a3d5f6f1eebb98b63c + languageName: node + linkType: hard + +"opener@npm:1": + version: 1.5.2 + resolution: "opener@npm:1.5.2" + bin: + opener: bin/opener-bin.js + checksum: 33b620c0d53d5b883f2abc6687dd1c5fd394d270dbe33a6356f2d71e0a2ec85b100d5bac94694198ccf5c30d592da863b2292c5539009c715a9c80c697b4f6cc + languageName: node + linkType: hard + +"optionator@npm:^0.9.1": + version: 0.9.1 + resolution: "optionator@npm:0.9.1" + dependencies: + deep-is: ^0.1.3 + fast-levenshtein: ^2.0.6 + levn: ^0.4.1 + prelude-ls: ^1.2.1 + type-check: ^0.4.0 + word-wrap: ^1.2.3 + checksum: dbc6fa065604b24ea57d734261914e697bd73b69eff7f18e967e8912aa2a40a19a9f599a507fa805be6c13c24c4eae8c71306c239d517d42d4c041c942f508a0 + languageName: node + linkType: hard + +"p-finally@npm:^1.0.0": + version: 1.0.0 + resolution: "p-finally@npm:1.0.0" + checksum: 93a654c53dc805dd5b5891bab16eb0ea46db8f66c4bfd99336ae929323b1af2b70a8b0654f8f1eae924b2b73d037031366d645f1fd18b3d30cbd15950cc4b1d4 + languageName: node + linkType: hard + +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: ^2.0.0 + checksum: 84ff17f1a38126c3314e91ecfe56aecbf36430940e2873dadaa773ffe072dc23b7af8e46d4b6485d302a11673fe94c6b67ca2cfbb60c989848b02100d0594ac1 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2": + version: 3.1.0 + resolution: "p-limit@npm:3.1.0" + dependencies: + yocto-queue: ^0.1.0 + checksum: 7c3690c4dbf62ef625671e20b7bdf1cbc9534e83352a2780f165b0d3ceba21907e77ad63401708145ca4e25bfc51636588d89a8c0aeb715e6c37d1c066430360 + languageName: node + linkType: hard + +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: ^2.2.0 + checksum: 513bd14a455f5da4ebfcb819ef706c54adb09097703de6aeaa5d26fe5ea16df92b48d1ac45e01e3944ce1e6aa2a66f7f8894742b8c9d6e276e16cd2049a2b870 + languageName: node + linkType: hard + +"p-locate@npm:^5.0.0": + version: 5.0.0 + resolution: "p-locate@npm:5.0.0" + dependencies: + p-limit: ^3.0.2 + checksum: 1623088f36cf1cbca58e9b61c4e62bf0c60a07af5ae1ca99a720837356b5b6c5ba3eb1b2127e47a06865fee59dd0453cad7cc844cda9d5a62ac1a5a51b7c86d3 + languageName: node + linkType: hard + +"p-map@npm:^4.0.0": + version: 4.0.0 + resolution: "p-map@npm:4.0.0" + dependencies: + aggregate-error: ^3.0.0 + checksum: cb0ab21ec0f32ddffd31dfc250e3afa61e103ef43d957cc45497afe37513634589316de4eb88abdfd969fe6410c22c0b93ab24328833b8eb1ccc087fc0442a1c + languageName: node + linkType: hard + +"p-queue@npm:^6.6.2": + version: 6.6.2 + resolution: "p-queue@npm:6.6.2" + dependencies: + eventemitter3: ^4.0.4 + p-timeout: ^3.2.0 + checksum: 832642fcc4ab6477b43e6d7c30209ab10952969ed211c6d6f2931be8a4f9935e3578c72e8cce053dc34f2eb6941a408a2c516a54904e989851a1a209cf19761c + languageName: node + linkType: hard + +"p-timeout@npm:^3.2.0": + version: 3.2.0 + resolution: "p-timeout@npm:3.2.0" + dependencies: + p-finally: ^1.0.0 + checksum: 3dd0eaa048780a6f23e5855df3dd45c7beacff1f820476c1d0d1bcd6648e3298752ba2c877aa1c92f6453c7dd23faaf13d9f5149fc14c0598a142e2c5e8d649c + languageName: node + linkType: hard + +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: f8a8e9a7693659383f06aec604ad5ead237c7a261c18048a6e1b5b85a5f8a067e469aa24f5bc009b991ea3b058a87f5065ef4176793a200d4917349881216cae + languageName: node + linkType: hard + +"parent-module@npm:^1.0.0": + version: 1.0.1 + resolution: "parent-module@npm:1.0.1" + dependencies: + callsites: ^3.0.0 + checksum: 6ba8b255145cae9470cf5551eb74be2d22281587af787a2626683a6c20fbb464978784661478dd2a3f1dad74d1e802d403e1b03c1a31fab310259eec8ac560ff + languageName: node + linkType: hard + +"parse-json@npm:^5.0.0": + version: 5.2.0 + resolution: "parse-json@npm:5.2.0" + dependencies: + "@babel/code-frame": ^7.0.0 + error-ex: ^1.3.1 + json-parse-even-better-errors: ^2.3.0 + lines-and-columns: ^1.1.6 + checksum: 62085b17d64da57f40f6afc2ac1f4d95def18c4323577e1eced571db75d9ab59b297d1d10582920f84b15985cbfc6b6d450ccbf317644cfa176f3ed982ad87e2 + languageName: node + linkType: hard + +"path-browserify@npm:^1.0.1": + version: 1.0.1 + resolution: "path-browserify@npm:1.0.1" + checksum: c6d7fa376423fe35b95b2d67990060c3ee304fc815ff0a2dc1c6c3cfaff2bd0d572ee67e18f19d0ea3bbe32e8add2a05021132ac40509416459fffee35200699 + languageName: node + linkType: hard + +"path-exists@npm:^4.0.0": + version: 4.0.0 + resolution: "path-exists@npm:4.0.0" + checksum: 505807199dfb7c50737b057dd8d351b82c033029ab94cb10a657609e00c1bc53b951cfdbccab8de04c5584d5eff31128ce6afd3db79281874a5ef2adbba55ed1 + languageName: node + linkType: hard + +"path-is-absolute@npm:^1.0.0": + version: 1.0.1 + resolution: "path-is-absolute@npm:1.0.1" + checksum: 060840f92cf8effa293bcc1bea81281bd7d363731d214cbe5c227df207c34cd727430f70c6037b5159c8a870b9157cba65e775446b0ab06fd5ecc7e54615a3b8 + languageName: node + linkType: hard + +"path-key@npm:^3.1.0": + version: 3.1.1 + resolution: "path-key@npm:3.1.1" + checksum: 55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 + languageName: node + linkType: hard + +"path-parse@npm:^1.0.7": + version: 1.0.7 + resolution: "path-parse@npm:1.0.7" + checksum: 49abf3d81115642938a8700ec580da6e830dde670be21893c62f4e10bd7dd4c3742ddc603fe24f898cba7eb0c6bc1777f8d9ac14185d34540c6d4d80cd9cae8a + languageName: node + linkType: hard + +"path-type@npm:^4.0.0": + version: 4.0.0 + resolution: "path-type@npm:4.0.0" + checksum: 5b1e2daa247062061325b8fdbfd1fb56dde0a448fb1455453276ea18c60685bdad23a445dc148cf87bc216be1573357509b7d4060494a6fd768c7efad833ee45 + languageName: node + linkType: hard + +"picocolors@npm:^1.0.0": + version: 1.0.0 + resolution: "picocolors@npm:1.0.0" + checksum: a2e8092dd86c8396bdba9f2b5481032848525b3dc295ce9b57896f931e63fc16f79805144321f72976383fc249584672a75cc18d6777c6b757603f372f745981 + languageName: node + linkType: hard + +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.2, picomatch@npm:^2.3.1": + version: 2.3.1 + resolution: "picomatch@npm:2.3.1" + checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf + languageName: node + linkType: hard + +"pkg-dir@npm:^4.1.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: ^4.0.0 + checksum: 9863e3f35132bf99ae1636d31ff1e1e3501251d480336edb1c211133c8d58906bed80f154a1d723652df1fda91e01c7442c2eeaf9dc83157c7ae89087e43c8d6 + languageName: node + linkType: hard + +"postcss-calc@npm:^8.2.3": + version: 8.2.4 + resolution: "postcss-calc@npm:8.2.4" + dependencies: + postcss-selector-parser: ^6.0.9 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.2 + checksum: 314b4cebb0c4ed0cf8356b4bce71eca78f5a7842e6a3942a3bba49db168d5296b2bd93c3f735ae1c616f2651d94719ade33becc03c73d2d79c7394fb7f73eabb + languageName: node + linkType: hard + +"postcss-colormin@npm:^5.3.0": + version: 5.3.0 + resolution: "postcss-colormin@npm:5.3.0" + dependencies: + browserslist: ^4.16.6 + caniuse-api: ^3.0.0 + colord: ^2.9.1 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 3d3e3cc25071407fb73d68541ca1039ebd154fceb649041461a8a3cab0400cc89b42dbb34a4eeaf573be4ba2370ce23af5e01aff5e03a8d72275f40605577212 + languageName: node + linkType: hard + +"postcss-convert-values@npm:^5.1.3": + version: 5.1.3 + resolution: "postcss-convert-values@npm:5.1.3" + dependencies: + browserslist: ^4.21.4 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: df48cdaffabf9737f9cfdc58a3dc2841cf282506a7a944f6c70236cff295d3a69f63de6e0935eeb8a9d3f504324e5b4e240abc29e21df9e35a02585d3060aeb5 + languageName: node + linkType: hard + +"postcss-discard-comments@npm:^5.1.2": + version: 5.1.2 + resolution: "postcss-discard-comments@npm:5.1.2" + peerDependencies: + postcss: ^8.2.15 + checksum: abfd064ebc27aeaf5037643dd51ffaff74d1fa4db56b0523d073ace4248cbb64ffd9787bd6924b0983a9d0bd0e9bf9f10d73b120e50391dc236e0d26c812fa2a + languageName: node + linkType: hard + +"postcss-discard-duplicates@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-discard-duplicates@npm:5.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: 88d6964201b1f4ed6bf7a32cefe68e86258bb6e42316ca01d9b32bdb18e7887d02594f89f4a2711d01b51ea6e3fcca8c54be18a59770fe5f4521c61d3eb6ca35 + languageName: node + linkType: hard + +"postcss-discard-empty@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-discard-empty@npm:5.1.1" + peerDependencies: + postcss: ^8.2.15 + checksum: 970adb12fae5c214c0768236ad9a821552626e77dedbf24a8213d19cc2c4a531a757cd3b8cdd3fc22fb1742471b8692a1db5efe436a71236dec12b1318ee8ff4 + languageName: node + linkType: hard + +"postcss-discard-overridden@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-discard-overridden@npm:5.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: d64d4a545aa2c81b22542895cfcddc787d24119f294d35d29b0599a1c818b3cc51f4ee80b80f5a0a09db282453dd5ac49f104c2117cc09112d0ac9b40b499a41 + languageName: node + linkType: hard + +"postcss-merge-longhand@npm:^5.1.7": + version: 5.1.7 + resolution: "postcss-merge-longhand@npm:5.1.7" + dependencies: + postcss-value-parser: ^4.2.0 + stylehacks: ^5.1.1 + peerDependencies: + postcss: ^8.2.15 + checksum: 81c3fc809f001b9b71a940148e242bdd6e2d77713d1bfffa15eb25c1f06f6648d5e57cb21645746d020a2a55ff31e1740d2b27900442913a9d53d8a01fb37e1b + languageName: node + linkType: hard + +"postcss-merge-rules@npm:^5.1.3": + version: 5.1.3 + resolution: "postcss-merge-rules@npm:5.1.3" + dependencies: + browserslist: ^4.21.4 + caniuse-api: ^3.0.0 + cssnano-utils: ^3.1.0 + postcss-selector-parser: ^6.0.5 + peerDependencies: + postcss: ^8.2.15 + checksum: 0ddaddff98cd7f3fac2b0e716c641f529a61a8668be6d5b48d60770d0a1246126088e1d606f309b9748ff598a3794f3fd6dd5b8c3d79112f84744cab5375d4d9 + languageName: node + linkType: hard + +"postcss-minify-font-values@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-minify-font-values@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 35e858fa41efa05acdeb28f1c76579c409fdc7eabb1744c3bd76e895bb9fea341a016746362a67609688ab2471f587202b9a3e14ea28ad677754d663a2777ece + languageName: node + linkType: hard + +"postcss-minify-gradients@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-minify-gradients@npm:5.1.1" + dependencies: + colord: ^2.9.1 + cssnano-utils: ^3.1.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 27354072a07c5e6dab36731103b94ca2354d4ed3c5bc6aacfdf2ede5a55fa324679d8fee5450800bc50888dbb5e9ed67569c0012040c2be128143d0cebb36d67 + languageName: node + linkType: hard + +"postcss-minify-params@npm:^5.1.4": + version: 5.1.4 + resolution: "postcss-minify-params@npm:5.1.4" + dependencies: + browserslist: ^4.21.4 + cssnano-utils: ^3.1.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: bd63e2cc89edcf357bb5c2a16035f6d02ef676b8cede4213b2bddd42626b3d428403849188f95576fc9f03e43ebd73a29bf61d33a581be9a510b13b7f7f100d5 + languageName: node + linkType: hard + +"postcss-minify-selectors@npm:^5.2.1": + version: 5.2.1 + resolution: "postcss-minify-selectors@npm:5.2.1" + dependencies: + postcss-selector-parser: ^6.0.5 + peerDependencies: + postcss: ^8.2.15 + checksum: 6fdbc84f99a60d56b43df8930707da397775e4c36062a106aea2fd2ac81b5e24e584a1892f4baa4469fa495cb87d1422560eaa8f6c9d500f9f0b691a5f95bab5 + languageName: node + linkType: hard + +"postcss-modules-extract-imports@npm:^3.0.0": + version: 3.0.0 + resolution: "postcss-modules-extract-imports@npm:3.0.0" + peerDependencies: + postcss: ^8.1.0 + checksum: 4b65f2f1382d89c4bc3c0a1bdc5942f52f3cb19c110c57bd591ffab3a5fee03fcf831604168205b0c1b631a3dce2255c70b61aaae3ef39d69cd7eb450c2552d2 + languageName: node + linkType: hard + +"postcss-modules-local-by-default@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-modules-local-by-default@npm:4.0.0" + dependencies: + icss-utils: ^5.0.0 + postcss-selector-parser: ^6.0.2 + postcss-value-parser: ^4.1.0 + peerDependencies: + postcss: ^8.1.0 + checksum: 6cf570badc7bc26c265e073f3ff9596b69bb954bc6ac9c5c1b8cba2995b80834226b60e0a3cbb87d5f399dbb52e6466bba8aa1d244f6218f99d834aec431a69d + languageName: node + linkType: hard + +"postcss-modules-scope@npm:^3.0.0": + version: 3.0.0 + resolution: "postcss-modules-scope@npm:3.0.0" + dependencies: + postcss-selector-parser: ^6.0.4 + peerDependencies: + postcss: ^8.1.0 + checksum: 330b9398dbd44c992c92b0dc612c0626135e2cc840fee41841eb61247a6cfed95af2bd6f67ead9dd9d0bb41f5b0367129d93c6e434fa3e9c58ade391d9a5a138 + languageName: node + linkType: hard + +"postcss-modules-values@npm:^4.0.0": + version: 4.0.0 + resolution: "postcss-modules-values@npm:4.0.0" + dependencies: + icss-utils: ^5.0.0 + peerDependencies: + postcss: ^8.1.0 + checksum: f7f2cdf14a575b60e919ad5ea52fed48da46fe80db2733318d71d523fc87db66c835814940d7d05b5746b0426e44661c707f09bdb83592c16aea06e859409db6 + languageName: node + linkType: hard + +"postcss-normalize-charset@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-charset@npm:5.1.0" + peerDependencies: + postcss: ^8.2.15 + checksum: e79d92971fc05b8b3c9b72f3535a574e077d13c69bef68156a0965f397fdf157de670da72b797f57b0e3bac8f38155b5dd1735ecab143b9cc4032d72138193b4 + languageName: node + linkType: hard + +"postcss-normalize-display-values@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-display-values@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: b6eb7b9b02c3bdd62bbc54e01e2b59733d73a1c156905d238e178762962efe0c6f5104544da39f32cade8a4fb40f10ff54b63a8ebfbdff51e8780afb9fbdcf86 + languageName: node + linkType: hard + +"postcss-normalize-positions@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-positions@npm:5.1.1" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: d9afc233729c496463c7b1cdd06732469f401deb387484c3a2422125b46ec10b4af794c101f8c023af56f01970b72b535e88373b9058ecccbbf88db81662b3c4 + languageName: node + linkType: hard + +"postcss-normalize-repeat-style@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-repeat-style@npm:5.1.1" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 2c6ad2b0ae10a1fda156b948c34f78c8f1e185513593de4d7e2480973586675520edfec427645fa168c337b0a6b3ceca26f92b96149741ca98a9806dad30d534 + languageName: node + linkType: hard + +"postcss-normalize-string@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-string@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 6e549c6e5b2831e34c7bdd46d8419e2278f6af1d5eef6d26884a37c162844e60339340c57e5e06058cdbe32f27fc6258eef233e811ed2f71168ef2229c236ada + languageName: node + linkType: hard + +"postcss-normalize-timing-functions@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-timing-functions@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: da550f50e90b0b23e17b67449a7d1efd1aa68288e66d4aa7614ca6f5cc012896be1972b7168eee673d27da36504faccf7b9f835c0f7e81243f966a42c8c030aa + languageName: node + linkType: hard + +"postcss-normalize-unicode@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-unicode@npm:5.1.1" + dependencies: + browserslist: ^4.21.4 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 4c24d26cc9f4b19a9397db4e71dd600dab690f1de8e14a3809e2aa1452dbc3791c208c38a6316bbc142f29e934fdf02858e68c94038c06174d78a4937e0f273c + languageName: node + linkType: hard + +"postcss-normalize-url@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-normalize-url@npm:5.1.0" + dependencies: + normalize-url: ^6.0.1 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 3bd4b3246d6600230bc827d1760b24cb3101827ec97570e3016cbe04dc0dd28f4dbe763245d1b9d476e182c843008fbea80823061f1d2219b96f0d5c724a24c0 + languageName: node + linkType: hard + +"postcss-normalize-whitespace@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-normalize-whitespace@npm:5.1.1" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 12d8fb6d1c1cba208cc08c1830959b7d7ad447c3f5581873f7e185f99a9a4230c43d3af21ca12c818e4690a5085a95b01635b762ad4a7bef69d642609b4c0e19 + languageName: node + linkType: hard + +"postcss-ordered-values@npm:^5.1.3": + version: 5.1.3 + resolution: "postcss-ordered-values@npm:5.1.3" + dependencies: + cssnano-utils: ^3.1.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 6f3ca85b6ceffc68aadaf319d9ee4c5ac16d93195bf8cba2d1559b631555ad61941461cda6d3909faab86e52389846b2b36345cff8f0c3f4eb345b1b8efadcf9 + languageName: node + linkType: hard + +"postcss-reduce-initial@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-reduce-initial@npm:5.1.1" + dependencies: + browserslist: ^4.21.4 + caniuse-api: ^3.0.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 1b704aba8c38103cbb5a75c6201dbf58ec2f3a978013c7f7e8957fd3bf3282f992050dec5a01bc050d031bad836e187dd6622b922ca78ab92bcd0afd21fb0b98 + languageName: node + linkType: hard + +"postcss-reduce-transforms@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-reduce-transforms@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.2.15 + checksum: 0c6af2cba20e3ff63eb9ad045e634ddfb9c3e5c0e614c020db2a02f3aa20632318c4ede9e0c995f9225d9a101e673de91c0a6e10bb2fa5da6d6c75d15a55882f + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.5, postcss-selector-parser@npm:^6.0.9": + version: 6.0.11 + resolution: "postcss-selector-parser@npm:6.0.11" + dependencies: + cssesc: ^3.0.0 + util-deprecate: ^1.0.2 + checksum: 0b01aa9c2d2c8dbeb51e9b204796b678284be9823abc8d6d40a8b16d4149514e922c264a8ed4deb4d6dbced564b9be390f5942c058582d8656351516d6c49cde + languageName: node + linkType: hard + +"postcss-svgo@npm:^5.1.0": + version: 5.1.0 + resolution: "postcss-svgo@npm:5.1.0" + dependencies: + postcss-value-parser: ^4.2.0 + svgo: ^2.7.0 + peerDependencies: + postcss: ^8.2.15 + checksum: d86eb5213d9f700cf5efe3073799b485fb7cacae0c731db3d7749c9c2b1c9bc85e95e0baeca439d699ff32ea24815fc916c4071b08f67ed8219df229ce1129bd + languageName: node + linkType: hard + +"postcss-unique-selectors@npm:^5.1.1": + version: 5.1.1 + resolution: "postcss-unique-selectors@npm:5.1.1" + dependencies: + postcss-selector-parser: ^6.0.5 + peerDependencies: + postcss: ^8.2.15 + checksum: 637e7b786e8558265775c30400c54b6b3b24d4748923f4a39f16a65fd0e394f564ccc9f0a1d3c0e770618a7637a7502ea1d0d79f731d429cb202255253c23278 + languageName: node + linkType: hard + +"postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: 819ffab0c9d51cf0acbabf8996dffbfafbafa57afc0e4c98db88b67f2094cb44488758f06e5da95d7036f19556a4a732525e84289a425f4f6fd8e412a9d7442f + languageName: node + linkType: hard + +"postcss@npm:^8.4.5": + version: 8.4.21 + resolution: "postcss@npm:8.4.21" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: e39ac60ccd1542d4f9d93d894048aac0d686b3bb38e927d8386005718e6793dbbb46930f0a523fe382f1bbd843c6d980aaea791252bf5e176180e5a4336d9679 + languageName: node + linkType: hard + +"prelude-ls@npm:^1.2.1": + version: 1.2.1 + resolution: "prelude-ls@npm:1.2.1" + checksum: cd192ec0d0a8e4c6da3bb80e4f62afe336df3f76271ac6deb0e6a36187133b6073a19e9727a1ff108cd8b9982e4768850d413baa71214dd80c7979617dca827a + languageName: node + linkType: hard + +"prettier-linter-helpers@npm:^1.0.0": + version: 1.0.0 + resolution: "prettier-linter-helpers@npm:1.0.0" + dependencies: + fast-diff: ^1.1.2 + checksum: 00ce8011cf6430158d27f9c92cfea0a7699405633f7f1d4a45f07e21bf78e99895911cbcdc3853db3a824201a7c745bd49bfea8abd5fb9883e765a90f74f8392 + languageName: node + linkType: hard + +"prettier@npm:^2.6.0": + version: 2.8.3 + resolution: "prettier@npm:2.8.3" + bin: + prettier: bin-prettier.js + checksum: 92f2ceb522d454370e02082aa74ad27388672f7cee8975028b59517c069fe643bdc73e322675c8faf2ff173d7a626d1a6389f26b474000308e793aa25fff46e5 + languageName: node + linkType: hard + +"promise-inflight@npm:^1.0.1": + version: 1.0.1 + resolution: "promise-inflight@npm:1.0.1" + checksum: 22749483091d2c594261517f4f80e05226d4d5ecc1fc917e1886929da56e22b5718b7f2a75f3807e7a7d471bc3be2907fe92e6e8f373ddf5c64bae35b5af3981 + languageName: node + linkType: hard + +"promise-retry@npm:^2.0.1": + version: 2.0.1 + resolution: "promise-retry@npm:2.0.1" + dependencies: + err-code: ^2.0.2 + retry: ^0.12.0 + checksum: f96a3f6d90b92b568a26f71e966cbbc0f63ab85ea6ff6c81284dc869b41510e6cdef99b6b65f9030f0db422bf7c96652a3fff9f2e8fb4a0f069d8f4430359429 + languageName: node + linkType: hard + +"propagating-hammerjs@npm:^2.0.1": + version: 2.0.1 + resolution: "propagating-hammerjs@npm:2.0.1" + peerDependencies: + "@egjs/hammerjs": ^2.0.17 + checksum: 7177010beae4536c681f0d44c5f7208fb46d4c90cd79f18d7e96e2b4e0f2a3386b79b291e74813a3ccc155ad5dd40bf87796ec63efda84f4e6bdd82007f4270a + languageName: node + linkType: hard + +"punycode@npm:^2.1.0": + version: 2.3.0 + resolution: "punycode@npm:2.3.0" + checksum: 39f760e09a2a3bbfe8f5287cf733ecdad69d6af2fe6f97ca95f24b8921858b91e9ea3c9eeec6e08cede96181b3bb33f95c6ffd8c77e63986508aa2e8159fa200 + languageName: node + linkType: hard + +"query-string@npm:^7.1.0": + version: 7.1.3 + resolution: "query-string@npm:7.1.3" + dependencies: + decode-uri-component: ^0.2.2 + filter-obj: ^1.1.0 + split-on-first: ^1.0.0 + strict-uri-encode: ^2.0.0 + checksum: 91af02dcd9cc9227a052841d5c2eecb80a0d6489d05625df506a097ef1c59037cfb5e907f39b84643cbfd535c955abec3e553d0130a7b510120c37d06e0f4346 + languageName: node + linkType: hard + +"queue-microtask@npm:^1.2.2": + version: 1.2.3 + resolution: "queue-microtask@npm:1.2.3" + checksum: b676f8c040cdc5b12723ad2f91414d267605b26419d5c821ff03befa817ddd10e238d22b25d604920340fd73efd8ba795465a0377c4adf45a4a41e4234e42dc4 + languageName: node + linkType: hard + +"quick-lru@npm:^6.1.0": + version: 6.1.1 + resolution: "quick-lru@npm:6.1.1" + checksum: a9c75bf1d208c1d207590e10403a75d037f9faf7857f2e0d38c294d92b40d8b400776bcb5d4dd516baa84e6f5e3baf2a16cf405e6fedc18107922f46aeae83ee + languageName: node + linkType: hard + +"randombytes@npm:^2.1.0": + version: 2.1.0 + resolution: "randombytes@npm:2.1.0" + dependencies: + safe-buffer: ^5.1.0 + checksum: d779499376bd4cbb435ef3ab9a957006c8682f343f14089ed5f27764e4645114196e75b7f6abf1cbd84fd247c0cb0651698444df8c9bf30e62120fbbc52269d6 + languageName: node + linkType: hard + +"readable-stream@npm:^3.6.0": + version: 3.6.0 + resolution: "readable-stream@npm:3.6.0" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: d4ea81502d3799439bb955a3a5d1d808592cf3133350ed352aeaa499647858b27b1c4013984900238b0873ec8d0d8defce72469fb7a83e61d53f5ad61cb80dc8 + languageName: node + linkType: hard + +"readdirp@npm:~3.6.0": + version: 3.6.0 + resolution: "readdirp@npm:3.6.0" + dependencies: + picomatch: ^2.2.1 + checksum: 1ced032e6e45670b6d7352d71d21ce7edf7b9b928494dcaba6f11fba63180d9da6cd7061ebc34175ffda6ff529f481818c962952004d273178acd70f7059b320 + languageName: node + linkType: hard + +"regexp.prototype.flags@npm:^1.4.3": + version: 1.4.3 + resolution: "regexp.prototype.flags@npm:1.4.3" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.3 + functions-have-names: ^1.2.2 + checksum: 51228bae732592adb3ededd5e15426be25f289e9c4ef15212f4da73f4ec3919b6140806374b8894036a86020d054a8d2657d3fee6bb9b4d35d8939c20030b7a6 + languageName: node + linkType: hard + +"regexpp@npm:^3.2.0": + version: 3.2.0 + resolution: "regexpp@npm:3.2.0" + checksum: a78dc5c7158ad9ddcfe01aa9144f46e192ddbfa7b263895a70a5c6c73edd9ce85faf7c0430e59ac38839e1734e275b9c3de5c57ee3ab6edc0e0b1bdebefccef8 + languageName: node + linkType: hard + +"require-directory@npm:^2.1.1": + version: 2.1.1 + resolution: "require-directory@npm:2.1.1" + checksum: fb47e70bf0001fdeabdc0429d431863e9475e7e43ea5f94ad86503d918423c1543361cc5166d713eaa7029dd7a3d34775af04764bebff99ef413111a5af18c80 + languageName: node + linkType: hard + +"resolve-from@npm:^4.0.0": + version: 4.0.0 + resolution: "resolve-from@npm:4.0.0" + checksum: f4ba0b8494846a5066328ad33ef8ac173801a51739eb4d63408c847da9a2e1c1de1e6cbbf72699211f3d13f8fc1325648b169bd15eb7da35688e30a5fb0e4a7f + languageName: node + linkType: hard + +"resolve@npm:^1.17.0, resolve@npm:^1.19.0, resolve@npm:^1.21.0, resolve@npm:^1.22.1": + version: 1.22.1 + resolution: "resolve@npm:1.22.1" + dependencies: + is-core-module: ^2.9.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 07af5fc1e81aa1d866cbc9e9460fbb67318a10fa3c4deadc35c3ad8a898ee9a71a86a65e4755ac3195e0ea0cfbe201eb323ebe655ce90526fd61917313a34e4e + languageName: node + linkType: hard + +"resolve@patch:resolve@^1.17.0#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.21.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin": + version: 1.22.1 + resolution: "resolve@patch:resolve@npm%3A1.22.1#~builtin::version=1.22.1&hash=c3c19d" + dependencies: + is-core-module: ^2.9.0 + path-parse: ^1.0.7 + supports-preserve-symlinks-flag: ^1.0.0 + bin: + resolve: bin/resolve + checksum: 5656f4d0bedcf8eb52685c1abdf8fbe73a1603bb1160a24d716e27a57f6cecbe2432ff9c89c2bd57542c3a7b9d14b1882b73bfe2e9d7849c9a4c0b8b39f02b8b + languageName: node + linkType: hard + +"retry@npm:^0.12.0": + version: 0.12.0 + resolution: "retry@npm:0.12.0" + checksum: 623bd7d2e5119467ba66202d733ec3c2e2e26568074923bc0585b6b99db14f357e79bdedb63cab56cec47491c4a0da7e6021a7465ca6dc4f481d3898fdd3158c + languageName: node + linkType: hard + +"reusify@npm:^1.0.4": + version: 1.0.4 + resolution: "reusify@npm:1.0.4" + checksum: c3076ebcc22a6bc252cb0b9c77561795256c22b757f40c0d8110b1300723f15ec0fc8685e8d4ea6d7666f36c79ccc793b1939c748bf36f18f542744a4e379fcc + languageName: node + linkType: hard + +"rimraf@npm:^3.0.2": + version: 3.0.2 + resolution: "rimraf@npm:3.0.2" + dependencies: + glob: ^7.1.3 + bin: + rimraf: bin.js + checksum: 87f4164e396f0171b0a3386cc1877a817f572148ee13a7e113b238e48e8a9f2f31d009a92ec38a591ff1567d9662c6b67fd8818a2dbbaed74bc26a87a2a4a9a0 + languageName: node + linkType: hard + +"rollup-plugin-git-info@npm:^1.0.0": + version: 1.0.0 + resolution: "rollup-plugin-git-info@npm:1.0.0" + dependencies: + "@rollup/plugin-json": ^4.1.0 + checksum: b54133235f102fa392edfaae2da2c4568cd6c8ac59dfd0cdf5b003f6af0ce0dc423b3f4686ac671d1302af80bc752df04d10c76152a76b9e25b1d58f1c337751 + languageName: node + linkType: hard + +"rollup-plugin-serve@npm:^2.0.1": + version: 2.0.2 + resolution: "rollup-plugin-serve@npm:2.0.2" + dependencies: + mime: ">=2.4.6" + opener: 1 + checksum: de74616bc197360d282cabb26aa2f77780b4864364f62562d1d6cb3fd3ba74f956e1ce1bd881edf122e11e234b96132857a760e0668f22aebbd54a0fc55d1bec + languageName: node + linkType: hard + +"rollup-plugin-styles@npm:^4.0.0": + version: 4.0.0 + resolution: "rollup-plugin-styles@npm:4.0.0" + dependencies: + "@rollup/pluginutils": ^4.1.2 + "@types/cssnano": ^5.0.0 + cosmiconfig: ^7.0.1 + cssnano: ^5.0.15 + fs-extra: ^10.0.0 + icss-utils: ^5.1.0 + mime-types: ^2.1.34 + p-queue: ^6.6.2 + postcss: ^8.4.5 + postcss-modules-extract-imports: ^3.0.0 + postcss-modules-local-by-default: ^4.0.0 + postcss-modules-scope: ^3.0.0 + postcss-modules-values: ^4.0.0 + postcss-value-parser: ^4.2.0 + query-string: ^7.1.0 + resolve: ^1.21.0 + source-map-js: ^1.0.1 + tslib: ^2.3.1 + peerDependencies: + rollup: ^2.63.0 + checksum: 857e5fd6820aedf2f751878d2ef4fa40210094fb548d1baaa1dafbcdd7f7ea9775bac1c899b305bd27a23026d0694940651692a39901ac2ba2f8ce9f2bd10eb1 + languageName: node + linkType: hard + +"rollup-plugin-terser@npm:^7.0.2": + version: 7.0.2 + resolution: "rollup-plugin-terser@npm:7.0.2" + dependencies: + "@babel/code-frame": ^7.10.4 + jest-worker: ^26.2.1 + serialize-javascript: ^4.0.0 + terser: ^5.0.0 + peerDependencies: + rollup: ^2.0.0 + checksum: af84bb7a7a894cd00852b6486528dfb8653cf94df4c126f95f389a346f401d054b08c46bee519a2ab6a22b33804d1d6ac6d8c90b1b2bf8fffb097eed73fc3c72 + languageName: node + linkType: hard + +"rollup-plugin-typescript2@npm:^0.33.0": + version: 0.33.0 + resolution: "rollup-plugin-typescript2@npm:0.33.0" + dependencies: + "@rollup/pluginutils": ^4.1.2 + find-cache-dir: ^3.3.2 + fs-extra: ^10.0.0 + tslib: ^2.4.0 + peerDependencies: + rollup: ">=1.26.3" + typescript: ">=2.4.0" + checksum: 03e3077c5197f512222f11ebb8b7682c97ddbd6332189ea06884aa3e778a04107483d340f3be94dbc471595e8908687dd841b81976855485486097b6bb095f65 + languageName: node + linkType: hard + +"rollup-plugin-visualizer@npm:^5.8.2": + version: 5.9.0 + resolution: "rollup-plugin-visualizer@npm:5.9.0" + dependencies: + open: ^8.4.0 + picomatch: ^2.3.1 + source-map: ^0.7.4 + yargs: ^17.5.1 + peerDependencies: + rollup: 2.x || 3.x + peerDependenciesMeta: + rollup: + optional: true + bin: + rollup-plugin-visualizer: dist/bin/cli.js + checksum: 362d4fac0295c14bd205dbc85c20c31f4b6c47604868da21d9565ed47e0333759f08b9fe0acb82f78221f5173ea01e4eb70d47351eb6012216afe71b5492ed5f + languageName: node + linkType: hard + +"rollup@npm:^2.63.0, rollup@npm:^2.79.0": + version: 2.79.1 + resolution: "rollup@npm:2.79.1" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 6a2bf167b3587d4df709b37d149ad0300692cc5deb510f89ac7bdc77c8738c9546ae3de9322b0968e1ed2b0e984571f5f55aae28fa7de4cfcb1bc5402a4e2be6 + languageName: node + linkType: hard + +"run-parallel@npm:^1.1.9": + version: 1.2.0 + resolution: "run-parallel@npm:1.2.0" + dependencies: + queue-microtask: ^1.2.2 + checksum: cb4f97ad25a75ebc11a8ef4e33bb962f8af8516bb2001082ceabd8902e15b98f4b84b4f8a9b222e5d57fc3bd1379c483886ed4619367a7680dad65316993021d + languageName: node + linkType: hard + +"safe-buffer@npm:^5.1.0, safe-buffer@npm:~5.2.0": + version: 5.2.1 + resolution: "safe-buffer@npm:5.2.1" + checksum: b99c4b41fdd67a6aaf280fcd05e9ffb0813654894223afb78a31f14a19ad220bba8aba1cb14eddce1fcfb037155fe6de4e861784eb434f7d11ed58d1e70dd491 + languageName: node + linkType: hard + +"safe-regex-test@npm:^1.0.0": + version: 1.0.0 + resolution: "safe-regex-test@npm:1.0.0" + dependencies: + call-bind: ^1.0.2 + get-intrinsic: ^1.1.3 + is-regex: ^1.1.4 + checksum: bc566d8beb8b43c01b94e67de3f070fd2781685e835959bbbaaec91cc53381145ca91f69bd837ce6ec244817afa0a5e974fc4e40a2957f0aca68ac3add1ddd34 + languageName: node + linkType: hard + +"safer-buffer@npm:>= 2.1.2 < 3.0.0": + version: 2.1.2 + resolution: "safer-buffer@npm:2.1.2" + checksum: cab8f25ae6f1434abee8d80023d7e72b598cf1327164ddab31003c51215526801e40b66c5e65d658a0af1e9d6478cadcb4c745f4bd6751f97d8644786c0978b0 + languageName: node + linkType: hard + +"sass@npm:^1.54.9": + version: 1.58.0 + resolution: "sass@npm:1.58.0" + dependencies: + chokidar: ">=3.0.0 <4.0.0" + immutable: ^4.0.0 + source-map-js: ">=0.6.2 <2.0.0" + bin: + sass: sass.js + checksum: a7219634881d2de6441fb619787fb1a02e3fa0333fb715be26aa335ba49d6bdb4f1105d9df70a80a67200893022b08346745783dc49046095d94fc6e044492d6 + languageName: node + linkType: hard + +"screenfull@npm:^6.0.2": + version: 6.0.2 + resolution: "screenfull@npm:6.0.2" + checksum: 56a541da09241ae7c97115cc31245623c09230bcbebb93636871e955b545f26c7dcdb2299795234a0a21bff5d5401b7329b22609becf4c381a9e22347acf87ba + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.3.0": + version: 6.3.0 + resolution: "semver@npm:6.3.0" + bin: + semver: ./bin/semver.js + checksum: 1b26ecf6db9e8292dd90df4e781d91875c0dcc1b1909e70f5d12959a23c7eebb8f01ea581c00783bbee72ceeaad9505797c381756326073850dc36ed284b21b9 + languageName: node + linkType: hard + +"semver@npm:^7.3.5, semver@npm:^7.3.7": + version: 7.3.8 + resolution: "semver@npm:7.3.8" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: ba9c7cbbf2b7884696523450a61fee1a09930d888b7a8d7579025ad93d459b2d1949ee5bbfeb188b2be5f4ac163544c5e98491ad6152df34154feebc2cc337c1 + languageName: node + linkType: hard + +"serialize-javascript@npm:^4.0.0": + version: 4.0.0 + resolution: "serialize-javascript@npm:4.0.0" + dependencies: + randombytes: ^2.1.0 + checksum: 3273b3394b951671fcf388726e9577021870dfbf85e742a1183fb2e91273e6101bdccea81ff230724f6659a7ee4cef924b0ff9baca32b79d9384ec37caf07302 + languageName: node + linkType: hard + +"set-blocking@npm:^2.0.0": + version: 2.0.0 + resolution: "set-blocking@npm:2.0.0" + checksum: 6e65a05f7cf7ebdf8b7c75b101e18c0b7e3dff4940d480efed8aad3a36a4005140b660fa1d804cb8bce911cac290441dc728084a30504d3516ac2ff7ad607b02 + languageName: node + linkType: hard + +"shebang-command@npm:^2.0.0": + version: 2.0.0 + resolution: "shebang-command@npm:2.0.0" + dependencies: + shebang-regex: ^3.0.0 + checksum: 6b52fe87271c12968f6a054e60f6bde5f0f3d2db483a1e5c3e12d657c488a15474121a1d55cd958f6df026a54374ec38a4a963988c213b7570e1d51575cea7fa + languageName: node + linkType: hard + +"shebang-regex@npm:^3.0.0": + version: 3.0.0 + resolution: "shebang-regex@npm:3.0.0" + checksum: 1a2bcae50de99034fcd92ad4212d8e01eedf52c7ec7830eedcf886622804fe36884278f2be8be0ea5fde3fd1c23911643a4e0f726c8685b61871c8908af01222 + languageName: node + linkType: hard + +"side-channel@npm:^1.0.4": + version: 1.0.4 + resolution: "side-channel@npm:1.0.4" + dependencies: + call-bind: ^1.0.0 + get-intrinsic: ^1.0.2 + object-inspect: ^1.9.0 + checksum: 351e41b947079c10bd0858364f32bb3a7379514c399edb64ab3dce683933483fc63fb5e4efe0a15a2e8a7e3c436b6a91736ddb8d8c6591b0460a24bb4a1ee245 + languageName: node + linkType: hard + +"side-drawer@npm:^3.1.0": + version: 3.1.0 + resolution: "side-drawer@npm:3.1.0" + checksum: aa6825741986bc67bac74729f377889f71af96769c4d28cc4f566a1caca828fee61d9b6c79a599914f160af3532942906ff740b385ef95535b45a596a402063e + languageName: node + linkType: hard + +"signal-exit@npm:^3.0.7": + version: 3.0.7 + resolution: "signal-exit@npm:3.0.7" + checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 + languageName: node + linkType: hard + +"slash@npm:^3.0.0": + version: 3.0.0 + resolution: "slash@npm:3.0.0" + checksum: 94a93fff615f25a999ad4b83c9d5e257a7280c90a32a7cb8b4a87996e4babf322e469c42b7f649fd5796edd8687652f3fb452a86dc97a816f01113183393f11c + languageName: node + linkType: hard + +"smart-buffer@npm:^4.2.0": + version: 4.2.0 + resolution: "smart-buffer@npm:4.2.0" + checksum: b5167a7142c1da704c0e3af85c402002b597081dd9575031a90b4f229ca5678e9a36e8a374f1814c8156a725d17008ae3bde63b92f9cfd132526379e580bec8b + languageName: node + linkType: hard + +"socks-proxy-agent@npm:^7.0.0": + version: 7.0.0 + resolution: "socks-proxy-agent@npm:7.0.0" + dependencies: + agent-base: ^6.0.2 + debug: ^4.3.3 + socks: ^2.6.2 + checksum: 720554370154cbc979e2e9ce6a6ec6ced205d02757d8f5d93fe95adae454fc187a5cbfc6b022afab850a5ce9b4c7d73e0f98e381879cf45f66317a4895953846 + languageName: node + linkType: hard + +"socks@npm:^2.6.2": + version: 2.7.1 + resolution: "socks@npm:2.7.1" + dependencies: + ip: ^2.0.0 + smart-buffer: ^4.2.0 + checksum: 259d9e3e8e1c9809a7f5c32238c3d4d2a36b39b83851d0f573bfde5f21c4b1288417ce1af06af1452569cd1eb0841169afd4998f0e04ba04656f6b7f0e46d748 + languageName: node + linkType: hard + +"source-map-js@npm:>=0.6.2 <2.0.0, source-map-js@npm:^1.0.1, source-map-js@npm:^1.0.2": + version: 1.0.2 + resolution: "source-map-js@npm:1.0.2" + checksum: c049a7fc4deb9a7e9b481ae3d424cc793cb4845daa690bc5a05d428bf41bf231ced49b4cf0c9e77f9d42fdb3d20d6187619fc586605f5eabe995a316da8d377c + languageName: node + linkType: hard + +"source-map-support@npm:~0.5.20": + version: 0.5.21 + resolution: "source-map-support@npm:0.5.21" + dependencies: + buffer-from: ^1.0.0 + source-map: ^0.6.0 + checksum: 43e98d700d79af1d36f859bdb7318e601dfc918c7ba2e98456118ebc4c4872b327773e5a1df09b0524e9e5063bb18f0934538eace60cca2710d1fa687645d137 + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": + version: 0.6.1 + resolution: "source-map@npm:0.6.1" + checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 + languageName: node + linkType: hard + +"source-map@npm:^0.7.4": + version: 0.7.4 + resolution: "source-map@npm:0.7.4" + checksum: 01cc5a74b1f0e1d626a58d36ad6898ea820567e87f18dfc9d24a9843a351aaa2ec09b87422589906d6ff1deed29693e176194dc88bcae7c9a852dc74b311dbf5 + languageName: node + linkType: hard + +"sourcemap-codec@npm:^1.4.8": + version: 1.4.8 + resolution: "sourcemap-codec@npm:1.4.8" + checksum: b57981c05611afef31605732b598ccf65124a9fcb03b833532659ac4d29ac0f7bfacbc0d6c5a28a03e84c7510e7e556d758d0bb57786e214660016fb94279316 + languageName: node + linkType: hard + +"split-on-first@npm:^1.0.0": + version: 1.1.0 + resolution: "split-on-first@npm:1.1.0" + checksum: 16ff85b54ddcf17f9147210a4022529b343edbcbea4ce977c8f30e38408b8d6e0f25f92cd35b86a524d4797f455e29ab89eb8db787f3c10708e0b47ebf528d30 + languageName: node + linkType: hard + +"ssri@npm:^9.0.0": + version: 9.0.1 + resolution: "ssri@npm:9.0.1" + dependencies: + minipass: ^3.1.1 + checksum: fb58f5e46b6923ae67b87ad5ef1c5ab6d427a17db0bead84570c2df3cd50b4ceb880ebdba2d60726588272890bae842a744e1ecce5bd2a2a582fccd5068309eb + languageName: node + linkType: hard + +"stable@npm:^0.1.8": + version: 0.1.8 + resolution: "stable@npm:0.1.8" + checksum: 2ff482bb100285d16dd75cd8f7c60ab652570e8952c0bfa91828a2b5f646a0ff533f14596ea4eabd48bb7f4aeea408dce8f8515812b975d958a4cc4fa6b9dfeb + languageName: node + linkType: hard + +"strict-uri-encode@npm:^2.0.0": + version: 2.0.0 + resolution: "strict-uri-encode@npm:2.0.0" + checksum: eaac4cf978b6fbd480f1092cab8b233c9b949bcabfc9b598dd79a758f7243c28765ef7639c876fa72940dac687181b35486ea01ff7df3e65ce3848c64822c581 + languageName: node + linkType: hard + +"string-width@npm:^1.0.2 || 2 || 3 || 4, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": + version: 4.2.3 + resolution: "string-width@npm:4.2.3" + dependencies: + emoji-regex: ^8.0.0 + is-fullwidth-code-point: ^3.0.0 + strip-ansi: ^6.0.1 + checksum: e52c10dc3fbfcd6c3a15f159f54a90024241d0f149cf8aed2982a2d801d2e64df0bf1dc351cf8e95c3319323f9f220c16e740b06faecd53e2462df1d2b5443fb + languageName: node + linkType: hard + +"string.prototype.trimend@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimend@npm:1.0.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 0fdc34645a639bd35179b5a08227a353b88dc089adf438f46be8a7c197fc3f22f8514c1c9be4629b3cd29c281582730a8cbbad6466c60f76b5f99cf2addb132e + languageName: node + linkType: hard + +"string.prototype.trimstart@npm:^1.0.6": + version: 1.0.6 + resolution: "string.prototype.trimstart@npm:1.0.6" + dependencies: + call-bind: ^1.0.2 + define-properties: ^1.1.4 + es-abstract: ^1.20.4 + checksum: 89080feef416621e6ef1279588994305477a7a91648d9436490d56010a1f7adc39167cddac7ce0b9884b8cdbef086987c4dcb2960209f2af8bac0d23ceff4f41 + languageName: node + linkType: hard + +"string_decoder@npm:^1.1.1": + version: 1.3.0 + resolution: "string_decoder@npm:1.3.0" + dependencies: + safe-buffer: ~5.2.0 + checksum: 8417646695a66e73aefc4420eb3b84cc9ffd89572861fe004e6aeb13c7bc00e2f616247505d2dbbef24247c372f70268f594af7126f43548565c68c117bdeb56 + languageName: node + linkType: hard + +"strip-ansi@npm:^6.0.0, strip-ansi@npm:^6.0.1": + version: 6.0.1 + resolution: "strip-ansi@npm:6.0.1" + dependencies: + ansi-regex: ^5.0.1 + checksum: f3cd25890aef3ba6e1a74e20896c21a46f482e93df4a06567cebf2b57edabb15133f1f94e57434e0a958d61186087b1008e89c94875d019910a213181a14fc8c + languageName: node + linkType: hard + +"strip-bom@npm:^3.0.0": + version: 3.0.0 + resolution: "strip-bom@npm:3.0.0" + checksum: 8d50ff27b7ebe5ecc78f1fe1e00fcdff7af014e73cf724b46fb81ef889eeb1015fc5184b64e81a2efe002180f3ba431bdd77e300da5c6685d702780fbf0c8d5b + languageName: node + linkType: hard + +"strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": + version: 3.1.1 + resolution: "strip-json-comments@npm:3.1.1" + checksum: 492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 + languageName: node + linkType: hard + +"stylehacks@npm:^5.1.1": + version: 5.1.1 + resolution: "stylehacks@npm:5.1.1" + dependencies: + browserslist: ^4.21.4 + postcss-selector-parser: ^6.0.4 + peerDependencies: + postcss: ^8.2.15 + checksum: 11175366ef52de65bf06cefba0ddc9db286dc3a1451fd2989e74c6ea47091a02329a4bf6ce10b1a36950056927b6bbbe47c5ab3a1f4c7032df932d010fbde5a2 + languageName: node + linkType: hard + +"superstruct@npm:^0.15.3": + version: 0.15.5 + resolution: "superstruct@npm:0.15.5" + checksum: 6d1f5249fee789424b7178fa0a1ffb2ace629c5480c39505885bd8c0046a4ff8b267569a3442fa53b8c560a7ba6599cf3f8af94225aebeb2cf6023f7dd911050 + languageName: node + linkType: hard + +"supports-color@npm:^5.3.0": + version: 5.5.0 + resolution: "supports-color@npm:5.5.0" + dependencies: + has-flag: ^3.0.0 + checksum: 95f6f4ba5afdf92f495b5a912d4abee8dcba766ae719b975c56c084f5004845f6f5a5f7769f52d53f40e21952a6d87411bafe34af4a01e65f9926002e38e1dac + languageName: node + linkType: hard + +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": + version: 7.2.0 + resolution: "supports-color@npm:7.2.0" + dependencies: + has-flag: ^4.0.0 + checksum: 3dda818de06ebbe5b9653e07842d9479f3555ebc77e9a0280caf5a14fb877ffee9ed57007c3b78f5a6324b8dbeec648d9e97a24e2ed9fdb81ddc69ea07100f4a + languageName: node + linkType: hard + +"supports-preserve-symlinks-flag@npm:^1.0.0": + version: 1.0.0 + resolution: "supports-preserve-symlinks-flag@npm:1.0.0" + checksum: 53b1e247e68e05db7b3808b99b892bd36fb096e6fba213a06da7fab22045e97597db425c724f2bbd6c99a3c295e1e73f3e4de78592289f38431049e1277ca0ae + languageName: node + linkType: hard + +"svgo@npm:^2.7.0": + version: 2.8.0 + resolution: "svgo@npm:2.8.0" + dependencies: + "@trysound/sax": 0.2.0 + commander: ^7.2.0 + css-select: ^4.1.3 + css-tree: ^1.1.3 + csso: ^4.2.0 + picocolors: ^1.0.0 + stable: ^0.1.8 + bin: + svgo: bin/svgo + checksum: b92f71a8541468ffd0b81b8cdb36b1e242eea320bf3c1a9b2c8809945853e9d8c80c19744267eb91cabf06ae9d5fff3592d677df85a31be4ed59ff78534fa420 + languageName: node + linkType: hard + +"tar@npm:^6.1.11, tar@npm:^6.1.2": + version: 6.1.13 + resolution: "tar@npm:6.1.13" + dependencies: + chownr: ^2.0.0 + fs-minipass: ^2.0.0 + minipass: ^4.0.0 + minizlib: ^2.1.1 + mkdirp: ^1.0.3 + yallist: ^4.0.0 + checksum: 8a278bed123aa9f53549b256a36b719e317c8b96fe86a63406f3c62887f78267cea9b22dc6f7007009738509800d4a4dccc444abd71d762287c90f35b002eb1c + languageName: node + linkType: hard + +"terser@npm:^5.0.0": + version: 5.16.3 + resolution: "terser@npm:5.16.3" + dependencies: + "@jridgewell/source-map": ^0.3.2 + acorn: ^8.5.0 + commander: ^2.20.0 + source-map-support: ~0.5.20 + bin: + terser: bin/terser + checksum: d3c2ac1c2723c37b698b25b68d76fd315a1277fddde113983d5783d1f2a01dd7b8ed83ba3f54e5e65f0b59dd971ed7be2fdf8d4be94ec694b2d27832d2e7561f + languageName: node + linkType: hard + +"text-table@npm:^0.2.0": + version: 0.2.0 + resolution: "text-table@npm:0.2.0" + checksum: b6937a38c80c7f84d9c11dd75e49d5c44f71d95e810a3250bd1f1797fc7117c57698204adf676b71497acc205d769d65c16ae8fa10afad832ae1322630aef10a + languageName: node + linkType: hard + +"to-fast-properties@npm:^2.0.0": + version: 2.0.0 + resolution: "to-fast-properties@npm:2.0.0" + checksum: be2de62fe58ead94e3e592680052683b1ec986c72d589e7b21e5697f8744cdbf48c266fa72f6c15932894c10187b5f54573a3bcf7da0bfd964d5caf23d436168 + languageName: node + linkType: hard + +"to-regex-range@npm:^5.0.1": + version: 5.0.1 + resolution: "to-regex-range@npm:5.0.1" + dependencies: + is-number: ^7.0.0 + checksum: f76fa01b3d5be85db6a2a143e24df9f60dd047d151062d0ba3df62953f2f697b16fe5dad9b0ac6191c7efc7b1d9dcaa4b768174b7b29da89d4428e64bc0a20ed + languageName: node + linkType: hard + +"true-myth@npm:^4.1.0": + version: 4.1.1 + resolution: "true-myth@npm:4.1.1" + checksum: 0b937bb215b78d0ccff8637ac1c49be7f08c04f32a7b755c45a1566a1d522f50b0cadac93ad869b6bea9b843f60479160d3382c2c91d2381ef3559915564ff82 + languageName: node + linkType: hard + +"ts-morph@npm:^13.0.1": + version: 13.0.3 + resolution: "ts-morph@npm:13.0.3" + dependencies: + "@ts-morph/common": ~0.12.3 + code-block-writer: ^11.0.0 + checksum: 0983a7c4e0e1063e3900186bf2e85c5080621e31b754434136d2130a82bda6556985fdf012b766a87b9ae47d6f63732a562bb80854c34644f9352bea9a35b8ee + languageName: node + linkType: hard + +"ts-prune@npm:^0.10.3": + version: 0.10.3 + resolution: "ts-prune@npm:0.10.3" + dependencies: + commander: ^6.2.1 + cosmiconfig: ^7.0.1 + json5: ^2.1.3 + lodash: ^4.17.21 + true-myth: ^4.1.0 + ts-morph: ^13.0.1 + bin: + ts-prune: lib/index.js + checksum: 2cbc4d64a2e50637d06691e7f962b913d87aba2419ba772a3a1cb6e4b7542412cd77af592ba7b376ed273f5e37d9be180613b479602b9f92693e4d72f0f5a373 + languageName: node + linkType: hard + +"ts-toolbelt@npm:^9.6.0": + version: 9.6.0 + resolution: "ts-toolbelt@npm:9.6.0" + checksum: 9f35fd95d895a5d32ea9fd2e532a695b0bae6cbff6832b77292efa188a0ed1ed6e54f63f74a8920390f3d909a7a3adb20a144686372a8e78b420246a9bd3d58a + languageName: node + linkType: hard + +"tsconfig-paths@npm:^3.14.1": + version: 3.14.1 + resolution: "tsconfig-paths@npm:3.14.1" + dependencies: + "@types/json5": ^0.0.29 + json5: ^1.0.1 + minimist: ^1.2.6 + strip-bom: ^3.0.0 + checksum: 8afa01c673ebb4782ba53d3a12df97fa837ce524f8ad38ee4e2b2fd57f5ac79abc21c574e9e9eb014d93efe7fe8214001b96233b5c6ea75bd1ea82afe17a4c6d + languageName: node + linkType: hard + +"tslib@npm:^1.8.1": + version: 1.14.1 + resolution: "tslib@npm:1.14.1" + checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd + languageName: node + linkType: hard + +"tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0": + version: 2.5.0 + resolution: "tslib@npm:2.5.0" + checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + languageName: node + linkType: hard + +"tsutils@npm:^3.21.0": + version: 3.21.0 + resolution: "tsutils@npm:3.21.0" + dependencies: + tslib: ^1.8.1 + peerDependencies: + typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + checksum: 1843f4c1b2e0f975e08c4c21caa4af4f7f65a12ac1b81b3b8489366826259323feb3fc7a243123453d2d1a02314205a7634e048d4a8009921da19f99755cdc48 + languageName: node + linkType: hard + +"type-check@npm:^0.4.0, type-check@npm:~0.4.0": + version: 0.4.0 + resolution: "type-check@npm:0.4.0" + dependencies: + prelude-ls: ^1.2.1 + checksum: ec688ebfc9c45d0c30412e41ca9c0cdbd704580eb3a9ccf07b9b576094d7b86a012baebc95681999dd38f4f444afd28504cb3a89f2ef16b31d4ab61a0739025a + languageName: node + linkType: hard + +"type-fest@npm:^0.20.2": + version: 0.20.2 + resolution: "type-fest@npm:0.20.2" + checksum: 4fb3272df21ad1c552486f8a2f8e115c09a521ad7a8db3d56d53718d0c907b62c6e9141ba5f584af3f6830d0872c521357e512381f24f7c44acae583ad517d73 + languageName: node + linkType: hard + +"typed-array-length@npm:^1.0.4": + version: 1.0.4 + resolution: "typed-array-length@npm:1.0.4" + dependencies: + call-bind: ^1.0.2 + for-each: ^0.3.3 + is-typed-array: ^1.1.9 + checksum: 2228febc93c7feff142b8c96a58d4a0d7623ecde6c7a24b2b98eb3170e99f7c7eff8c114f9b283085cd59dcd2bd43aadf20e25bba4b034a53c5bb292f71f8956 + languageName: node + linkType: hard + +"typescript@npm:^4.5.4, typescript@npm:^4.8.3": + version: 4.9.5 + resolution: "typescript@npm:4.9.5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: ee000bc26848147ad423b581bd250075662a354d84f0e06eb76d3b892328d8d4440b7487b5a83e851b12b255f55d71835b008a66cbf8f255a11e4400159237db + languageName: node + linkType: hard + +"typescript@patch:typescript@^4.5.4#~builtin, typescript@patch:typescript@^4.8.3#~builtin": + version: 4.9.5 + resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=23ec76" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: ab417a2f398380c90a6cf5a5f74badd17866adf57f1165617d6a551f059c3ba0a3e4da0d147b3ac5681db9ac76a303c5876394b13b3de75fdd5b1eaa06181c9d + languageName: node + linkType: hard + +"unbox-primitive@npm:^1.0.2": + version: 1.0.2 + resolution: "unbox-primitive@npm:1.0.2" + dependencies: + call-bind: ^1.0.2 + has-bigints: ^1.0.2 + has-symbols: ^1.0.3 + which-boxed-primitive: ^1.0.2 + checksum: b7a1cf5862b5e4b5deb091672ffa579aa274f648410009c81cca63fed3b62b610c4f3b773f912ce545bb4e31edc3138975b5bc777fc6e4817dca51affb6380e9 + languageName: node + linkType: hard + +"unique-filename@npm:^2.0.0": + version: 2.0.1 + resolution: "unique-filename@npm:2.0.1" + dependencies: + unique-slug: ^3.0.0 + checksum: 807acf3381aff319086b64dc7125a9a37c09c44af7620bd4f7f3247fcd5565660ac12d8b80534dcbfd067e6fe88a67e621386dd796a8af828d1337a8420a255f + languageName: node + linkType: hard + +"unique-slug@npm:^3.0.0": + version: 3.0.0 + resolution: "unique-slug@npm:3.0.0" + dependencies: + imurmurhash: ^0.1.4 + checksum: 49f8d915ba7f0101801b922062ee46b7953256c93ceca74303bd8e6413ae10aa7e8216556b54dc5382895e8221d04f1efaf75f945c2e4a515b4139f77aa6640c + languageName: node + linkType: hard + +"universalify@npm:^2.0.0": + version: 2.0.0 + resolution: "universalify@npm:2.0.0" + checksum: 2406a4edf4a8830aa6813278bab1f953a8e40f2f63a37873ffa9a3bc8f9745d06cc8e88f3572cb899b7e509013f7f6fcc3e37e8a6d914167a5381d8440518c44 + languageName: node + linkType: hard + +"update-browserslist-db@npm:^1.0.10": + version: 1.0.10 + resolution: "update-browserslist-db@npm:1.0.10" + dependencies: + escalade: ^3.1.1 + picocolors: ^1.0.0 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + browserslist-lint: cli.js + checksum: 12db73b4f63029ac407b153732e7cd69a1ea8206c9100b482b7d12859cd3cd0bc59c602d7ae31e652706189f1acb90d42c53ab24a5ba563ed13aebdddc5561a0 + languageName: node + linkType: hard + +"uri-js@npm:^4.2.2": + version: 4.4.1 + resolution: "uri-js@npm:4.4.1" + dependencies: + punycode: ^2.1.0 + checksum: 7167432de6817fe8e9e0c9684f1d2de2bb688c94388f7569f7dbdb1587c9f4ca2a77962f134ec90be0cc4d004c939ff0d05acc9f34a0db39a3c797dada262633 + languageName: node + linkType: hard + +"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2": + version: 1.0.2 + resolution: "util-deprecate@npm:1.0.2" + checksum: 474acf1146cb2701fe3b074892217553dfcf9a031280919ba1b8d651a068c9b15d863b7303cb15bd00a862b498e6cf4ad7b4a08fb134edd5a6f7641681cb54a2 + languageName: node + linkType: hard + +"uuid@npm:^8.3.2": + version: 8.3.2 + resolution: "uuid@npm:8.3.2" + bin: + uuid: dist/bin/uuid + checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df + languageName: node + linkType: hard + +"vis-data@npm:^7.1.4": + version: 7.1.4 + resolution: "vis-data@npm:7.1.4" + peerDependencies: + uuid: ^7.0.0 || ^8.0.0 + vis-util: ^4.0.0 || ^5.0.0 + checksum: a1266eb678b7f75c422c070ebd579a99565de9b23deba2e75862afb9c15ef5fa67e34f002c8c7013751100c75d6fb76b25bdb9f1a309d9f0cda1bec0935b26ff + languageName: node + linkType: hard + +"vis-timeline@npm:^7.7.0": + version: 7.7.0 + resolution: "vis-timeline@npm:7.7.0" + peerDependencies: + "@egjs/hammerjs": ^2.0.0 + component-emitter: ^1.3.0 + keycharm: ^0.3.0 || ^0.4.0 + moment: ^2.24.0 + propagating-hammerjs: ^1.4.0 || ^2.0.0 + uuid: ^3.4.0 || ^7.0.0 || ^8.0.0 + vis-data: ^6.3.0 || ^7.0.0 + vis-util: ^3.0.0 || ^4.0.0 || ^5.0.0 + xss: ^1.0.0 + checksum: 07d8618b7cf63ea64870c1404fb852d7b6e58df1b9fa429564225bd7a8f7f6ca047c109cee13c1501ee514abfed176699434be3f4fcf620731ca94aee525f8f8 + languageName: node + linkType: hard + +"vis-util@npm:^5.0.2": + version: 5.0.3 + resolution: "vis-util@npm:5.0.3" + peerDependencies: + "@egjs/hammerjs": ^2.0.0 + component-emitter: ^1.3.0 + checksum: a6a809cca36fdebd2c9b06038c30980b21c346e10eba8b30b4cfac1495751adee8f7f5a10ca1842b352b3c1eb3051abc669dc8cf204ad391c52f8165b0ca274b + languageName: node + linkType: hard + +"wheel-gestures@npm:^2.2.5": + version: 2.2.5 + resolution: "wheel-gestures@npm:2.2.5" + checksum: d382de6d4e49ad753dfb22f49000923b30dba1b622ec80821435d1097af63cdf6a1904e2ab1963a3f580d7522d7ba6de02976233eeb99e387d9cfdbeb8a0f9bf + languageName: node + linkType: hard + +"which-boxed-primitive@npm:^1.0.2": + version: 1.0.2 + resolution: "which-boxed-primitive@npm:1.0.2" + dependencies: + is-bigint: ^1.0.1 + is-boolean-object: ^1.1.0 + is-number-object: ^1.0.4 + is-string: ^1.0.5 + is-symbol: ^1.0.3 + checksum: 53ce774c7379071729533922adcca47220228405e1895f26673bbd71bdf7fb09bee38c1d6399395927c6289476b5ae0629863427fd151491b71c4b6cb04f3a5e + languageName: node + linkType: hard + +"which-typed-array@npm:^1.1.9": + version: 1.1.9 + resolution: "which-typed-array@npm:1.1.9" + dependencies: + available-typed-arrays: ^1.0.5 + call-bind: ^1.0.2 + for-each: ^0.3.3 + gopd: ^1.0.1 + has-tostringtag: ^1.0.0 + is-typed-array: ^1.1.10 + checksum: fe0178ca44c57699ca2c0e657b64eaa8d2db2372a4e2851184f568f98c478ae3dc3fdb5f7e46c384487046b0cf9e23241423242b277e03e8ba3dabc7c84c98ef + languageName: node + linkType: hard + +"which@npm:^2.0.1, which@npm:^2.0.2": + version: 2.0.2 + resolution: "which@npm:2.0.2" + dependencies: + isexe: ^2.0.0 + bin: + node-which: ./bin/node-which + checksum: 1a5c563d3c1b52d5f893c8b61afe11abc3bab4afac492e8da5bde69d550de701cf9806235f20a47b5c8fa8a1d6a9135841de2596535e998027a54589000e66d1 + languageName: node + linkType: hard + +"wide-align@npm:^1.1.5": + version: 1.1.5 + resolution: "wide-align@npm:1.1.5" + dependencies: + string-width: ^1.0.2 || 2 || 3 || 4 + checksum: d5fc37cd561f9daee3c80e03b92ed3e84d80dde3365a8767263d03dacfc8fa06b065ffe1df00d8c2a09f731482fcacae745abfbb478d4af36d0a891fad4834d3 + languageName: node + linkType: hard + +"word-wrap@npm:^1.2.3": + version: 1.2.3 + resolution: "word-wrap@npm:1.2.3" + checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f + languageName: node + linkType: hard + +"wrap-ansi@npm:^7.0.0": + version: 7.0.0 + resolution: "wrap-ansi@npm:7.0.0" + dependencies: + ansi-styles: ^4.0.0 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + checksum: a790b846fd4505de962ba728a21aaeda189b8ee1c7568ca5e817d85930e06ef8d1689d49dbf0e881e8ef84436af3a88bc49115c2e2788d841ff1b8b5b51a608b + languageName: node + linkType: hard + +"wrappy@npm:1": + version: 1.0.2 + resolution: "wrappy@npm:1.0.2" + checksum: 159da4805f7e84a3d003d8841557196034155008f817172d4e986bd591f74aa82aa7db55929a54222309e01079a65a92a9e6414da5a6aa4b01ee44a511ac3ee5 + languageName: node + linkType: hard + +"xss@npm:^1.0.14": + version: 1.0.14 + resolution: "xss@npm:1.0.14" + dependencies: + commander: ^2.20.3 + cssfilter: 0.0.10 + bin: + xss: bin/xss + checksum: 77c6a60a5f96490098ef837c3b49321c0f31963f323c748cb0a01bc02fc6e465a6accbe4c54cad62f8d6ffe647d984be173879433af861883f6213d9416d3f53 + languageName: node + linkType: hard + +"y18n@npm:^5.0.5": + version: 5.0.8 + resolution: "y18n@npm:5.0.8" + checksum: 54f0fb95621ee60898a38c572c515659e51cc9d9f787fb109cef6fde4befbe1c4602dc999d30110feee37456ad0f1660fa2edcfde6a9a740f86a290999550d30 + languageName: node + linkType: hard + +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 48f7bb00dc19fc635a13a39fe547f527b10c9290e7b3e836b9a8f1ca04d4d342e85714416b3c2ab74949c9c66f9cebb0473e6bc353b79035356103b47641285d + languageName: node + linkType: hard + +"yallist@npm:^4.0.0": + version: 4.0.0 + resolution: "yallist@npm:4.0.0" + checksum: 343617202af32df2a15a3be36a5a8c0c8545208f3d3dfbc6bb7c3e3b7e8c6f8e7485432e4f3b88da3031a6e20afa7c711eded32ddfb122896ac5d914e75848d5 + languageName: node + linkType: hard + +"yaml@npm:^1.10.0, yaml@npm:^1.10.2": + version: 1.10.2 + resolution: "yaml@npm:1.10.2" + checksum: ce4ada136e8a78a0b08dc10b4b900936912d15de59905b2bf415b4d33c63df1d555d23acb2a41b23cf9fb5da41c256441afca3d6509de7247daa062fd2c5ea5f + languageName: node + linkType: hard + +"yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c + languageName: node + linkType: hard + +"yargs@npm:^17.5.1": + version: 17.6.2 + resolution: "yargs@npm:17.6.2" + dependencies: + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 47da1b0d854fa16d45a3ded57b716b013b2179022352a5f7467409da5a04a1eef5b3b3d97a2dfc13e8bbe5f2ffc0afe3bc6a4a72f8254e60f5a4bd7947138643 + languageName: node + linkType: hard + +"yocto-queue@npm:^0.1.0": + version: 0.1.0 + resolution: "yocto-queue@npm:0.1.0" + checksum: f77b3d8d00310def622123df93d4ee654fc6a0096182af8bd60679ddcdfb3474c56c6c7190817c84a2785648cdee9d721c0154eb45698c62176c322fb46fc700 + languageName: node + linkType: hard + +"zod@npm:^3.20.2": + version: 3.20.2 + resolution: "zod@npm:3.20.2" + checksum: 04172f7e9350372684ccd298d4716908edc9113751295b6c4e1b3ea84e2af8997e504b33ba36f4741417bb2a5dc90bfd40501f6b0e7389df10e42a63d6d8366c + languageName: node + linkType: hard