mirror of
https://github.com/home-assistant/frontend.git
synced 2026-08-09 16:08:11 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b9b6f4942f |
@@ -32,12 +32,12 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
uses: github/codeql-action/init@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||
with:
|
||||
languages: javascript-typescript
|
||||
build-mode: none
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4
|
||||
uses: github/codeql-action/analyze@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v4.37.3
|
||||
with:
|
||||
category: "/language:javascript-typescript"
|
||||
|
||||
@@ -18,6 +18,6 @@ jobs:
|
||||
pull-requests: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: release-drafter/release-drafter@34d80673e067bdc0c24568d3af899c216adcfaa9 # v7.7.0
|
||||
- uses: release-drafter/release-drafter@eada3c96a64734dd381cfbda23511034e328ddb0 # v7.6.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Verify version
|
||||
uses: home-assistant/actions/helpers/verify-version@a7c616ce81ccda50150bf1595786c71b1883fabb # master
|
||||
uses: home-assistant/actions/helpers/verify-version@ab22029681aa532bfe7de5774a9972d67bfbd2c0 # master
|
||||
|
||||
- name: Setup Node and install
|
||||
uses: ./.github/actions/setup
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
script/release
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
||||
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
||||
with:
|
||||
skip-existing: true
|
||||
|
||||
@@ -76,21 +76,19 @@ jobs:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
run: |
|
||||
version=$(echo "$GITHUB_REF" | awk -F"/" '{print $NF}' )
|
||||
# Wait for the exact wheel to appear on the simple index (the surface
|
||||
# the wheels build's pip uses). The JSON API can report a version as
|
||||
# available before it propagates here, which fails the wheels build.
|
||||
wheel="home_assistant_frontend-${version}-py3-none-any.whl"
|
||||
echo "Waiting for $wheel to appear on the PyPI simple index..."
|
||||
# Wait for the package to become available on PyPI
|
||||
echo "Waiting for home-assistant-frontend==$version to appear on PyPI..."
|
||||
for i in $(seq 1 30); do
|
||||
if curl -sf "https://pypi.org/simple/home-assistant-frontend/" | grep -qF "$wheel"; then
|
||||
echo "Package is available on the PyPI simple index!"
|
||||
status=$(curl -s -o /dev/null -w "%{http_code}" "https://pypi.org/pypi/home-assistant-frontend/$version/json")
|
||||
if [ "$status" = "200" ]; then
|
||||
echo "Package is available on PyPI!"
|
||||
break
|
||||
fi
|
||||
if [ "$i" = "30" ]; then
|
||||
echo "Timed out waiting for package to appear on PyPI"
|
||||
exit 1
|
||||
fi
|
||||
echo "Not available yet, retrying in 30 seconds... ($i/30)"
|
||||
echo "Not available yet (HTTP $status), retrying in 30 seconds... ($i/30)"
|
||||
sleep 30
|
||||
done
|
||||
echo "home-assistant-frontend==$version" > ./requirements.txt
|
||||
|
||||
@@ -15,7 +15,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 90 days stale policy
|
||||
uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
|
||||
uses: actions/stale@1e223db275d687790206a7acac4d1a11bd6fe629 # v10.4.0
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
days-before-stale: 90
|
||||
|
||||
@@ -228,7 +228,6 @@ export default [
|
||||
"entity-state",
|
||||
"ha-markdown",
|
||||
"integration-card",
|
||||
"cloud-account",
|
||||
"box-shadow",
|
||||
"util-long-press",
|
||||
"remove-delete-add-create",
|
||||
|
||||
@@ -106,17 +106,6 @@ export class DemoHaSelectBox extends LitElement {
|
||||
</ha-card>
|
||||
`;
|
||||
})}
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<label>Disabled with a selected option</label>
|
||||
<ha-select-box
|
||||
.value=${"card"}
|
||||
.options=${fullOptions}
|
||||
.disabled=${true}
|
||||
>
|
||||
</ha-select-box>
|
||||
</div>
|
||||
</ha-card>
|
||||
<ha-card>
|
||||
<div class="card-content">
|
||||
<p class="title"><b>Column layout</b></p>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
title: Cloud account
|
||||
---
|
||||
|
||||
The [Home Assistant Cloud](https://www.nabucasa.com/) account page, rendered from
|
||||
mocked cloud data. The controls at the top flip the mocked subscription, remote,
|
||||
backup, onboarding, and feature state so every UI state can be previewed here
|
||||
without a real cloud account.
|
||||
@@ -1,568 +0,0 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import type { HASSDomEvent } from "../../../../src/common/dom/fire_event";
|
||||
import "../../../../src/components/ha-formfield";
|
||||
import "../../../../src/components/ha-select";
|
||||
import type { HaSelectSelectEvent } from "../../../../src/components/ha-select";
|
||||
import "../../../../src/components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../src/components/ha-switch";
|
||||
import type { BackupConfig } from "../../../../src/data/backup";
|
||||
import { BackupScheduleRecurrence } from "../../../../src/data/backup";
|
||||
import type {
|
||||
CloudStatusLoggedIn,
|
||||
RemoteCertificateStatus,
|
||||
SubscriptionInfo,
|
||||
} from "../../../../src/data/cloud";
|
||||
import { ONBOARDING_ITEMS } from "../../../../src/data/cloud";
|
||||
import type { Webhook } from "../../../../src/data/webhook";
|
||||
import type { ShowDialogParams } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import { showDialog } from "../../../../src/dialogs/make-dialog-manager";
|
||||
import type { MockHomeAssistant } from "../../../../src/fake_data/provide_hass";
|
||||
import { provideHass } from "../../../../src/fake_data/provide_hass";
|
||||
import type { ProvideHassElement } from "../../../../src/mixins/provide-hass-lit-mixin";
|
||||
import "../../../../src/panels/config/cloud/account/cloud-account-onboarding";
|
||||
import "../../../../src/panels/config/cloud/account/cloud-account-overview";
|
||||
import { onboardingComplete } from "../../../../src/panels/config/cloud/account/cloud-account-status";
|
||||
import { showCloudOnboardingDialog } from "../../../../src/panels/config/cloud/account/show-dialog-cloud-onboarding";
|
||||
import type { HomeAssistant } from "../../../../src/types";
|
||||
|
||||
// This demo renders the self-contained cloud account cards (overview +
|
||||
// onboarding) directly, driven by mocked data, with controls to flip the
|
||||
// subscription, remote, backup, onboarding, and feature state so every UI state
|
||||
// can be previewed. It intentionally does NOT render the <cloud-account> panel
|
||||
// wrapper, which adds a hass-subpage toolbar/back button and route links that
|
||||
// have no home in the gallery. See src/panels/config/cloud/account.
|
||||
|
||||
// The five PaymentSubscriptionState values.
|
||||
type DemoCloudAccount =
|
||||
"active" | "trialing" | "canceled" | "expired" | "unknown";
|
||||
|
||||
// "local": automatic backups run, but only to the local agent (no cloud copy).
|
||||
// "none": no automatic backups at all.
|
||||
type DemoCloudBackup = "fresh" | "stale" | "failed" | "local" | "none";
|
||||
|
||||
interface CloudDemoScenario {
|
||||
account: DemoCloudAccount;
|
||||
onboarded: boolean;
|
||||
postponed: boolean;
|
||||
remote: boolean;
|
||||
remoteStatus: RemoteCertificateStatus;
|
||||
backup: DemoCloudBackup;
|
||||
alexa: boolean;
|
||||
google: boolean;
|
||||
webrtc: boolean;
|
||||
webhooks: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_SCENARIO: CloudDemoScenario = {
|
||||
account: "active",
|
||||
// Onboarding not completed and streaming (WebRTC) left off, so the onboarding
|
||||
// card shows by default with streaming as the remaining step.
|
||||
onboarded: false,
|
||||
postponed: false,
|
||||
remote: true,
|
||||
remoteStatus: "ready",
|
||||
backup: "fresh",
|
||||
alexa: true,
|
||||
google: true,
|
||||
webrtc: false,
|
||||
webhooks: true,
|
||||
};
|
||||
|
||||
const SUBSCRIPTION_OPTIONS: { value: DemoCloudAccount; label: string }[] = [
|
||||
{ value: "active", label: "Active" },
|
||||
{ value: "trialing", label: "Trialing" },
|
||||
{ value: "canceled", label: "Canceled" },
|
||||
{ value: "expired", label: "Expired" },
|
||||
{ value: "unknown", label: "Unknown" },
|
||||
];
|
||||
|
||||
const REMOTE_STATUS_OPTIONS: {
|
||||
value: RemoteCertificateStatus;
|
||||
label: string;
|
||||
}[] = [
|
||||
{ value: "ready", label: "Ready" },
|
||||
{ value: "generating", label: "Preparing" },
|
||||
{ value: "loading", label: "Loading" },
|
||||
{ value: "loaded", label: "Loaded" },
|
||||
{ value: "error", label: "Error" },
|
||||
];
|
||||
|
||||
const BACKUP_OPTIONS: { value: DemoCloudBackup; label: string }[] = [
|
||||
{ value: "fresh", label: "Recent" },
|
||||
{ value: "stale", label: "Old" },
|
||||
{ value: "failed", label: "Failed" },
|
||||
{ value: "local", label: "Local only" },
|
||||
{ value: "none", label: "None" },
|
||||
];
|
||||
|
||||
const TOGGLES: [keyof CloudDemoScenario, string][] = [
|
||||
["onboarded", "Onboarded"],
|
||||
["postponed", "Onboarding postponed"],
|
||||
["remote", "Remote access"],
|
||||
["alexa", "Alexa linked"],
|
||||
["google", "Google linked"],
|
||||
["webrtc", "Cameras (WebRTC)"],
|
||||
["webhooks", "Has webhooks"],
|
||||
];
|
||||
|
||||
const CLOUD_AGENT = "cloud.cloud";
|
||||
|
||||
const emptyFilter = () => ({
|
||||
include_domains: [],
|
||||
include_entities: [],
|
||||
exclude_domains: [],
|
||||
exclude_entities: [],
|
||||
});
|
||||
|
||||
const demoWebhooks: Webhook[] = [
|
||||
{
|
||||
webhook_id: "demo_front_door",
|
||||
domain: "automation",
|
||||
name: "Front door motion",
|
||||
local_only: false,
|
||||
},
|
||||
{
|
||||
webhook_id: "demo_companion_app",
|
||||
domain: "mobile_app",
|
||||
name: "Companion app",
|
||||
local_only: false,
|
||||
},
|
||||
];
|
||||
|
||||
const buildSubscription = (scenario: CloudDemoScenario): SubscriptionInfo => ({
|
||||
human_description: "Demo subscription, renews automatically",
|
||||
provider: "Nabu Casa, Inc.",
|
||||
plan_renewal_date: 4102444800,
|
||||
subscription: { status: scenario.account },
|
||||
});
|
||||
|
||||
const buildCloudStatus = (scenario: CloudDemoScenario): CloudStatusLoggedIn => {
|
||||
const active =
|
||||
scenario.account !== "canceled" && scenario.account !== "expired";
|
||||
const cloudhooks = scenario.webhooks
|
||||
? Object.fromEntries(
|
||||
demoWebhooks.map((webhook) => [
|
||||
webhook.webhook_id,
|
||||
{
|
||||
webhook_id: webhook.webhook_id,
|
||||
cloudhook_id: `demo-${webhook.webhook_id}`,
|
||||
cloudhook_url: `https://hooks.nabu.casa/demo-${webhook.webhook_id}`,
|
||||
managed: false,
|
||||
},
|
||||
])
|
||||
)
|
||||
: {};
|
||||
return {
|
||||
logged_in: true,
|
||||
cloud: "connected",
|
||||
cloud_last_disconnect_reason: null,
|
||||
email: "[email protected]",
|
||||
google_registered: scenario.google,
|
||||
google_entities: emptyFilter(),
|
||||
google_domains: ["light", "switch", "climate", "cover"],
|
||||
alexa_registered: scenario.alexa,
|
||||
alexa_entities: emptyFilter(),
|
||||
remote_domain: "demo-instance.ui.nabu.casa",
|
||||
remote_connected: scenario.remote,
|
||||
remote_certificate: {
|
||||
common_name: "demo-instance.ui.nabu.casa",
|
||||
expire_date: "2099-01-01T00:00:00+00:00",
|
||||
fingerprint: "demodemodemodemodemodemodemodemodemodemodemodemodemo",
|
||||
alternative_names: ["demo-instance.ui.nabu.casa"],
|
||||
},
|
||||
remote_certificate_status: scenario.remoteStatus,
|
||||
http_use_ssl: false,
|
||||
active_subscription: active,
|
||||
onboarding_postponed: scenario.postponed,
|
||||
onboarding_completed: scenario.onboarded,
|
||||
prefs: {
|
||||
google_enabled: scenario.google,
|
||||
alexa_enabled: scenario.alexa,
|
||||
remote_enabled: scenario.remote,
|
||||
remote_allow_remote_enable: true,
|
||||
strict_connection: "disabled",
|
||||
google_secure_devices_pin: undefined,
|
||||
cloudhooks,
|
||||
alexa_report_state: true,
|
||||
google_report_state: true,
|
||||
tts_default_voice: ["en-US", "JennyNeural"],
|
||||
cloud_ice_servers_enabled: scenario.webrtc,
|
||||
onboarded_items: scenario.onboarded ? [...ONBOARDING_ITEMS] : [],
|
||||
onboarding_postponed_until: scenario.postponed
|
||||
? new Date(Date.now() + 24 * 3600 * 1000).toISOString()
|
||||
: null,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const buildBackupConfig = (scenario: CloudDemoScenario): BackupConfig => {
|
||||
const now = Date.now();
|
||||
const recent = new Date(now - 12 * 3600 * 1000).toISOString();
|
||||
const old = new Date(now - 5 * 86400000).toISOString();
|
||||
const future = new Date(now + 86400000).toISOString();
|
||||
const overdue = new Date(now - 6 * 3600 * 1000).toISOString();
|
||||
|
||||
const cloudEnabled =
|
||||
scenario.backup === "fresh" ||
|
||||
scenario.backup === "stale" ||
|
||||
scenario.backup === "failed";
|
||||
|
||||
let configured = true;
|
||||
let lastCompleted: string | null = null;
|
||||
let lastAttempted: string | null = null;
|
||||
let next: string | null = null;
|
||||
switch (scenario.backup) {
|
||||
case "fresh":
|
||||
case "local":
|
||||
lastCompleted = recent;
|
||||
lastAttempted = recent;
|
||||
next = future;
|
||||
break;
|
||||
case "stale":
|
||||
lastCompleted = old;
|
||||
lastAttempted = old;
|
||||
next = overdue;
|
||||
break;
|
||||
case "failed":
|
||||
lastCompleted = old;
|
||||
lastAttempted = recent;
|
||||
next = future;
|
||||
break;
|
||||
case "none":
|
||||
configured = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
automatic_backups_configured: configured,
|
||||
last_attempted_automatic_backup: lastAttempted,
|
||||
last_completed_automatic_backup: lastCompleted,
|
||||
next_automatic_backup: next,
|
||||
next_automatic_backup_additional: false,
|
||||
create_backup: {
|
||||
agent_ids: cloudEnabled
|
||||
? ["backup.local", CLOUD_AGENT]
|
||||
: ["backup.local"],
|
||||
include_addons: [],
|
||||
include_all_addons: true,
|
||||
include_database: true,
|
||||
include_folders: [],
|
||||
name: null,
|
||||
password: null,
|
||||
},
|
||||
retention: { copies: 3, days: null },
|
||||
schedule: {
|
||||
recurrence: BackupScheduleRecurrence.DAILY,
|
||||
time: null,
|
||||
days: [],
|
||||
},
|
||||
agents: {
|
||||
"backup.local": { protected: true, retention: null },
|
||||
"cloud.cloud": { protected: true, retention: null },
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@customElement("demo-misc-cloud-account")
|
||||
export class DemoMiscCloudAccount
|
||||
extends LitElement
|
||||
implements ProvideHassElement
|
||||
{
|
||||
@state() private hass!: HomeAssistant;
|
||||
|
||||
@state() private _scenario: CloudDemoScenario = { ...DEFAULT_SCENARIO };
|
||||
|
||||
@state() private _cloudStatus!: CloudStatusLoggedIn;
|
||||
|
||||
@state() private _subscription!: SubscriptionInfo;
|
||||
|
||||
@state() private _backupConfig!: BackupConfig;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
const hass = provideHass(this);
|
||||
hass.updateTranslations(null, "en");
|
||||
hass.updateTranslations("config", "en");
|
||||
|
||||
hass.updateHass({
|
||||
config: {
|
||||
...hass.config,
|
||||
components: [
|
||||
...(hass.config?.components ?? []),
|
||||
"cloud",
|
||||
"backup",
|
||||
"webhook",
|
||||
],
|
||||
},
|
||||
});
|
||||
this._registerMocks(hass);
|
||||
this._applyScenario();
|
||||
}
|
||||
|
||||
public provideHass(el) {
|
||||
el.hass = this.hass;
|
||||
}
|
||||
|
||||
public connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.addEventListener("show-dialog", this._showDialog);
|
||||
this.addEventListener("cloud-open-onboarding", this._openOnboarding);
|
||||
this.addEventListener("ha-refresh-cloud-status", this._refreshFromMocks);
|
||||
// The overview and onboarding dialog contain real <a href="/config/..">
|
||||
// links to panel routes that do not exist in the gallery. Keep them inert.
|
||||
this.addEventListener("click", this._neutralizeNavigation);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener("show-dialog", this._showDialog);
|
||||
this.removeEventListener("cloud-open-onboarding", this._openOnboarding);
|
||||
this.removeEventListener("ha-refresh-cloud-status", this._refreshFromMocks);
|
||||
this.removeEventListener("click", this._neutralizeNavigation);
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this.hass) {
|
||||
return nothing;
|
||||
}
|
||||
const showOnboarding =
|
||||
this._cloudStatus.active_subscription &&
|
||||
!this._cloudStatus.onboarding_completed &&
|
||||
!this._cloudStatus.onboarding_postponed &&
|
||||
!onboardingComplete(this._cloudStatus, this._backupConfig);
|
||||
|
||||
return html`
|
||||
<div class="options">
|
||||
<div class="selects">
|
||||
${this._select("Subscription", "account", SUBSCRIPTION_OPTIONS)}
|
||||
${this._select("Remote status", "remoteStatus", REMOTE_STATUS_OPTIONS)}
|
||||
${this._select("Backups", "backup", BACKUP_OPTIONS)}
|
||||
</div>
|
||||
<div class="switches">
|
||||
${TOGGLES.map(([field, label]) => this._toggle(label, field))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview">
|
||||
${
|
||||
showOnboarding
|
||||
? html`
|
||||
<cloud-account-onboarding
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this._cloudStatus}
|
||||
.backupConfig=${this._backupConfig}
|
||||
></cloud-account-onboarding>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<cloud-account-overview
|
||||
.hass=${this.hass}
|
||||
.cloudStatus=${this._cloudStatus}
|
||||
.subscription=${this._subscription}
|
||||
.backupConfig=${this._backupConfig}
|
||||
.webhooks=${demoWebhooks}
|
||||
></cloud-account-overview>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _select(
|
||||
label: string,
|
||||
field: keyof CloudDemoScenario,
|
||||
options: { value: string; label: string }[]
|
||||
) {
|
||||
return html`
|
||||
<ha-select
|
||||
.label=${label}
|
||||
.value=${String(this._scenario[field])}
|
||||
.options=${options}
|
||||
data-field=${field}
|
||||
@selected=${this._selectChanged}
|
||||
></ha-select>
|
||||
`;
|
||||
}
|
||||
|
||||
private _toggle(label: string, field: keyof CloudDemoScenario) {
|
||||
return html`
|
||||
<ha-formfield .label=${label}>
|
||||
<ha-switch
|
||||
.checked=${this._scenario[field] as boolean}
|
||||
data-field=${field}
|
||||
@change=${this._switchChanged}
|
||||
></ha-switch>
|
||||
</ha-formfield>
|
||||
`;
|
||||
}
|
||||
|
||||
private _selectChanged(ev: HaSelectSelectEvent) {
|
||||
const field = (ev.currentTarget as HTMLElement).dataset
|
||||
.field as keyof CloudDemoScenario;
|
||||
this._setField(field, ev.detail.value as string);
|
||||
}
|
||||
|
||||
private _switchChanged(ev: Event) {
|
||||
const target = ev.target as HaSwitch;
|
||||
this._setField(
|
||||
target.dataset.field as keyof CloudDemoScenario,
|
||||
target.checked
|
||||
);
|
||||
}
|
||||
|
||||
private _setField(field: keyof CloudDemoScenario, value: string | boolean) {
|
||||
if (this._scenario[field] === value) {
|
||||
return;
|
||||
}
|
||||
this._scenario = { ...this._scenario, [field]: value };
|
||||
this._applyScenario();
|
||||
}
|
||||
|
||||
private _applyScenario() {
|
||||
this._cloudStatus = buildCloudStatus(this._scenario);
|
||||
this._subscription = buildSubscription(this._scenario);
|
||||
this._backupConfig = buildBackupConfig(this._scenario);
|
||||
}
|
||||
|
||||
private _openOnboarding = () => {
|
||||
showCloudOnboardingDialog(this, {
|
||||
cloudStatus: this._cloudStatus,
|
||||
backupConfig: this._backupConfig,
|
||||
onChanged: () => this._refreshFromMocks(),
|
||||
});
|
||||
};
|
||||
|
||||
private _showDialog = (ev: HASSDomEvent<ShowDialogParams<unknown>>) => {
|
||||
const { dialogTag, dialogImport, dialogParams, addHistory, parentElement } =
|
||||
ev.detail;
|
||||
showDialog(
|
||||
this,
|
||||
dialogTag,
|
||||
dialogParams,
|
||||
dialogImport,
|
||||
parentElement,
|
||||
addHistory
|
||||
);
|
||||
};
|
||||
|
||||
private _refreshFromMocks = () => {
|
||||
this._cloudStatus = {
|
||||
...this._cloudStatus,
|
||||
prefs: { ...this._cloudStatus.prefs },
|
||||
};
|
||||
this._backupConfig = { ...this._backupConfig };
|
||||
const cloudBackup =
|
||||
this._backupConfig.create_backup.agent_ids.includes(CLOUD_AGENT);
|
||||
this._scenario = {
|
||||
...this._scenario,
|
||||
onboarded: this._cloudStatus.onboarding_completed,
|
||||
postponed: this._cloudStatus.onboarding_postponed,
|
||||
remote: this._cloudStatus.prefs.remote_enabled,
|
||||
webrtc: this._cloudStatus.prefs.cloud_ice_servers_enabled,
|
||||
backup: !this._backupConfig.automatic_backups_configured
|
||||
? "none"
|
||||
: cloudBackup
|
||||
? this._scenario.backup === "fresh" ||
|
||||
this._scenario.backup === "stale" ||
|
||||
this._scenario.backup === "failed"
|
||||
? this._scenario.backup
|
||||
: "fresh"
|
||||
: "local",
|
||||
};
|
||||
};
|
||||
|
||||
private _neutralizeNavigation = (ev: MouseEvent) => {
|
||||
const anchor = ev
|
||||
.composedPath()
|
||||
.find((el): el is HTMLAnchorElement => el instanceof HTMLAnchorElement);
|
||||
if (anchor?.getAttribute("href")?.startsWith("/")) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
private _registerMocks(hass: MockHomeAssistant) {
|
||||
hass.mockWS("cloud/status", () => ({
|
||||
...this._cloudStatus,
|
||||
prefs: { ...this._cloudStatus.prefs },
|
||||
}));
|
||||
|
||||
hass.mockWS("cloud/update_prefs", (msg) => {
|
||||
const { type, ...prefs } = msg;
|
||||
this._cloudStatus.prefs = { ...this._cloudStatus.prefs, ...prefs };
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
hass.mockWS("cloud/onboarding/postpone", () => {
|
||||
this._cloudStatus.onboarding_postponed = true;
|
||||
this._cloudStatus.prefs.onboarding_postponed_until = new Date(
|
||||
Date.now() + 24 * 3600 * 1000
|
||||
).toISOString();
|
||||
return { ...this._cloudStatus, prefs: { ...this._cloudStatus.prefs } };
|
||||
});
|
||||
|
||||
hass.mockWS("cloud/remote/connect", () => {
|
||||
this._cloudStatus.remote_connected = true;
|
||||
this._cloudStatus.prefs.remote_enabled = true;
|
||||
return null;
|
||||
});
|
||||
hass.mockWS("cloud/remote/disconnect", () => {
|
||||
this._cloudStatus.remote_connected = false;
|
||||
this._cloudStatus.prefs.remote_enabled = false;
|
||||
return null;
|
||||
});
|
||||
|
||||
hass.mockWS("backup/config/info", () => ({
|
||||
config: { ...this._backupConfig },
|
||||
}));
|
||||
hass.mockWS("backup/config/update", (msg) => {
|
||||
const { type, ...update } = msg;
|
||||
if (update.create_backup) {
|
||||
this._backupConfig.create_backup = {
|
||||
...this._backupConfig.create_backup,
|
||||
...update.create_backup,
|
||||
};
|
||||
}
|
||||
if (update.automatic_backups_configured !== undefined) {
|
||||
this._backupConfig.automatic_backups_configured =
|
||||
update.automatic_backups_configured;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.options {
|
||||
max-width: 600px;
|
||||
margin: 16px auto 0;
|
||||
padding: 0 16px 16px;
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
.selects {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
.selects ha-select {
|
||||
min-width: 160px;
|
||||
flex: 1;
|
||||
}
|
||||
.switches {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 16px;
|
||||
}
|
||||
.preview {
|
||||
padding: 24px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-space-6, 24px);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"demo-misc-cloud-account": DemoMiscCloudAccount;
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -49,7 +49,7 @@
|
||||
"@codemirror/lint": "6.9.7",
|
||||
"@codemirror/search": "6.7.1",
|
||||
"@codemirror/state": "6.7.1",
|
||||
"@codemirror/view": "6.43.8",
|
||||
"@codemirror/view": "6.43.7",
|
||||
"@date-fns/tz": "1.5.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.6.0",
|
||||
@@ -91,9 +91,10 @@
|
||||
"@webcomponents/webcomponentsjs": "2.8.0",
|
||||
"barcode-detector": "3.2.1",
|
||||
"cally": "0.9.2",
|
||||
"chart2music": "1.20.0",
|
||||
"color-name": "2.1.1",
|
||||
"comlink": "4.4.2",
|
||||
"core-js": "3.50.0",
|
||||
"core-js": "3.49.0",
|
||||
"cropperjs": "1.6.2",
|
||||
"culori": "4.0.2",
|
||||
"date-fns": "4.4.0",
|
||||
@@ -101,9 +102,10 @@
|
||||
"deep-freeze": "0.0.1",
|
||||
"dialog-polyfill": "0.5.6",
|
||||
"echarts": "6.1.0",
|
||||
"echarts-extension-chart2music": "0.1.0",
|
||||
"element-internals-polyfill": "3.0.2",
|
||||
"fuse.js": "7.5.0",
|
||||
"hls.js": "1.6.17",
|
||||
"hls.js": "1.6.16",
|
||||
"home-assistant-js-websocket": "9.6.0",
|
||||
"idb-keyval": "6.3.0",
|
||||
"intl-messageformat": "11.2.13",
|
||||
@@ -114,7 +116,7 @@
|
||||
"lit": "3.3.3",
|
||||
"lit-html": "3.3.3",
|
||||
"luxon": "3.7.2",
|
||||
"marked": "18.0.9",
|
||||
"marked": "18.0.7",
|
||||
"memoize-one": "6.0.0",
|
||||
"node-vibrant": "4.0.4",
|
||||
"object-hash": "3.0.0",
|
||||
@@ -152,8 +154,8 @@
|
||||
"@octokit/rest": "22.0.1",
|
||||
"@playwright/test": "1.62.1",
|
||||
"@rsdoctor/rspack-plugin": "1.6.1",
|
||||
"@rspack/core": "2.1.8",
|
||||
"@rspack/dev-server": "2.2.0",
|
||||
"@rspack/core": "2.1.7",
|
||||
"@rspack/dev-server": "2.1.0",
|
||||
"@types/babel__plugin-transform-runtime": "7.9.5",
|
||||
"@types/chromecast-caf-receiver": "6.0.26",
|
||||
"@types/chromecast-caf-sender": "1.0.11",
|
||||
@@ -210,7 +212,7 @@
|
||||
"terser-webpack-plugin": "5.6.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.66.0",
|
||||
"typescript-eslint": "8.65.0",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.10",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
||||
@@ -23,24 +23,24 @@ export const isNavigationClick = (e: MouseEvent, preventDefault = true) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
// anchor.href is always absolute; an empty or unparseable value throws.
|
||||
url = new URL(anchor.href);
|
||||
} catch {
|
||||
let href = anchor.href;
|
||||
if (!href || href.indexOf("mailto:") !== -1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Only intercept same-origin links. A different scheme, host, or port is a
|
||||
// different origin (e.g. another port like ":8123") and must trigger a full
|
||||
// browser navigation instead of an in-app route change. Non-http(s) schemes
|
||||
// such as mailto: resolve to a null origin and are excluded here too.
|
||||
if (url.origin !== window.location.origin) {
|
||||
const location = window.location;
|
||||
const origin = location.origin || location.protocol + "//" + location.host;
|
||||
if (!href.startsWith(origin)) {
|
||||
return undefined;
|
||||
}
|
||||
href = href.slice(origin.length);
|
||||
|
||||
if (href === "#") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return url.pathname + url.search + url.hash;
|
||||
return href;
|
||||
};
|
||||
|
||||
@@ -52,7 +52,7 @@ export function stateActive(stateObj: HassEntity, state?: string): boolean {
|
||||
case "timer":
|
||||
return compareState === "active";
|
||||
case "camera":
|
||||
return ["streaming", "recording"].includes(compareState);
|
||||
return compareState === "streaming";
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
import type { HassConfig } from "home-assistant-js-websocket";
|
||||
import type { EChartsType } from "echarts/core";
|
||||
import type { XAXisOption, YAXisOption } from "echarts/types/dist/shared";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { formatDateTime } from "../../common/datetime/format_date_time";
|
||||
import type { LocalizeFunc } from "../../common/translations/localize";
|
||||
import type { FrontendLocaleData } from "../../data/translation";
|
||||
import type {
|
||||
HaECSeries,
|
||||
HaECSeriesItem,
|
||||
} from "../../resources/echarts/echarts";
|
||||
|
||||
export interface ChartSonification {
|
||||
update: () => void;
|
||||
dispose: () => void;
|
||||
}
|
||||
|
||||
// Series types the Chart2Music ECharts extension can turn into data points. Our
|
||||
// other series (custom timelines, sankey, network graphs) have no equivalent, and
|
||||
// the extension refuses the whole chart if a single series is unsupported.
|
||||
const SONIFIABLE_SERIES_TYPES = new Set(["bar", "line", "pie", "scatter"]);
|
||||
|
||||
// Languages shipped by chart2music. Anything else falls back to its English.
|
||||
const SONIFICATION_LANGUAGES = new Set(["de", "en", "es", "fr", "hmn", "it"]);
|
||||
|
||||
// Mirrors the extension's own reading of a point: it drops anything whose y is
|
||||
// not a real number, so a series of nothing but nulls converts to an empty group
|
||||
// and makes Chart2Music throw. Short-circuits on the first usable point.
|
||||
const hasNumericPoint = (data: unknown): boolean =>
|
||||
Array.isArray(data) &&
|
||||
data.some((raw) => {
|
||||
let y: unknown = raw;
|
||||
if (Array.isArray(raw)) {
|
||||
y = raw.length > 1 ? raw[1] : raw[0];
|
||||
} else if (raw && typeof raw === "object") {
|
||||
const { value } = raw as { value?: unknown };
|
||||
y = Array.isArray(value)
|
||||
? value.length > 1
|
||||
? value[1]
|
||||
: value[0]
|
||||
: value;
|
||||
}
|
||||
return typeof y === "number" && !Number.isNaN(y);
|
||||
});
|
||||
|
||||
export const canSonifyChart = (data: HaECSeries): boolean => {
|
||||
const series = ensureArray(data);
|
||||
return (
|
||||
// Cards commonly push empty placeholder series, so only require that
|
||||
// something is plottable — but every type has to be convertible.
|
||||
series.some((s) => hasNumericPoint(s.data)) &&
|
||||
series.every((s) => SONIFIABLE_SERIES_TYPES.has(s.type as string))
|
||||
);
|
||||
};
|
||||
|
||||
interface SonifyChartOptions {
|
||||
cc: HTMLElement;
|
||||
localize: LocalizeFunc;
|
||||
locale: FrontendLocaleData;
|
||||
config: HassConfig;
|
||||
onError: (error: string) => void;
|
||||
}
|
||||
|
||||
// Chart2Music appends its help and options dialogs straight to document.body, so
|
||||
// they can only be themed from a document-level stylesheet.
|
||||
let stylesAppended = false;
|
||||
const appendSonificationStyles = () => {
|
||||
if (stylesAppended) {
|
||||
return;
|
||||
}
|
||||
stylesAppended = true;
|
||||
const style = document.createElement("style");
|
||||
style.textContent = `
|
||||
dialog.chart2music-dialog {
|
||||
box-sizing: border-box;
|
||||
max-width: min(600px, calc(100vw - 32px));
|
||||
max-height: calc(100vh - 32px);
|
||||
overflow: auto;
|
||||
padding: var(--ha-space-6);
|
||||
border: none;
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
background-color: var(--card-background-color);
|
||||
color: var(--primary-text-color);
|
||||
font-family: var(--ha-font-family-body);
|
||||
font-size: var(--ha-font-size-m);
|
||||
box-shadow: var(--ha-box-shadow-l);
|
||||
}
|
||||
dialog.chart2music-dialog::backdrop {
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
dialog.chart2music-dialog h1 {
|
||||
font-size: var(--ha-font-size-2xl);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
margin-block: 0 var(--ha-space-4);
|
||||
padding-inline-end: var(--ha-space-8);
|
||||
}
|
||||
dialog.chart2music-dialog table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
dialog.chart2music-dialog th,
|
||||
dialog.chart2music-dialog td {
|
||||
text-align: start;
|
||||
padding: var(--ha-space-1) var(--ha-space-2);
|
||||
border-bottom: 1px solid var(--divider-color);
|
||||
}
|
||||
dialog.chart2music-dialog a {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
dialog.chart2music-dialog > button {
|
||||
/* The extension inlines "right", which does not mirror in RTL, and inline
|
||||
styles can only be beaten with !important. */
|
||||
inset-inline-end: var(--ha-space-4) !important;
|
||||
inset-inline-start: auto !important;
|
||||
top: var(--ha-space-4);
|
||||
min-width: 32px;
|
||||
min-height: 32px;
|
||||
cursor: pointer;
|
||||
border: 1px solid var(--divider-color);
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
background-color: transparent;
|
||||
color: var(--primary-text-color);
|
||||
font: inherit;
|
||||
}
|
||||
`;
|
||||
document.head.append(style);
|
||||
};
|
||||
|
||||
export const sonifyChart = async (
|
||||
chart: EChartsType,
|
||||
options: SonifyChartOptions
|
||||
): Promise<ChartSonification | null> => {
|
||||
const { connect } = await import("echarts-extension-chart2music");
|
||||
const { localize, locale, config } = options;
|
||||
appendSonificationStyles();
|
||||
|
||||
// ECharts nulls its model on dispose, and the instance can be disposed while
|
||||
// the chunk is in flight.
|
||||
const chartOptions = chart.getOption() as ReturnType<
|
||||
EChartsType["getOption"]
|
||||
> | null;
|
||||
if (!chartOptions) {
|
||||
return null;
|
||||
}
|
||||
const xAxis = ensureArray(chartOptions.xAxis)[0] as XAXisOption | undefined;
|
||||
const yAxis = ensureArray(chartOptions.yAxis)[0] as YAXisOption | undefined;
|
||||
|
||||
// Chart2Music throws while validating a group with no points, which is what
|
||||
// placeholder, legend-hidden and all-null series turn into, so only offer it
|
||||
// the series carrying at least one point it can read.
|
||||
const seriesIndex: number[] = [];
|
||||
ensureArray(chartOptions.series).forEach((s, index) => {
|
||||
if (hasNumericPoint((s as HaECSeriesItem | undefined)?.data)) {
|
||||
seriesIndex.push(index);
|
||||
}
|
||||
});
|
||||
if (!seriesIndex.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Chart2Music always reads out an axis label, and the extension picks the wrong
|
||||
// axis to name when there is no category axis, so label both explicitly.
|
||||
const isTimeAxis = xAxis?.type === "time";
|
||||
const x = {
|
||||
label:
|
||||
xAxis?.name ||
|
||||
localize(
|
||||
isTimeAxis
|
||||
? "ui.components.history_charts.time"
|
||||
: "ui.components.history_charts.category"
|
||||
),
|
||||
// Time series carry raw timestamps, which would otherwise be announced as
|
||||
// epoch milliseconds.
|
||||
format: isTimeAxis
|
||||
? (value: number) => formatDateTime(new Date(value), locale, config)
|
||||
: undefined,
|
||||
};
|
||||
const y = {
|
||||
label: yAxis?.name || localize("ui.components.history_charts.value"),
|
||||
};
|
||||
|
||||
let connection: ReturnType<typeof connect>;
|
||||
try {
|
||||
connection = connect(chart, {
|
||||
cc: options.cc,
|
||||
seriesIndex,
|
||||
title: localize("ui.components.history_charts.chart"),
|
||||
lang: SONIFICATION_LANGUAGES.has(locale.language)
|
||||
? locale.language
|
||||
: "en",
|
||||
errorCallback: options.onError,
|
||||
axes: { x, y },
|
||||
});
|
||||
} catch (err: any) {
|
||||
options.onError(err?.message ?? String(err));
|
||||
return null;
|
||||
}
|
||||
if (!connection) {
|
||||
return null;
|
||||
}
|
||||
// Chart2Music bails out silently on mobile user agents, returning an instance
|
||||
// that never wired anything up. Turning the caption container into a live
|
||||
// region is the last thing it does, so use that as the "really connected" test
|
||||
// rather than leaving a focus stop that does nothing.
|
||||
if (!options.cc.hasAttribute("aria-live")) {
|
||||
connection.dispose();
|
||||
return null;
|
||||
}
|
||||
const connected = connection;
|
||||
|
||||
// The extension re-reads the chart from ECharts' own "finished" event. Run that
|
||||
// through a guard of our own so a conversion failure cannot escape into
|
||||
// ECharts' event dispatch and leave the chart half-rendered.
|
||||
const update = () => {
|
||||
try {
|
||||
connected.update();
|
||||
} catch (_err) {
|
||||
// Keep whatever Chart2Music last read successfully.
|
||||
}
|
||||
};
|
||||
chart.off("finished", connected.update);
|
||||
chart.on("finished", update);
|
||||
|
||||
return {
|
||||
update,
|
||||
dispose: () => {
|
||||
chart.off("finished", update);
|
||||
connected.dispose();
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -22,6 +22,7 @@ import type { PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
import { ifDefined } from "lit/directives/if-defined";
|
||||
import { styleMap } from "lit/directives/style-map";
|
||||
import { ensureArray } from "../../common/array/ensure-array";
|
||||
import { getAllGraphColors } from "../../common/color/colors";
|
||||
@@ -47,6 +48,8 @@ import { isMac } from "../../util/is_mac";
|
||||
import "../chips/ha-assist-chip";
|
||||
import "../ha-icon-button";
|
||||
import { formatTimeLabel } from "./axis-label";
|
||||
import type { ChartSonification } from "./chart-sonification";
|
||||
import { canSonifyChart, sonifyChart } from "./chart-sonification";
|
||||
import { downSampleLineData } from "./down-sample";
|
||||
import { wrapLitTooltipFormatter } from "./lit-tooltip-formatter";
|
||||
|
||||
@@ -146,6 +149,17 @@ export class HaChartBase extends LitElement {
|
||||
|
||||
@query(".chart") private _chartContainer?: HTMLDivElement;
|
||||
|
||||
@query(".sonification-output")
|
||||
private _sonificationOutput?: HTMLDivElement;
|
||||
|
||||
private _sonification?: ChartSonification;
|
||||
|
||||
private _sonificationLoading = false;
|
||||
|
||||
@state() private _sonificationUnavailable = false;
|
||||
|
||||
@state() private _sonificationFocusHeld = false;
|
||||
|
||||
private _modifierPressed = false;
|
||||
|
||||
private _isTouchDevice = "ontouchstart" in window;
|
||||
@@ -198,6 +212,7 @@ export class HaChartBase extends LitElement {
|
||||
while (this._listeners.length) {
|
||||
this._listeners.pop()!();
|
||||
}
|
||||
this._disposeSonification();
|
||||
this.chart?.dispose();
|
||||
this.chart = undefined;
|
||||
this._originalZrFlush = undefined;
|
||||
@@ -312,6 +327,17 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
if (changedProps.has("data") || changedProps.has("_hiddenDatasets")) {
|
||||
chartOptions.series = this._getSeries();
|
||||
// New data, or a series shown again, may well be convertible where the
|
||||
// last set was not.
|
||||
this._sonificationUnavailable = false;
|
||||
// The connection is built from the series that had data at the time, so
|
||||
// drop it and let the next focus rebuild it against the current set.
|
||||
if (
|
||||
this._sonification &&
|
||||
(changedProps.has("_hiddenDatasets") || !canSonifyChart(this.data))
|
||||
) {
|
||||
this._disposeSonification();
|
||||
}
|
||||
}
|
||||
if (changedProps.has("options")) {
|
||||
chartOptions = { ...chartOptions, ...this._createOptions() };
|
||||
@@ -337,6 +363,8 @@ export class HaChartBase extends LitElement {
|
||||
}
|
||||
|
||||
protected render() {
|
||||
const sonifiable =
|
||||
!this._sonificationUnavailable && canSonifyChart(this.data);
|
||||
return html`
|
||||
<div
|
||||
class="container ${classMap({ "has-height": !!this.height })}"
|
||||
@@ -348,8 +376,22 @@ export class HaChartBase extends LitElement {
|
||||
height: this.height ? undefined : `${this._getDefaultHeight()}px`,
|
||||
})}
|
||||
>
|
||||
<div class="chart"></div>
|
||||
<div
|
||||
class="chart"
|
||||
role=${ifDefined(sonifiable ? "application" : undefined)}
|
||||
tabindex=${ifDefined(
|
||||
sonifiable ? "0" : this._sonificationFocusHeld ? "-1" : undefined
|
||||
)}
|
||||
aria-label=${ifDefined(
|
||||
sonifiable
|
||||
? this.hass.localize("ui.components.history_charts.chart")
|
||||
: undefined
|
||||
)}
|
||||
@focus=${this._handleChartFocus}
|
||||
@blur=${this._handleChartBlur}
|
||||
></div>
|
||||
</div>
|
||||
<div class="sonification-output"></div>
|
||||
${this._renderLegend()}
|
||||
<div class="top-controls ${classMap({ small: this.smallControls })}">
|
||||
<slot name="search"></slot>
|
||||
@@ -521,6 +563,61 @@ export class HaChartBase extends LitElement {
|
||||
</div>`;
|
||||
}
|
||||
|
||||
// Chart2Music adds ~45 kB gzipped, so it is only fetched once someone actually
|
||||
// moves keyboard focus into a chart.
|
||||
private async _handleChartFocus() {
|
||||
if (this._sonification || this._sonificationLoading || !this.chart) {
|
||||
return;
|
||||
}
|
||||
this._sonificationLoading = true;
|
||||
try {
|
||||
const sonification = await sonifyChart(this.chart, {
|
||||
cc: this._sonificationOutput!,
|
||||
localize: this.hass.localize,
|
||||
locale: this.hass.locale,
|
||||
config: this.hass.config,
|
||||
onError: () => {
|
||||
// Charts the extension cannot describe stay silent rather than
|
||||
// dropping an error on someone who only pressed Tab.
|
||||
},
|
||||
});
|
||||
if (!this.isConnected || !this.chart) {
|
||||
sonification?.dispose();
|
||||
return;
|
||||
}
|
||||
if (!sonification) {
|
||||
// Nothing came back, so stop offering a focus stop that leads nowhere.
|
||||
// Stay programmatically focusable for as long as we hold focus though:
|
||||
// dropping tabindex off the active element resets focus to the document
|
||||
// and costs the user their place in the tab order.
|
||||
this._sonificationFocusHeld =
|
||||
this.shadowRoot?.activeElement === this._chartContainer;
|
||||
this._sonificationUnavailable = true;
|
||||
return;
|
||||
}
|
||||
this._sonification = sonification;
|
||||
if (this.shadowRoot?.activeElement === this._chartContainer) {
|
||||
// Chart2Music reads its summary and key hints on focus, which already
|
||||
// happened while it was still being fetched.
|
||||
this._chartContainer!.dispatchEvent(new FocusEvent("focus"));
|
||||
}
|
||||
} catch (_err) {
|
||||
// Never let a failure here escape a focus handler. The tab stop stays, so
|
||||
// focusing the chart again retries.
|
||||
} finally {
|
||||
this._sonificationLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private _handleChartBlur() {
|
||||
this._sonificationFocusHeld = false;
|
||||
}
|
||||
|
||||
private _disposeSonification() {
|
||||
this._sonification?.dispose();
|
||||
this._sonification = undefined;
|
||||
}
|
||||
|
||||
private _formatTimeLabel = (value: number | Date) =>
|
||||
formatTimeLabel(
|
||||
value,
|
||||
@@ -533,6 +630,9 @@ export class HaChartBase extends LitElement {
|
||||
if (this._loading) return;
|
||||
this._loading = true;
|
||||
try {
|
||||
// The connection holds a reference to the chart instance, so it cannot
|
||||
// outlive it. Focusing the chart again reconnects.
|
||||
this._disposeSonification();
|
||||
if (this.chart) {
|
||||
this.chart.dispose();
|
||||
}
|
||||
@@ -1450,6 +1550,23 @@ export class HaChartBase extends LitElement {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
.chart:focus-visible {
|
||||
outline: 2px solid var(--primary-color);
|
||||
outline-offset: 2px;
|
||||
border-radius: var(--ha-border-radius-sm);
|
||||
}
|
||||
/* Chart2Music renders its announcements here. It must stay in the layout for
|
||||
screen readers to pick up the live region, so hide it visually only. */
|
||||
.sonification-output {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
clip: rect(0 0 0 0);
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
margin: -1px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
.top-controls {
|
||||
position: absolute;
|
||||
top: var(--ha-space-4);
|
||||
|
||||
@@ -56,7 +56,6 @@ export class HaSelectBox extends LitElement {
|
||||
class="list"
|
||||
style=${styleMap({ "--columns": columns })}
|
||||
.value=${this.value}
|
||||
?disabled=${this.disabled}
|
||||
@change=${this._radioChanged}
|
||||
>
|
||||
${this.options.map((option) => this._renderOption(option))}
|
||||
@@ -101,7 +100,7 @@ export class HaSelectBox extends LitElement {
|
||||
)}
|
||||
aria-labelledby=${`label-${option.value}`}
|
||||
.value=${option.value}
|
||||
.disabled=${option.disabled || false}
|
||||
.disabled=${disabled}
|
||||
></ha-radio-option>
|
||||
<div class="text">
|
||||
<span id=${`label-${option.value}`} class="label"
|
||||
|
||||
@@ -53,6 +53,7 @@ export class HaSelectorAutomationBehavior extends LitElement {
|
||||
value: behavior,
|
||||
label: this._localizeOption(behavior, "label"),
|
||||
description: this._localizeOption(behavior, "description"),
|
||||
disabled: this.disabled,
|
||||
...(isTrigger && {
|
||||
image: {
|
||||
src: `/static/images/form/automation_behavior_trigger_${behavior}.svg`,
|
||||
@@ -65,7 +66,6 @@ export class HaSelectorAutomationBehavior extends LitElement {
|
||||
<ha-select-box
|
||||
.options=${options}
|
||||
.value=${this.value ?? ""}
|
||||
.disabled=${this.disabled}
|
||||
max_columns="1"
|
||||
?stacked_image=${isTrigger}
|
||||
@value-changed=${this._valueChanged}
|
||||
|
||||
@@ -104,7 +104,6 @@ export class HaSelectSelector extends LitElement {
|
||||
<ha-select-box
|
||||
.options=${options}
|
||||
.value=${this.value as string | undefined}
|
||||
.disabled=${this.disabled}
|
||||
@value-changed=${this._selectChanged}
|
||||
.maxColumns=${this.selector.select?.box_max_columns}
|
||||
></ha-select-box>
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import type {
|
||||
HassEntity,
|
||||
HassEntityAttributeBase,
|
||||
HassEntityBase,
|
||||
} from "home-assistant-js-websocket";
|
||||
import { DOMAINS_WITH_DYNAMIC_PICTURE } from "../common/const";
|
||||
import { computeDomain } from "../common/entity/compute_domain";
|
||||
import { stateActive } from "../common/entity/state_active";
|
||||
import { navigate } from "../common/navigate";
|
||||
import type { HomeAssistant, ServiceCallResponse } from "../types";
|
||||
|
||||
@@ -70,75 +66,6 @@ export type SceneMetaData = Record<
|
||||
{ entity_only?: boolean | undefined }
|
||||
>;
|
||||
|
||||
// Hand-edited scenes.yaml is parsed as YAML 1.1 by the backend, so unquoted
|
||||
// on/off arrive here as booleans. The backend applies boolean states as
|
||||
// on/off; mirror that so the badge reflects what activating the scene will
|
||||
// actually do. Everything else the backend rejects (numbers, null, arrays,
|
||||
// objects) yields undefined.
|
||||
const normalizeSceneEntityState = (sceneState: unknown): string | undefined => {
|
||||
if (typeof sceneState === "boolean") {
|
||||
return sceneState ? "on" : "off";
|
||||
}
|
||||
if (typeof sceneState === "string") {
|
||||
return sceneState;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Builds a state object from the scene's stored target state, so the scene
|
||||
// editor can render the icon the entity will have once the scene is applied
|
||||
// rather than its current live icon. The parameter is typed unknown because
|
||||
// the scene config API returns raw YAML: booleans, numbers, nulls, and
|
||||
// malformed shapes occur in hand-edited files beyond what the SceneEntities
|
||||
// union declares. Entries that hold no usable target state (an entity left
|
||||
// without a value in the YAML editor parses as null, a dict may lack a state
|
||||
// key) yield undefined so the caller renders no badge instead of guessing.
|
||||
//
|
||||
// The result is a partial HassEntity meant for badge rendering only - it has
|
||||
// no last_changed, last_updated, or context.
|
||||
export const sceneEntityStateObj = (
|
||||
entityId: string,
|
||||
sceneEntity: unknown
|
||||
): HassEntity | undefined => {
|
||||
if (
|
||||
typeof sceneEntity !== "object" ||
|
||||
sceneEntity === null ||
|
||||
Array.isArray(sceneEntity)
|
||||
) {
|
||||
const state = normalizeSceneEntityState(sceneEntity);
|
||||
return state === undefined
|
||||
? undefined
|
||||
: ({ entity_id: entityId, state, attributes: {} } as HassEntity);
|
||||
}
|
||||
const { state: sceneState, ...attributes } = sceneEntity as Record<
|
||||
string,
|
||||
unknown
|
||||
>;
|
||||
const state = normalizeSceneEntityState(sceneState);
|
||||
if (state === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
// Media-derived entity pictures are snapshotted with an access token that
|
||||
// is stale by the time review mode renders, which would leave the badge
|
||||
// showing a broken image instead of an icon. Stable pictures on other
|
||||
// domains are kept - the same policy createHistoricState applies for the
|
||||
// logbook.
|
||||
if (DOMAINS_WITH_DYNAMIC_PICTURE.has(computeDomain(entityId))) {
|
||||
delete attributes.entity_picture;
|
||||
delete attributes.entity_picture_local;
|
||||
}
|
||||
const stateObj = { entity_id: entityId, state, attributes } as HassEntity;
|
||||
// A live entity never carries color attributes while off, and state-badge
|
||||
// applies rgb_color and brightness without checking activity; drop them for
|
||||
// inactive targets so a scene that turns a light off does not render an
|
||||
// active-looking colored icon.
|
||||
if (!stateActive(stateObj)) {
|
||||
delete attributes.rgb_color;
|
||||
delete attributes.brightness;
|
||||
}
|
||||
return stateObj;
|
||||
};
|
||||
|
||||
export const activateScene = (
|
||||
hass: HomeAssistant,
|
||||
entityId: string
|
||||
|
||||
@@ -102,12 +102,13 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
) {
|
||||
this.checkDataBaseMigration();
|
||||
}
|
||||
// Wait for `hass.user` to first populate so the admin guard can run; it
|
||||
// arrives asynchronously after `hass.config`. `hass.user` also gets a fresh
|
||||
// reference at runtime (reconnect, profile refresh via subscribeUser), so
|
||||
// only trigger on the initial population (null -> user). Reconnect re-checks
|
||||
// come from connection-mixin, the launch-screen swap re-check from update().
|
||||
if (changedProps.has("hass") && this.hass?.user && !oldHass?.user) {
|
||||
// Wait for `hass.user` to populate so the admin guard can run; it arrives
|
||||
// asynchronously after `hass.config`.
|
||||
if (
|
||||
changedProps.has("hass") &&
|
||||
this.hass?.user &&
|
||||
oldHass?.user !== this.hass.user
|
||||
) {
|
||||
this.checkHttpPendingConfig();
|
||||
}
|
||||
if (
|
||||
@@ -124,12 +125,12 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
}
|
||||
|
||||
protected update(changedProps: PropertyValues<this>) {
|
||||
const removingLaunchScreen =
|
||||
!!this.hass?.states &&
|
||||
!!this.hass.config &&
|
||||
!!this.hass.services &&
|
||||
this._databaseMigration === false;
|
||||
if (removingLaunchScreen) {
|
||||
if (
|
||||
this.hass?.states &&
|
||||
this.hass.config &&
|
||||
this.hass.services &&
|
||||
this._databaseMigration === false
|
||||
) {
|
||||
this.render = this.renderHass;
|
||||
this.update = super.update;
|
||||
// partial-panel-resolver removes the launch screen after the first panel
|
||||
@@ -137,13 +138,6 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
// screen covers the frontend until frontend/loaded is sent.
|
||||
}
|
||||
super.update(changedProps);
|
||||
if (removingLaunchScreen) {
|
||||
// Surface the HTTP pending config dialog only after super.update() has
|
||||
// committed the render swap above, which clears the launch screen from
|
||||
// the shadow root. Appending the dialog before that render would let it
|
||||
// tear the freshly-added dialog straight back out of the DOM.
|
||||
this.checkHttpPendingConfig();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
@@ -262,13 +256,6 @@ export class HomeAssistantAppEl extends QuickBarMixin(HassElement) {
|
||||
if (__DEMO__ || this._httpPendingDialogOpen) {
|
||||
return;
|
||||
}
|
||||
// Only show once the main UI is rendered. During startup the root swaps
|
||||
// the launch screen for the app, which clears its shadow root and would
|
||||
// tear the freshly-appended dialog straight back out of the DOM (closing
|
||||
// it). When called too early we skip; the swap in update() re-runs this.
|
||||
if (this.render !== this.renderHass) {
|
||||
return;
|
||||
}
|
||||
if (!this.hass?.user?.is_admin) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -128,12 +128,8 @@ class DialogAutomationSave
|
||||
`;
|
||||
}
|
||||
|
||||
private get _isDiscardDialog(): boolean {
|
||||
return this._params?.onDiscard !== undefined;
|
||||
}
|
||||
|
||||
protected _renderDiscard() {
|
||||
if (!this._isDiscardDialog) {
|
||||
if (!this._params?.onDiscard) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
@@ -328,14 +324,10 @@ class DialogAutomationSave
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
.disabled=${
|
||||
!!this._params.config.alias &&
|
||||
!this._isDiscardDialog &&
|
||||
!this.isDirtyState
|
||||
}
|
||||
.disabled=${!!this._params.config.alias && !this.isDirtyState}
|
||||
>
|
||||
${this.hass.localize(
|
||||
this._params.config.alias && !this._isDiscardDialog
|
||||
this._params.config.alias && !this._params.onDiscard
|
||||
? "ui.panel.config.automation.editor.rename"
|
||||
: "ui.common.save"
|
||||
)}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
import { mdiInformationOutline } from "@mdi/js";
|
||||
import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-expansion-panel";
|
||||
import "../../../../components/ha-icon-picker";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import "../../../../components/input/ha-input";
|
||||
import "../../../../components/radio/ha-radio-group";
|
||||
import type { HaRadioGroup } from "../../../../components/radio/ha-radio-group";
|
||||
import "../../../../components/radio/ha-radio-option";
|
||||
import type { InputBoolean } from "../../../../data/input_boolean";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
@@ -30,8 +22,6 @@ class HaInputBooleanForm extends LitElement {
|
||||
|
||||
@state() private _icon!: string;
|
||||
|
||||
@state() private _initial?: boolean;
|
||||
|
||||
@query("[dialogInitialFocus]") private _focusElement?: HTMLElement;
|
||||
|
||||
set item(item: InputBoolean) {
|
||||
@@ -39,11 +29,9 @@ class HaInputBooleanForm extends LitElement {
|
||||
if (item) {
|
||||
this._name = item.name || "";
|
||||
this._icon = item.icon || "";
|
||||
this._initial = item.initial;
|
||||
} else {
|
||||
this._name = "";
|
||||
this._icon = "";
|
||||
this._initial = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,79 +70,10 @@ class HaInputBooleanForm extends LitElement {
|
||||
)}
|
||||
.disabled=${this.disabled}
|
||||
></ha-icon-picker>
|
||||
<ha-expansion-panel
|
||||
header=${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.generic.more_options"
|
||||
)}
|
||||
outlined
|
||||
>
|
||||
<ha-radio-group
|
||||
.value=${
|
||||
this._initial === undefined
|
||||
? "restore"
|
||||
: this._initial
|
||||
? "on"
|
||||
: "off"
|
||||
}
|
||||
.disabled=${this.disabled}
|
||||
name="initial"
|
||||
@change=${this._initialChanged}
|
||||
>
|
||||
<span slot="label">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.initial"
|
||||
)}
|
||||
<ha-svg-icon
|
||||
id="initial-note"
|
||||
tabindex="0"
|
||||
class="note-icon"
|
||||
.path=${mdiInformationOutline}
|
||||
@click=${stopPropagation}
|
||||
></ha-svg-icon>
|
||||
</span>
|
||||
<ha-radio-option value="restore">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.restore"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="on">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.turn_on"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
<ha-radio-option value="off">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.turn_off"
|
||||
)}
|
||||
</ha-radio-option>
|
||||
</ha-radio-group>
|
||||
<ha-tooltip for="initial-note" placement="top">
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.helper_settings.input_boolean.initial_helper"
|
||||
)}
|
||||
</ha-tooltip>
|
||||
</ha-expansion-panel>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private _initialChanged(ev: Event) {
|
||||
const option = String((ev.currentTarget as HaRadioGroup).value);
|
||||
const initial = option === "restore" ? undefined : option === "on";
|
||||
if (initial === this._initial) {
|
||||
return;
|
||||
}
|
||||
const newValue = { ...this._item } as InputBoolean;
|
||||
if (initial === undefined) {
|
||||
delete newValue.initial;
|
||||
} else {
|
||||
newValue.initial = initial;
|
||||
}
|
||||
fireEvent(this, "value-changed", {
|
||||
value: newValue,
|
||||
});
|
||||
}
|
||||
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
if (!this.new && !this._item) {
|
||||
return;
|
||||
@@ -189,18 +108,6 @@ class HaInputBooleanForm extends LitElement {
|
||||
ha-input {
|
||||
margin: var(--ha-space-2) 0;
|
||||
}
|
||||
ha-expansion-panel {
|
||||
margin-top: var(--ha-space-4);
|
||||
}
|
||||
ha-expansion-panel ha-radio-group {
|
||||
margin: var(--ha-space-4) 0;
|
||||
}
|
||||
.note-icon {
|
||||
margin-inline-start: var(--ha-space-1);
|
||||
color: var(--secondary-text-color);
|
||||
--mdc-icon-size: 18px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -462,7 +462,7 @@ class HaConfigHttpForm extends LitElement {
|
||||
"ui.panel.config.network.http.restart_address.text"
|
||||
)}
|
||||
</p>
|
||||
<a href=${url} target="_blank" rel="noreferrer noopener">${url}</a>
|
||||
<a href=${url} rel="noreferrer noopener">${url}</a>
|
||||
<p class="dialog-note">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.network.http.restart_address.note"
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
mdiPlaylistEdit,
|
||||
mdiTag,
|
||||
} from "@mdi/js";
|
||||
import type { HassEntity, HassEvent } from "home-assistant-js-websocket";
|
||||
import type { HassEvent } from "home-assistant-js-websocket";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
@@ -67,7 +67,6 @@ import {
|
||||
getSceneEditorInitData,
|
||||
saveScene,
|
||||
SCENE_IGNORED_DOMAINS,
|
||||
sceneEntityStateObj,
|
||||
showSceneEditor,
|
||||
} from "../../../data/scene";
|
||||
import {
|
||||
@@ -448,15 +447,13 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
const integrationName = platform
|
||||
? domainToName(this.hass.localize, platform)
|
||||
: undefined;
|
||||
const badgeStateObj = this._badgeStateObj(
|
||||
entityId,
|
||||
entityStateObj
|
||||
);
|
||||
return html`
|
||||
<ha-list-item
|
||||
hasMeta
|
||||
?twoline=${!!secondary}
|
||||
graphic="icon"
|
||||
.graphic=${
|
||||
this._mode === "live" ? "icon" : undefined
|
||||
}
|
||||
.entityId=${entityId}
|
||||
@click=${
|
||||
this._mode === "live"
|
||||
@@ -466,10 +463,10 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
.noninteractive=${this._mode === "review"}
|
||||
>
|
||||
${
|
||||
badgeStateObj
|
||||
this._mode === "live"
|
||||
? html`
|
||||
<state-badge
|
||||
.stateObj=${badgeStateObj}
|
||||
.stateObj=${entityStateObj}
|
||||
slot="graphic"
|
||||
></state-badge>
|
||||
`
|
||||
@@ -555,16 +552,14 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
this.hass.localize,
|
||||
computeDomain(entityId)
|
||||
);
|
||||
const badgeStateObj = this._badgeStateObj(
|
||||
entityId,
|
||||
entityStateObj
|
||||
);
|
||||
return html`
|
||||
<ha-list-item
|
||||
class="entity"
|
||||
hasMeta
|
||||
?twoline=${!!secondary}
|
||||
graphic="icon"
|
||||
.graphic=${
|
||||
this._mode === "live" ? "icon" : undefined
|
||||
}
|
||||
.entityId=${entityId}
|
||||
@click=${
|
||||
this._mode === "live"
|
||||
@@ -574,13 +569,11 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
.noninteractive=${this._mode === "review"}
|
||||
>
|
||||
${
|
||||
badgeStateObj
|
||||
? html`
|
||||
<state-badge
|
||||
.stateObj=${badgeStateObj}
|
||||
slot="graphic"
|
||||
></state-badge>
|
||||
`
|
||||
this._mode === "live"
|
||||
? html` <state-badge
|
||||
.stateObj=${entityStateObj}
|
||||
slot="graphic"
|
||||
></state-badge>`
|
||||
: nothing
|
||||
}
|
||||
${primary}
|
||||
@@ -1195,33 +1188,6 @@ export class HaSceneEditor extends DirtyStateProviderMixin<number>()(
|
||||
return { ...stateObj.attributes, state: stateObj.state };
|
||||
}
|
||||
|
||||
// Memoized per config so re-renders reuse the same object references and
|
||||
// the state badges skip work when nothing changed.
|
||||
private _sceneStateObjs = memoizeOne((config?: SceneConfig) => {
|
||||
const objs: Record<string, HassEntity | undefined> = {};
|
||||
for (const entityId of Object.keys(config?.entities ?? {})) {
|
||||
objs[entityId] = sceneEntityStateObj(
|
||||
entityId,
|
||||
config!.entities[entityId]
|
||||
);
|
||||
}
|
||||
return objs;
|
||||
});
|
||||
|
||||
// Picks the state the row's icon should reflect: the live state in live
|
||||
// mode, the scene's stored target in review mode. Undefined in review mode
|
||||
// when the scene holds no usable target for the entity - the row then
|
||||
// renders no badge rather than a live state that could be mistaken for a
|
||||
// target.
|
||||
private _badgeStateObj(
|
||||
entityId: string,
|
||||
entityStateObj: HassEntity
|
||||
): HassEntity | undefined {
|
||||
return this._mode === "live"
|
||||
? entityStateObj
|
||||
: this._sceneStateObjs(this._config)[entityId];
|
||||
}
|
||||
|
||||
private _generateConfigFromLive() {
|
||||
this._config = {
|
||||
...this._config!,
|
||||
|
||||
@@ -1129,7 +1129,11 @@
|
||||
"zoom_reset": "Reset zoom",
|
||||
"expand_legend": "More",
|
||||
"collapse_legend": "Less",
|
||||
"toggle_visibility": "Toggle visibility"
|
||||
"toggle_visibility": "Toggle visibility",
|
||||
"chart": "Chart",
|
||||
"time": "Time",
|
||||
"value": "Value",
|
||||
"category": "Category"
|
||||
},
|
||||
"map": {
|
||||
"error": "Unable to load map"
|
||||
@@ -2142,13 +2146,6 @@
|
||||
"icon": "Icon",
|
||||
"more_options": "[%key:ui::dialogs::restart::more_options%]"
|
||||
},
|
||||
"input_boolean": {
|
||||
"initial": "Each time Home Assistant starts",
|
||||
"initial_helper": "Changing this does not affect the toggle right now.",
|
||||
"restore": "Restore previous state",
|
||||
"turn_on": "Turn on",
|
||||
"turn_off": "Turn off"
|
||||
},
|
||||
"input_datetime": {
|
||||
"date": "Date",
|
||||
"time": "Time",
|
||||
@@ -3861,7 +3858,7 @@
|
||||
},
|
||||
"disable_view_transition": {
|
||||
"title": "Disable view transitions",
|
||||
"description": "Disable animated view transitions to prevent browser crash when using browser developer tools."
|
||||
"description": "Disable animated view transitions to prevent browser crash when using browser dev tools."
|
||||
},
|
||||
"entity_diagnostic": {
|
||||
"title": "Entity diagnostic",
|
||||
@@ -3906,7 +3903,7 @@
|
||||
},
|
||||
"actions": {
|
||||
"title": "Actions",
|
||||
"description": "The actions tool allows you to perform any action available in Home Assistant.",
|
||||
"description": "The actions dev tool allows you to perform any action available in Home Assistant.",
|
||||
"call_service": "Perform action",
|
||||
"response": "Response",
|
||||
"column_parameter": "Parameter",
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { isNavigationClick } from "../../../src/common/dom/is-navigation-click";
|
||||
|
||||
const createAnchor = (
|
||||
href: string,
|
||||
attributes: Record<string, string> = {}
|
||||
): HTMLAnchorElement => {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = href;
|
||||
for (const [name, value] of Object.entries(attributes)) {
|
||||
anchor.setAttribute(name, value);
|
||||
}
|
||||
return anchor;
|
||||
};
|
||||
|
||||
const clickEvent = (
|
||||
anchor: HTMLAnchorElement,
|
||||
overrides: Partial<MouseEvent> = {}
|
||||
): MouseEvent =>
|
||||
({
|
||||
defaultPrevented: false,
|
||||
button: 0,
|
||||
metaKey: false,
|
||||
ctrlKey: false,
|
||||
shiftKey: false,
|
||||
composedPath: () => [anchor],
|
||||
preventDefault: vi.fn(),
|
||||
...overrides,
|
||||
}) as unknown as MouseEvent;
|
||||
|
||||
describe("isNavigationClick", () => {
|
||||
it("returns the path for same-origin links", () => {
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor(`${location.origin}/config`)))
|
||||
).toEqual("/config");
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}/energy?historyBack=1`))
|
||||
)
|
||||
).toEqual("/energy?historyBack=1");
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}/config/areas#section`))
|
||||
)
|
||||
).toEqual("/config/areas#section");
|
||||
});
|
||||
|
||||
it("does not intercept a link on a different port", () => {
|
||||
// The current origin has no port, so a ported URL shares its string prefix
|
||||
// but is a different origin — it must not be treated as internal.
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor(`${location.origin}:8123/`)))
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not intercept a host that merely starts with the origin", () => {
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}.example.com/`))
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not intercept other origins", () => {
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor("https://example.com/")))
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("calls preventDefault for intercepted navigations", () => {
|
||||
const event = clickEvent(createAnchor(`${location.origin}/config`));
|
||||
isNavigationClick(event);
|
||||
expect(event.preventDefault).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("does not preventDefault when disabled", () => {
|
||||
const event = clickEvent(createAnchor(`${location.origin}/config`));
|
||||
expect(isNavigationClick(event, false)).toEqual("/config");
|
||||
expect(event.preventDefault).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("ignores clicks with a target, download, or rel=external", () => {
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(
|
||||
createAnchor(`${location.origin}/config`, { target: "_blank" })
|
||||
)
|
||||
)
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(createAnchor(`${location.origin}/config`, { download: "" }))
|
||||
)
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(
|
||||
createAnchor(`${location.origin}/config`, { rel: "external" })
|
||||
)
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores modified clicks and non-left buttons", () => {
|
||||
const anchor = createAnchor(`${location.origin}/config`);
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { button: 1 }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { metaKey: true }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { ctrlKey: true }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { shiftKey: true }))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(clickEvent(anchor, { defaultPrevented: true }))
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("ignores mailto links and clicks without an anchor", () => {
|
||||
expect(
|
||||
isNavigationClick(clickEvent(createAnchor("mailto:[email protected]")))
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
isNavigationClick(
|
||||
clickEvent(undefined as any, {
|
||||
composedPath: () => [document.createElement("div")],
|
||||
})
|
||||
)
|
||||
).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,91 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { canSonifyChart } from "../../../src/components/chart/chart-sonification";
|
||||
import type { HaECSeries } from "../../../src/resources/echarts/echarts";
|
||||
|
||||
const series = (items: { type: string; data?: unknown[] }[]) =>
|
||||
items as unknown as HaECSeries;
|
||||
|
||||
describe("canSonifyChart", () => {
|
||||
it("accepts line and bar series that carry data", () => {
|
||||
expect(canSonifyChart(series([{ type: "line", data: [[0, 1]] }]))).toBe(
|
||||
true
|
||||
);
|
||||
expect(canSonifyChart(series([{ type: "bar", data: [[0, 1]] }]))).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it("accepts a chart whose empty placeholder series sits beside real data", () => {
|
||||
expect(
|
||||
canSonifyChart(
|
||||
series([
|
||||
{ type: "bar", data: [] },
|
||||
{ type: "bar", data: [[0, 1]] },
|
||||
])
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects series types the extension cannot convert", () => {
|
||||
expect(canSonifyChart(series([{ type: "custom", data: [[0, 1]] }]))).toBe(
|
||||
false
|
||||
);
|
||||
expect(
|
||||
canSonifyChart(
|
||||
series([
|
||||
{ type: "line", data: [[0, 1]] },
|
||||
{ type: "sankey", data: [[0, 1]] },
|
||||
])
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects charts with nothing plotted", () => {
|
||||
expect(canSonifyChart(series([]))).toBe(false);
|
||||
expect(canSonifyChart(series([{ type: "line", data: [] }]))).toBe(false);
|
||||
expect(canSonifyChart(series([{ type: "line" }]))).toBe(false);
|
||||
});
|
||||
|
||||
it("rejects a series whose points are all gaps", () => {
|
||||
// Chart2Music drops any point without a numeric y, so a series of nothing
|
||||
// but nulls converts to an empty group and makes it throw.
|
||||
expect(
|
||||
canSonifyChart(
|
||||
series([
|
||||
{
|
||||
type: "line",
|
||||
data: [
|
||||
[0, null],
|
||||
[1, null],
|
||||
],
|
||||
},
|
||||
])
|
||||
)
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("accepts a series that only becomes numeric partway through", () => {
|
||||
expect(
|
||||
canSonifyChart(
|
||||
series([
|
||||
{
|
||||
type: "line",
|
||||
data: [
|
||||
[0, null],
|
||||
[1, 21.5],
|
||||
],
|
||||
},
|
||||
])
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("reads the y out of object-form points", () => {
|
||||
expect(
|
||||
canSonifyChart(series([{ type: "bar", data: [{ value: [0, 0.28] }] }]))
|
||||
).toBe(true);
|
||||
expect(
|
||||
canSonifyChart(series([{ type: "bar", data: [{ value: [0, null] }] }]))
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -1,116 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { sceneEntityStateObj } from "../../src/data/scene";
|
||||
|
||||
describe("sceneEntityStateObj", () => {
|
||||
it("builds a state object from string shorthand", () => {
|
||||
expect(sceneEntityStateObj("light.kitchen", "on")).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "on",
|
||||
attributes: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("builds a state object from the dict form", () => {
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", {
|
||||
state: "on",
|
||||
brightness: 180,
|
||||
rgb_color: [255, 64, 112],
|
||||
})
|
||||
).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "on",
|
||||
attributes: { brightness: 180, rgb_color: [255, 64, 112] },
|
||||
});
|
||||
});
|
||||
|
||||
it("strips media-derived entity pictures but keeps other attributes", () => {
|
||||
const sceneEntity = {
|
||||
state: "playing",
|
||||
entity_picture: "/api/media_player_proxy/x?token=stale",
|
||||
entity_picture_local: "/local/x.jpg",
|
||||
friendly_name: "Player",
|
||||
};
|
||||
expect(sceneEntityStateObj("media_player.x", sceneEntity)).toEqual({
|
||||
entity_id: "media_player.x",
|
||||
state: "playing",
|
||||
attributes: { friendly_name: "Player" },
|
||||
});
|
||||
// The input scene config must not be mutated.
|
||||
expect(sceneEntity.entity_picture).toBe(
|
||||
"/api/media_player_proxy/x?token=stale"
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps a stable entity picture on other domains", () => {
|
||||
expect(
|
||||
sceneEntityStateObj("vacuum.robot", {
|
||||
state: "cleaning",
|
||||
entity_picture: "/local/robot.png",
|
||||
})?.attributes.entity_picture
|
||||
).toBe("/local/robot.png");
|
||||
});
|
||||
|
||||
it("returns undefined for null and undefined values", () => {
|
||||
// An entity left without a value in the YAML editor parses as null.
|
||||
expect(sceneEntityStateObj("light.kitchen", null)).toBeUndefined();
|
||||
expect(sceneEntityStateObj("light.kitchen", undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for an array value", () => {
|
||||
expect(sceneEntityStateObj("light.kitchen", [255, 100])).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined for a numeric value", () => {
|
||||
// The backend only accepts string and boolean states, so a number never
|
||||
// becomes a target the scene can apply.
|
||||
expect(sceneEntityStateObj("input_number.x", 23)).toBeUndefined();
|
||||
expect(
|
||||
sceneEntityStateObj("input_number.x", { state: 23 })
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns undefined when the dict holds no usable state", () => {
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", { brightness: 100 })
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", { state: null })
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", { state: { brightness: 100 } })
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("normalizes boolean shorthand to on/off like the backend does", () => {
|
||||
// YAML 1.1 parses unquoted on/off in scenes.yaml as booleans.
|
||||
expect(sceneEntityStateObj("light.kitchen", true)?.state).toBe("on");
|
||||
expect(sceneEntityStateObj("light.kitchen", false)?.state).toBe("off");
|
||||
});
|
||||
|
||||
it("normalizes a boolean state in the dict form", () => {
|
||||
expect(sceneEntityStateObj("light.kitchen", { state: true })).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "on",
|
||||
attributes: {},
|
||||
});
|
||||
});
|
||||
|
||||
it("drops color attributes for an inactive target", () => {
|
||||
// A live entity never carries these while off, and state-badge applies
|
||||
// them without checking activity.
|
||||
expect(
|
||||
sceneEntityStateObj("light.kitchen", {
|
||||
state: "off",
|
||||
brightness: 180,
|
||||
rgb_color: [255, 0, 0],
|
||||
friendly_name: "Kitchen",
|
||||
})
|
||||
).toEqual({
|
||||
entity_id: "light.kitchen",
|
||||
state: "off",
|
||||
attributes: { friendly_name: "Kitchen" },
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2439,15 +2439,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:6.43.8, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.37.0, @codemirror/view@npm:^6.42.0":
|
||||
version: 6.43.8
|
||||
resolution: "@codemirror/view@npm:6.43.8"
|
||||
"@codemirror/view@npm:6.43.7, @codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.17.0, @codemirror/view@npm:^6.23.0, @codemirror/view@npm:^6.27.0, @codemirror/view@npm:^6.37.0, @codemirror/view@npm:^6.42.0":
|
||||
version: 6.43.7
|
||||
resolution: "@codemirror/view@npm:6.43.7"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.7.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/88cd5543b09c4b945862ff128aeea48e259a582e1106fbc22370ddf773e629810b43baaf3abdf743150098e07c601ca481186cdf46b537f0312452fa2e7e8f51
|
||||
checksum: 10/dbc7c4e9162099dae15be1107e831c955c778fa224e24f4fd02d6eb9e75bc6c6d636e3e9fce3dc5852576c392c5793f4f6c5838f932a9c9e77f32a4ca66f62bf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2561,13 +2561,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/core@npm:1.11.3":
|
||||
version: 1.11.3
|
||||
resolution: "@emnapi/core@npm:1.11.3"
|
||||
"@emnapi/core@npm:1.11.2":
|
||||
version: 1.11.2
|
||||
resolution: "@emnapi/core@npm:1.11.2"
|
||||
dependencies:
|
||||
"@emnapi/wasi-threads": "npm:1.2.3"
|
||||
"@emnapi/wasi-threads": "npm:1.2.2"
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/6ed871d5bbd0f6e25a09fe91583e886e4b90ae9344fa9292c78f0891b0b2a7d84899ea65a7d576450b9ac50440c698dad1907907e3d686ec9460ed876a9363a2
|
||||
checksum: 10/b3e9693f79ca1d8bb90cb8a950150c7738f54eca0ae1849d3d5103a87ce4e388c2669fb253cc123d3449ad2ae12583435455dbdc0270b1e9efb0cfd502c952b4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2589,12 +2589,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/runtime@npm:1.11.3":
|
||||
version: 1.11.3
|
||||
resolution: "@emnapi/runtime@npm:1.11.3"
|
||||
"@emnapi/runtime@npm:1.11.2":
|
||||
version: 1.11.2
|
||||
resolution: "@emnapi/runtime@npm:1.11.2"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/ec834cc0fe248e06dd84f6dee10162133f8a692636189e99aa992303c791d4be1679536ee91aeee6661c4478609768cee9c085acd858caa92784c67acaab44a5
|
||||
checksum: 10/280a219d3bf302615dabaaa248223b0e5fe9c49bacf0987dd958ca37ab425b62cf05287053cb188e711681418aaa5e447eaed6b62a0848c0a1303b2707864600
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -2616,15 +2616,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@emnapi/wasi-threads@npm:1.2.3":
|
||||
version: 1.2.3
|
||||
resolution: "@emnapi/wasi-threads@npm:1.2.3"
|
||||
dependencies:
|
||||
tslib: "npm:^2.4.0"
|
||||
checksum: 10/d9fef5c321a6df1988e813e0b621ed1e01d274c23d3028ff9511af97f8935adc921ae60a35167ccf4e0414fecc040b66b9b13458e7c00cfaad622457e1b03529
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@eslint-community/eslint-utils@npm:^4.8.0, @eslint-community/eslint-utils@npm:^4.9.1":
|
||||
version: 4.9.1
|
||||
resolution: "@eslint-community/eslint-utils@npm:4.9.1"
|
||||
@@ -2765,6 +2756,27 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/ecma402-abstract@npm:2.3.4":
|
||||
version: 2.3.4
|
||||
resolution: "@formatjs/ecma402-abstract@npm:2.3.4"
|
||||
dependencies:
|
||||
"@formatjs/fast-memoize": "npm:2.2.7"
|
||||
"@formatjs/intl-localematcher": "npm:0.6.1"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/573971ffc291096a4b9fcc80b4708124e89bf2e3ac50e0f78b41eb797e9aa1b842f4dc3665e4467a853c738386821769d9e40408a1d25bc73323a1f057a16cf2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/fast-memoize@npm:2.2.7":
|
||||
version: 2.2.7
|
||||
resolution: "@formatjs/fast-memoize@npm:2.2.7"
|
||||
dependencies:
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/e7e6efc677d63a13d99a854305db471b69f64cbfebdcb6dbe507dab9aa7eaae482ca5de86f343c856ca0a2c8f251672bd1f37c572ce14af602c0287378097d43
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/fast-memoize@npm:3.1.7":
|
||||
version: 3.1.7
|
||||
resolution: "@formatjs/fast-memoize@npm:3.1.7"
|
||||
@@ -2772,6 +2784,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-messageformat-parser@npm:2.11.2":
|
||||
version: 2.11.2
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:2.11.2"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:2.3.4"
|
||||
"@formatjs/icu-skeleton-parser": "npm:1.8.14"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/e919eb2a132ac1d54fb1a7e3a3254007649b55196d3818090df92a4268dcddf20cbdf863c06039fbbe7a35a8a3f17bdc172dade99d1f17c1d8a95dcec444c3e3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-messageformat-parser@npm:3.5.16":
|
||||
version: 3.5.16
|
||||
resolution: "@formatjs/icu-messageformat-parser@npm:3.5.16"
|
||||
@@ -2781,6 +2804,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-skeleton-parser@npm:1.8.14":
|
||||
version: 1.8.14
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:1.8.14"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:2.3.4"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/2fbe3155c310358820b118d8c9844f314eff3500a82f1c65402434a3095823e1afeaab8d1762b4a59cc5679d82dc4c8c134683565d7cdae4daace23251f46a47
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/icu-skeleton-parser@npm:2.1.11":
|
||||
version: 2.1.11
|
||||
resolution: "@formatjs/icu-skeleton-parser@npm:2.1.11"
|
||||
@@ -2843,6 +2876,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-localematcher@npm:0.6.1":
|
||||
version: 0.6.1
|
||||
resolution: "@formatjs/intl-localematcher@npm:0.6.1"
|
||||
dependencies:
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/c7b3bc8395d18670677f207b2fd107561fff5d6394a9b4273c29e0bea920300ec3a2eefead600ebb7761c04a770cada28f78ac059f84d00520bfb57a9db36998
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl-localematcher@npm:0.8.13":
|
||||
version: 0.8.13
|
||||
resolution: "@formatjs/intl-localematcher@npm:0.8.13"
|
||||
@@ -2890,6 +2932,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@formatjs/intl@npm:3.1.6":
|
||||
version: 3.1.6
|
||||
resolution: "@formatjs/intl@npm:3.1.6"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:2.3.4"
|
||||
"@formatjs/fast-memoize": "npm:2.2.7"
|
||||
"@formatjs/icu-messageformat-parser": "npm:2.11.2"
|
||||
intl-messageformat: "npm:10.7.16"
|
||||
tslib: "npm:^2.8.0"
|
||||
peerDependencies:
|
||||
typescript: ^5.6.0
|
||||
peerDependenciesMeta:
|
||||
typescript:
|
||||
optional: true
|
||||
checksum: 10/10ebdce088898ad7de59c10890f7c02fa9f5aa50518ed3fae7e0ae1c392f3973da00464d2a42229cce97916c2eff1e41630e95c18bab01713d2b6ef5c7fd80c1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@fullcalendar/core@npm:6.1.21":
|
||||
version: 6.1.21
|
||||
resolution: "@fullcalendar/core@npm:6.1.21"
|
||||
@@ -4765,110 +4825,110 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-arm64@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:2.1.8"
|
||||
"@rspack/binding-darwin-arm64@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-darwin-arm64@npm:2.1.7"
|
||||
conditions: os=darwin & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-darwin-x64@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-darwin-x64@npm:2.1.8"
|
||||
"@rspack/binding-darwin-x64@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-darwin-x64@npm:2.1.7"
|
||||
conditions: os=darwin & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-gnu@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:2.1.8"
|
||||
"@rspack/binding-linux-arm64-gnu@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-arm64-gnu@npm:2.1.7"
|
||||
conditions: os=linux & cpu=arm64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-arm64-musl@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:2.1.8"
|
||||
"@rspack/binding-linux-arm64-musl@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-arm64-musl@npm:2.1.7"
|
||||
conditions: os=linux & cpu=arm64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-riscv64-gnu@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-riscv64-gnu@npm:2.1.8"
|
||||
"@rspack/binding-linux-riscv64-gnu@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-riscv64-gnu@npm:2.1.7"
|
||||
conditions: os=linux & cpu=riscv64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-riscv64-musl@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-riscv64-musl@npm:2.1.8"
|
||||
"@rspack/binding-linux-riscv64-musl@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-riscv64-musl@npm:2.1.7"
|
||||
conditions: os=linux & cpu=riscv64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-gnu@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:2.1.8"
|
||||
"@rspack/binding-linux-x64-gnu@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-x64-gnu@npm:2.1.7"
|
||||
conditions: os=linux & cpu=x64 & libc=glibc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-linux-x64-musl@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:2.1.8"
|
||||
"@rspack/binding-linux-x64-musl@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-linux-x64-musl@npm:2.1.7"
|
||||
conditions: os=linux & cpu=x64 & libc=musl
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-wasm32-wasi@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:2.1.8"
|
||||
"@rspack/binding-wasm32-wasi@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-wasm32-wasi@npm:2.1.7"
|
||||
dependencies:
|
||||
"@emnapi/core": "npm:1.11.3"
|
||||
"@emnapi/runtime": "npm:1.11.3"
|
||||
"@emnapi/core": "npm:1.11.2"
|
||||
"@emnapi/runtime": "npm:1.11.2"
|
||||
"@napi-rs/wasm-runtime": "npm:1.1.6"
|
||||
conditions: cpu=wasm32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-arm64-msvc@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:2.1.8"
|
||||
"@rspack/binding-win32-arm64-msvc@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-win32-arm64-msvc@npm:2.1.7"
|
||||
conditions: os=win32 & cpu=arm64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-ia32-msvc@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:2.1.8"
|
||||
"@rspack/binding-win32-ia32-msvc@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-win32-ia32-msvc@npm:2.1.7"
|
||||
conditions: os=win32 & cpu=ia32
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding-win32-x64-msvc@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:2.1.8"
|
||||
"@rspack/binding-win32-x64-msvc@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding-win32-x64-msvc@npm:2.1.7"
|
||||
conditions: os=win32 & cpu=x64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/binding@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/binding@npm:2.1.8"
|
||||
"@rspack/binding@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/binding@npm:2.1.7"
|
||||
dependencies:
|
||||
"@rspack/binding-darwin-arm64": "npm:2.1.8"
|
||||
"@rspack/binding-darwin-x64": "npm:2.1.8"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:2.1.8"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:2.1.8"
|
||||
"@rspack/binding-linux-riscv64-gnu": "npm:2.1.8"
|
||||
"@rspack/binding-linux-riscv64-musl": "npm:2.1.8"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:2.1.8"
|
||||
"@rspack/binding-linux-x64-musl": "npm:2.1.8"
|
||||
"@rspack/binding-wasm32-wasi": "npm:2.1.8"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:2.1.8"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:2.1.8"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:2.1.8"
|
||||
"@rspack/binding-darwin-arm64": "npm:2.1.7"
|
||||
"@rspack/binding-darwin-x64": "npm:2.1.7"
|
||||
"@rspack/binding-linux-arm64-gnu": "npm:2.1.7"
|
||||
"@rspack/binding-linux-arm64-musl": "npm:2.1.7"
|
||||
"@rspack/binding-linux-riscv64-gnu": "npm:2.1.7"
|
||||
"@rspack/binding-linux-riscv64-musl": "npm:2.1.7"
|
||||
"@rspack/binding-linux-x64-gnu": "npm:2.1.7"
|
||||
"@rspack/binding-linux-x64-musl": "npm:2.1.7"
|
||||
"@rspack/binding-wasm32-wasi": "npm:2.1.7"
|
||||
"@rspack/binding-win32-arm64-msvc": "npm:2.1.7"
|
||||
"@rspack/binding-win32-ia32-msvc": "npm:2.1.7"
|
||||
"@rspack/binding-win32-x64-msvc": "npm:2.1.7"
|
||||
dependenciesMeta:
|
||||
"@rspack/binding-darwin-arm64":
|
||||
optional: true
|
||||
@@ -4894,15 +4954,15 @@ __metadata:
|
||||
optional: true
|
||||
"@rspack/binding-win32-x64-msvc":
|
||||
optional: true
|
||||
checksum: 10/74c73ac543059fa62365557ca949ef7a84e1cebc42fe51a343126c875d90f557b35a52b33bacd9498d9231e8fdb8ab13907988e1fd39131ffbd56becb4260785
|
||||
checksum: 10/1400d8553d2122850fc43a45b69828bcdcbccf7715381a627802e160da6f93e90ca41d0280a69d175894bc29a428a374c80157b874883363b998090dc0aff33c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/core@npm:2.1.8":
|
||||
version: 2.1.8
|
||||
resolution: "@rspack/core@npm:2.1.8"
|
||||
"@rspack/core@npm:2.1.7":
|
||||
version: 2.1.7
|
||||
resolution: "@rspack/core@npm:2.1.7"
|
||||
dependencies:
|
||||
"@rspack/binding": "npm:2.1.8"
|
||||
"@rspack/binding": "npm:2.1.7"
|
||||
peerDependencies:
|
||||
"@module-federation/runtime-tools": ^0.24.1 || ^2.0.0
|
||||
"@swc/helpers": ^0.5.23
|
||||
@@ -4911,7 +4971,7 @@ __metadata:
|
||||
optional: true
|
||||
"@swc/helpers":
|
||||
optional: true
|
||||
checksum: 10/c818257225a24feb22af81eb3ed7d02d4b74d6e6cb70689c8cbcd34e3facfcfe833dfe4078e0a35277e926f431cae9fd7f2101c3944b396a72baa7cf3ce99d19
|
||||
checksum: 10/84526df889fa2813cfa7dfbed71b713c47c2010b1f55da26db698a87ea11f3f433a931ec29616ff300cb63d0ea27cbe7d2b40df64abf45f9b1a484b59c94ca1a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4927,9 +4987,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@rspack/dev-server@npm:2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "@rspack/dev-server@npm:2.2.0"
|
||||
"@rspack/dev-server@npm:2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "@rspack/dev-server@npm:2.1.0"
|
||||
dependencies:
|
||||
"@rspack/dev-middleware": "npm:^2.0.3"
|
||||
peerDependencies:
|
||||
@@ -4938,7 +4998,7 @@ __metadata:
|
||||
peerDependenciesMeta:
|
||||
selfsigned:
|
||||
optional: true
|
||||
checksum: 10/edfc57223fad64c43c96be95b0b6650194ac2e02d929bef649d524faa9c618233afe8f970ac2e21c77e1b92f6c65dac20f06817e73a9bd661f43c73057ae6b6b
|
||||
checksum: 10/402d4f96c60beaba354081a36b053c3cf7c1dda7fddab791031dc4206b9873b98437895d5a6a68247e07cb8a5922a1770143c560772dfdaa00ba90a5396251d8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -5771,105 +5831,105 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.66.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.65.0"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.12.2"
|
||||
"@typescript-eslint/scope-manager": "npm:8.66.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.66.0"
|
||||
"@typescript-eslint/utils": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.65.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.65.0"
|
||||
"@typescript-eslint/utils": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
ignore: "npm:^7.0.5"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.66.0
|
||||
"@typescript-eslint/parser": ^8.65.0
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/bb144ff0c27592b6a53d964fe1e2145dd0737f5fe50fe7dcd88217ca5ac4e470d7965d97745a63de16252ac2880d00732a472b9cec14d7d6929ebda97e7bcbb1
|
||||
checksum: 10/20b1e5fd0c01d450c345750582e4911affef8ba934b2b78953ff593102d2a01f21c5f6469e13239fdd6a0e30152d6122906e7623f53aa8c937c6faae9407be47
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.66.0"
|
||||
"@typescript-eslint/parser@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.65.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/a0451abe17d2eeff6ff2e1bf637d5ea19b400378d98f4511672e0f30dc5a7971d03352e32764927176c3632f245d5f85e8b28a0a3e66d020effcf0c61624e9b6
|
||||
checksum: 10/55f68666953c02c8adae35a46076848da6456181b1849a28ec836a5866ca37b902f475fb4c32ba7aa3a6a7e5d66828df8859edec297da09181fb937aeea30f8e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.66.0"
|
||||
"@typescript-eslint/project-service@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.65.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.66.0"
|
||||
"@typescript-eslint/types": "npm:^8.66.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.65.0"
|
||||
"@typescript-eslint/types": "npm:^8.65.0"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/e8a71f69ee7f4bd9ca1c6a0a5110361b9927d17df2f41cdf6d042203b49aabfbddece79dec7882fde8dc016b6278671a2ba3f7bf08e5ae241582627b68be3a8b
|
||||
checksum: 10/915662449a66d90f03661a805f7c62a5efaa8f7887671272e08b684b41edab51a9021f47aac4d78001a0f87960e8587adc037424d56f13de883e0ce699e7ca55
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.66.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.65.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
checksum: 10/f2024cd2819dc3b967f32089ea1a0b9099acabf12860699432f0ba802d75b01ed35ea488a50576cae544813d5cbe9bd9ee5815bc4f11cefc89fe2fb24559238f
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
checksum: 10/038e208c907aa45fe5bb7168e1dccf89c1fc6678d1715a9c04f4b332d4e79ab719b5b43a4e0c2d5a183ea863813ff59fda040b2717fe5517468453af6b99a50a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.66.0, @typescript-eslint/tsconfig-utils@npm:^8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.66.0"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.65.0, @typescript-eslint/tsconfig-utils@npm:^8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.65.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/271b28660edc9e6272ae07a91f8da2c69e8a6b009e8588f1009c485c11fe4e66e09356d50dbeabf8e6a44c95c8adeb630f1b6089bdd68aec2512d0036cef7704
|
||||
checksum: 10/f88253a4df1d599a1bebeeb403611538485e22c52213f2f2b9c435bde8ebce64645d6bb985c900b01ef221032835fcd4f6fea4b94d178220c18de5d93af905c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.66.0"
|
||||
"@typescript-eslint/type-utils@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.65.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
"@typescript-eslint/utils": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
"@typescript-eslint/utils": "npm:8.65.0"
|
||||
debug: "npm:^4.4.3"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/6d3c55a591b96cbac4ac845ad1d281bac54df12a631ca550370d266aed30760fa9d498e44b938e454941930e3decda820c6e544e0748254c5089425d120e1ed1
|
||||
checksum: 10/d52e0c341c9731d8f3bfd2f475bbcd5a5632434e11fc39e8e21acb706145bedb8c6c1998b8ced73e132b43179dd95f2c318effc36c9a1dd61f14546b07b25852
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.66.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/types@npm:8.66.0"
|
||||
checksum: 10/de1ea79e7056c11e38e4001a899b88511793a0aa1702508af42ee179bc7303bed2eb02f177a7ae438f20eab00aee9407c7e842bec6fc5b7bb1fdcab4a7cd5c4b
|
||||
"@typescript-eslint/types@npm:8.65.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/types@npm:8.65.0"
|
||||
checksum: 10/a6fc10a733adbb98bbb9c312c8d99791e1a2fd3efef5c2a5b51da1c166aac3db4427c30bf32059808abe305a289f820bf610683914e897baeb18315af6a1d16c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.65.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.66.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.66.0"
|
||||
"@typescript-eslint/project-service": "npm:8.65.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.65.0"
|
||||
debug: "npm:^4.4.3"
|
||||
minimatch: "npm:^10.2.2"
|
||||
semver: "npm:^7.7.3"
|
||||
@@ -5877,32 +5937,32 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/0834efcdd4468c0dd954089daf26b40175e204402bd4075b06cd910786bb6de7a15aec4e79e40f0980f39fce5bfd9c871ec8fcc6871a8895a34a273bb187fd26
|
||||
checksum: 10/711fdb5eff67ff34437c56a1a661b16a2e1d6bcd96c9f96196c4242b8d4ddaea8e835ca8badd340c703e043d8dfe8cfca90b32eca60069a4f1d2880afdb670fb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.66.0"
|
||||
"@typescript-eslint/utils@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.65.0"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.9.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.65.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/e7a24ee0f35d58b4392daa03fb1ea37ab7e4519949a034be89151df1b0e9fc416831f50cb69f54150272e252cba350964632ee9bfbdb7e62ac599cc731de56e1
|
||||
checksum: 10/c1dcd555b58aef1e066164978335e521809acac36b56bd6a6dae62cffae80f3ea5f43527506be76dfef0fe3d4c8382a24355a28867c4904b0a7729691ba45656
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.66.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.65.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.66.0"
|
||||
"@typescript-eslint/types": "npm:8.65.0"
|
||||
eslint-visitor-keys: "npm:^5.0.0"
|
||||
checksum: 10/d47e936b10ec94a96f69f77a62463f4634fe72d12d42dd302d7777f7b98689468e0b67c538a52232b723695208f1b3fdeae5745eb3e71a70a90818fdd827a0bc
|
||||
checksum: 10/e7f86d21f0bf03ca7cff9fa9428aab98620564d15fb06c9f56a294c13cee324624d42f9115f3d76fbad92266220479cca334b5c66562f885da5c4d2bda3d87b3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -7501,6 +7561,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chart2music@npm:1.20.0, chart2music@npm:^1.20.0":
|
||||
version: 1.20.0
|
||||
resolution: "chart2music@npm:1.20.0"
|
||||
dependencies:
|
||||
"@formatjs/intl": "npm:3.1.6"
|
||||
checksum: 10/ed421708740e3644a72356c9f313d3bcb7cac26d94a7fb209fa4bc8f9bc24061ed63f98da89277684e71f9402b5414cd45bb60f01c01e576803ba3dca755f4ba
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chokidar@npm:^3.5.3":
|
||||
version: 3.6.0
|
||||
resolution: "chokidar@npm:3.6.0"
|
||||
@@ -7854,10 +7923,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"core-js@npm:3.50.0":
|
||||
version: 3.50.0
|
||||
resolution: "core-js@npm:3.50.0"
|
||||
checksum: 10/9690bd111d8500437e65b365ec0dbe8d8899e99e877452d142c72bfbf272bd6edbf38ec9c2da8fa88d9186bba9246309bff68cedd2a6c43a3bf0748625e0e092
|
||||
"core-js@npm:3.49.0":
|
||||
version: 3.49.0
|
||||
resolution: "core-js@npm:3.49.0"
|
||||
checksum: 10/31d018f9830b0240ae40869e380595f2d06a8800709ad63299a42a438ba0c8d5805045fa02a20a78f42761d83103b0b71eca982955f5e890fb7cf6b2fe6a9ab1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8098,7 +8167,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decimal.js@npm:^10.6.0":
|
||||
"decimal.js@npm:^10.4.3, decimal.js@npm:^10.6.0":
|
||||
version: 10.6.0
|
||||
resolution: "decimal.js@npm:10.6.0"
|
||||
checksum: 10/c0d45842d47c311d11b38ce7ccc911121953d4df3ebb1465d92b31970eb4f6738a065426a06094af59bee4b0d64e42e7c8984abd57b6767c64ea90cf90bb4a69
|
||||
@@ -8364,6 +8433,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"echarts-extension-chart2music@npm:0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "echarts-extension-chart2music@npm:0.1.0"
|
||||
dependencies:
|
||||
chart2music: "npm:^1.20.0"
|
||||
peerDependencies:
|
||||
echarts: ">=5.0.0 <7"
|
||||
checksum: 10/55a82c770355228b2a899007e488e5b60171cbaf5d83363a298c4214d9e4a5df1afd1c7805495f42f2ad6edd7c062c8b366f1f8ba7b1a902f2db7eb47fbfcccf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"echarts@npm:6.1.0":
|
||||
version: 6.1.0
|
||||
resolution: "echarts@npm:6.1.0"
|
||||
@@ -9868,10 +9948,10 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"hls.js@npm:1.6.17":
|
||||
version: 1.6.17
|
||||
resolution: "hls.js@npm:1.6.17"
|
||||
checksum: 10/345d6c98f7f32f25f1924d34d4177f37aa9ae10c95fe6518c4ca10b295acbf174fbeba113696fbce673a1ee90461b530e101dd2b2f4d958af82577706d6e24c2
|
||||
"hls.js@npm:1.6.16":
|
||||
version: 1.6.16
|
||||
resolution: "hls.js@npm:1.6.16"
|
||||
checksum: 10/478a4c29a4246dff0f1209b2808f1e2e6651ef584df51fab23f6bbcb582e464f17552b9b76f3043dd98b8ae388961e5174a10c1f3dd55ec2ba7587c6bb3bd7f3
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -9895,7 +9975,7 @@ __metadata:
|
||||
"@codemirror/lint": "npm:6.9.7"
|
||||
"@codemirror/search": "npm:6.7.1"
|
||||
"@codemirror/state": "npm:6.7.1"
|
||||
"@codemirror/view": "npm:6.43.8"
|
||||
"@codemirror/view": "npm:6.43.7"
|
||||
"@date-fns/tz": "npm:1.5.0"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@eslint/js": "npm:10.0.1"
|
||||
@@ -9936,8 +10016,8 @@ __metadata:
|
||||
"@playwright/test": "npm:1.62.1"
|
||||
"@replit/codemirror-indentation-markers": "npm:6.5.3"
|
||||
"@rsdoctor/rspack-plugin": "npm:1.6.1"
|
||||
"@rspack/core": "npm:2.1.8"
|
||||
"@rspack/dev-server": "npm:2.2.0"
|
||||
"@rspack/core": "npm:2.1.7"
|
||||
"@rspack/dev-server": "npm:2.1.0"
|
||||
"@swc/helpers": "npm:0.5.23"
|
||||
"@thomasloven/round-slider": "npm:0.6.0"
|
||||
"@tsparticles/engine": "npm:4.3.2"
|
||||
@@ -9967,9 +10047,10 @@ __metadata:
|
||||
barcode-detector: "npm:3.2.1"
|
||||
browserslist-useragent-regexp: "npm:4.1.4"
|
||||
cally: "npm:0.9.2"
|
||||
chart2music: "npm:1.20.0"
|
||||
color-name: "npm:2.1.1"
|
||||
comlink: "npm:4.4.2"
|
||||
core-js: "npm:3.50.0"
|
||||
core-js: "npm:3.49.0"
|
||||
cropperjs: "npm:1.6.2"
|
||||
culori: "npm:4.0.2"
|
||||
date-fns: "npm:4.4.0"
|
||||
@@ -9978,6 +10059,7 @@ __metadata:
|
||||
del: "npm:8.0.1"
|
||||
dialog-polyfill: "npm:0.5.6"
|
||||
echarts: "npm:6.1.0"
|
||||
echarts-extension-chart2music: "npm:0.1.0"
|
||||
element-internals-polyfill: "npm:3.0.2"
|
||||
eslint: "npm:10.8.0"
|
||||
eslint-config-prettier: "npm:10.1.8"
|
||||
@@ -9996,7 +10078,7 @@ __metadata:
|
||||
gulp: "npm:5.0.1"
|
||||
gulp-json-transform: "npm:0.5.0"
|
||||
gulp-rename: "npm:2.1.0"
|
||||
hls.js: "npm:1.6.17"
|
||||
hls.js: "npm:1.6.16"
|
||||
home-assistant-js-websocket: "npm:9.6.0"
|
||||
html-minifier-terser: "npm:7.2.0"
|
||||
husky: "npm:9.1.7"
|
||||
@@ -10017,7 +10099,7 @@ __metadata:
|
||||
lodash.template: "npm:4.18.1"
|
||||
luxon: "npm:3.7.2"
|
||||
map-stream: "npm:0.0.7"
|
||||
marked: "npm:18.0.9"
|
||||
marked: "npm:18.0.7"
|
||||
memoize-one: "npm:6.0.0"
|
||||
minify-literals: "npm:2.1.0"
|
||||
node-vibrant: "npm:4.0.4"
|
||||
@@ -10040,7 +10122,7 @@ __metadata:
|
||||
tinykeys: "patch:tinykeys@npm%3A4.0.0#~/.yarn/patches/tinykeys-npm-4.0.0-a6ca3fd771.patch"
|
||||
ts-lit-plugin: "npm:2.0.2"
|
||||
typescript: "npm:6.0.3"
|
||||
typescript-eslint: "npm:8.66.0"
|
||||
typescript-eslint: "npm:8.65.0"
|
||||
vite-tsconfig-paths: "npm:6.1.1"
|
||||
vitest: "npm:4.1.10"
|
||||
webpack-stats-plugin: "npm:1.1.3"
|
||||
@@ -10349,6 +10431,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intl-messageformat@npm:10.7.16":
|
||||
version: 10.7.16
|
||||
resolution: "intl-messageformat@npm:10.7.16"
|
||||
dependencies:
|
||||
"@formatjs/ecma402-abstract": "npm:2.3.4"
|
||||
"@formatjs/fast-memoize": "npm:2.2.7"
|
||||
"@formatjs/icu-messageformat-parser": "npm:2.11.2"
|
||||
tslib: "npm:^2.8.0"
|
||||
checksum: 10/c19b77c5e495ce8b0d1aa0d95444bf3a4f73886805f1e08d7159b364abcf2f63686b2ccf202eaafb0e39a0e9fde61848b8dd2db1679efd4f6ec8f6a3d0e77928
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"intl-messageformat@npm:11.2.13":
|
||||
version: 11.2.13
|
||||
resolution: "intl-messageformat@npm:11.2.13"
|
||||
@@ -11660,12 +11754,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"marked@npm:18.0.9":
|
||||
version: 18.0.9
|
||||
resolution: "marked@npm:18.0.9"
|
||||
"marked@npm:18.0.7":
|
||||
version: 18.0.7
|
||||
resolution: "marked@npm:18.0.7"
|
||||
bin:
|
||||
marked: bin/marked.js
|
||||
checksum: 10/99d337c50acd57034734f8460f25b28e9658b15627f950092707cb443b840f0bd29fe343bf211b948e643303c34abdb5b5a12cf5245a846635750697524bb747
|
||||
checksum: 10/7ea7b8556a9e8cab2881b194815a7550c61d77639c6b8f8d9022f96790fd2b289b2ea28969cea6e01c24c3b8ec822373288ee02b7c50f178bc3ec79aa42d05f1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -14892,18 +14986,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.66.0":
|
||||
version: 8.66.0
|
||||
resolution: "typescript-eslint@npm:8.66.0"
|
||||
"typescript-eslint@npm:8.65.0":
|
||||
version: 8.65.0
|
||||
resolution: "typescript-eslint@npm:8.65.0"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.66.0"
|
||||
"@typescript-eslint/parser": "npm:8.66.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.66.0"
|
||||
"@typescript-eslint/utils": "npm:8.66.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.65.0"
|
||||
"@typescript-eslint/parser": "npm:8.65.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.65.0"
|
||||
"@typescript-eslint/utils": "npm:8.65.0"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/e76da9c684e4fe012b49a5120dd57553df0788cf1cffcbd5dc72c98de33e549f306495e710292f111ee058366b1e5d8d603c2bb14385b12bb35edc7bbf13ad55
|
||||
checksum: 10/5b2242f59005afdd57190849be7df2e86ce33b007fa0bf605f700ad0e38ea14fc14c48deafdf4a039614c1f012b71c61a19edb27f76d52fdc924cde476a100d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user