mirror of
https://github.com/home-assistant/frontend.git
synced 2026-06-14 04:12:16 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 900efcba6c |
@@ -30,6 +30,7 @@ jobs:
|
||||
"Do Not Review",
|
||||
"Blocked",
|
||||
"has-parent",
|
||||
"Needs Template",
|
||||
];
|
||||
const prLabels = context.payload.pull_request.labels.map(
|
||||
(l) => l.name
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
name: Pull request standards
|
||||
|
||||
on:
|
||||
pull_request_target: # zizmor: ignore[dangerous-triggers] -- safe: reads PR metadata from event payload only, no PR code checkout
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- reopened
|
||||
- ready_for_review
|
||||
branches:
|
||||
- dev
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check pull request follows contribution standards
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write # To label and comment on pull requests
|
||||
steps:
|
||||
- name: Check pull request standards
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||
with:
|
||||
script: |
|
||||
const pr = context.payload.pull_request;
|
||||
|
||||
// Exempt bots (Copilot agent, dependabot), drafts, and maintainers.
|
||||
if (pr.user.type === "Bot") {
|
||||
core.info(`Skipping bot author: ${pr.user.login}`);
|
||||
return;
|
||||
}
|
||||
if (pr.draft) {
|
||||
core.info("Skipping draft pull request");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await github.rest.orgs.checkMembershipForUser({
|
||||
org: "home-assistant",
|
||||
username: pr.user.login,
|
||||
});
|
||||
core.info(`Skipping organization member: ${pr.user.login}`);
|
||||
return;
|
||||
} catch (error) {
|
||||
core.info(`${pr.user.login} is not an organization member, checking standards`);
|
||||
}
|
||||
|
||||
const label = "Needs Template";
|
||||
const marker = "<!-- pr-standards-check -->";
|
||||
const { owner, repo } = context.repo;
|
||||
const issue_number = pr.number;
|
||||
|
||||
const body = (pr.body || "").replace(/<!--[\s\S]*?-->/g, "");
|
||||
const normalized = body.toLowerCase();
|
||||
|
||||
// Ignore 404s from mutations that race manual edits or cancelled runs.
|
||||
const ignoreMissing = async (fn) => {
|
||||
try {
|
||||
await fn();
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
core.info("Target already removed, nothing to do");
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Hide/restore our comment via GraphQL (REST cannot minimize).
|
||||
const setMinimized = async (subjectId, minimized) => {
|
||||
const mutation = minimized
|
||||
? `mutation($id: ID!) {
|
||||
minimizeComment(input: { subjectId: $id, classifier: RESOLVED }) {
|
||||
clientMutationId
|
||||
}
|
||||
}`
|
||||
: `mutation($id: ID!) {
|
||||
unminimizeComment(input: { subjectId: $id }) {
|
||||
clientMutationId
|
||||
}
|
||||
}`;
|
||||
try {
|
||||
await github.graphql(mutation, { id: subjectId });
|
||||
} catch (error) {
|
||||
core.info(
|
||||
`Could not ${minimized ? "minimize" : "restore"} comment: ${error.message}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Content of a "## <name>" section, or null when the heading is absent.
|
||||
const section = (name) => {
|
||||
const match = body.match(
|
||||
new RegExp(`##\\s*${name}([\\s\\S]*?)(?=\\n##\\s|$)`, "i")
|
||||
);
|
||||
return match ? match[1] : null;
|
||||
};
|
||||
|
||||
const problems = [];
|
||||
|
||||
const requiredHeadings = [
|
||||
"## proposed change",
|
||||
"## type of change",
|
||||
"## checklist",
|
||||
];
|
||||
if (requiredHeadings.some((h) => !normalized.includes(h))) {
|
||||
problems.push(
|
||||
"Use the pull request template without removing its sections."
|
||||
);
|
||||
}
|
||||
|
||||
const typeOfChange = section("type of change");
|
||||
if (typeOfChange !== null) {
|
||||
const ticked = (typeOfChange.match(/-\s*\[[xX]\]/g) || []).length;
|
||||
if (ticked !== 1) {
|
||||
problems.push(
|
||||
'Select exactly one option under "Type of change".'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const proposedChange = section("proposed change");
|
||||
if (proposedChange !== null && proposedChange.trim().length === 0) {
|
||||
problems.push('Describe your changes under "Proposed change".');
|
||||
}
|
||||
|
||||
const isValid = problems.length === 0;
|
||||
|
||||
const comments = await github.paginate(
|
||||
github.rest.issues.listComments,
|
||||
{ owner, repo, issue_number, per_page: 100 }
|
||||
);
|
||||
const existing = comments.find((c) => c.body.includes(marker));
|
||||
const hasLabel = pr.labels.some((l) => l.name === label);
|
||||
|
||||
if (isValid) {
|
||||
core.info("Pull request standards met");
|
||||
|
||||
if (hasLabel) {
|
||||
await ignoreMissing(() =>
|
||||
github.rest.issues.removeLabel({
|
||||
owner, repo, issue_number, name: label,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (existing) {
|
||||
await setMinimized(existing.node_id, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
core.info(`Pull request standards not met:\n- ${problems.join("\n- ")}`);
|
||||
|
||||
if (!hasLabel) {
|
||||
await github.rest.issues.addLabels({
|
||||
owner, repo, issue_number, labels: [label],
|
||||
});
|
||||
}
|
||||
|
||||
const message =
|
||||
`${marker}\n` +
|
||||
`Hey @${pr.user.login}!\n\n` +
|
||||
`Thank you for your contribution! To help reviewers, please update ` +
|
||||
`this pull request to follow our pull request standards:\n\n` +
|
||||
problems.map((p) => `- ${p}`).join("\n") +
|
||||
`\n\n` +
|
||||
`Please complete the ` +
|
||||
`[PR template](https://github.com/home-assistant/frontend/blob/dev/.github/PULL_REQUEST_TEMPLATE.md?plain=1) ` +
|
||||
`and see the [developer docs](https://developers.home-assistant.io/docs/review-process) ` +
|
||||
`for more on creating a great pull request (see point 6).`;
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner, repo, comment_id: existing.id, body: message,
|
||||
});
|
||||
await setMinimized(existing.node_id, false);
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner, repo, issue_number, body: message,
|
||||
});
|
||||
}
|
||||
+4
-4
@@ -34,10 +34,10 @@
|
||||
"@codemirror/lang-jinja": "6.0.1",
|
||||
"@codemirror/lang-yaml": "6.1.3",
|
||||
"@codemirror/language": "6.12.3",
|
||||
"@codemirror/lint": "6.9.7",
|
||||
"@codemirror/lint": "6.9.6",
|
||||
"@codemirror/search": "6.7.0",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.43.1",
|
||||
"@codemirror/view": "6.43.0",
|
||||
"@date-fns/tz": "1.5.0",
|
||||
"@egjs/hammerjs": "2.0.17",
|
||||
"@formatjs/intl-datetimeformat": "7.4.8",
|
||||
@@ -186,7 +186,7 @@
|
||||
"lodash.template": "4.18.1",
|
||||
"map-stream": "0.0.7",
|
||||
"pinst": "3.0.0",
|
||||
"prettier": "3.8.4",
|
||||
"prettier": "3.8.3",
|
||||
"rspack-manifest-plugin": "5.2.2",
|
||||
"serve": "14.2.6",
|
||||
"sinon": "22.0.0",
|
||||
@@ -194,7 +194,7 @@
|
||||
"terser-webpack-plugin": "5.6.1",
|
||||
"ts-lit-plugin": "2.0.2",
|
||||
"typescript": "6.0.3",
|
||||
"typescript-eslint": "8.61.0",
|
||||
"typescript-eslint": "8.60.1",
|
||||
"vite-tsconfig-paths": "6.1.1",
|
||||
"vitest": "4.1.8",
|
||||
"webpack-stats-plugin": "1.1.3",
|
||||
|
||||
@@ -1,23 +1,5 @@
|
||||
import type { LineSeriesOption } from "echarts";
|
||||
|
||||
type Point = NonNullable<LineSeriesOption["data"]>[number];
|
||||
|
||||
interface MeanFrame {
|
||||
sumX: number;
|
||||
sumY: number;
|
||||
count: number;
|
||||
isArray: boolean;
|
||||
}
|
||||
|
||||
interface MinMaxFrame {
|
||||
minPoint: Point;
|
||||
minX: number;
|
||||
minY: number;
|
||||
maxPoint: Point;
|
||||
maxX: number;
|
||||
maxY: number;
|
||||
}
|
||||
|
||||
export function downSampleLineData<
|
||||
T extends [number, number] | NonNullable<LineSeriesOption["data"]>[number],
|
||||
>(
|
||||
@@ -37,47 +19,11 @@ export function downSampleLineData<
|
||||
const max = maxX ?? getPointData(data[data.length - 1]!)[0];
|
||||
const step = Math.ceil((max - min) / Math.floor(maxDetails));
|
||||
|
||||
if (useMean) {
|
||||
// Group points into frames, accumulating sums in insertion order.
|
||||
const frames = new Map<number, MeanFrame>();
|
||||
|
||||
for (const point of data) {
|
||||
const pointData = getPointData(point);
|
||||
if (!Array.isArray(pointData)) continue;
|
||||
const x = Number(pointData[0]);
|
||||
const y = Number(pointData[1]);
|
||||
if (isNaN(x) || isNaN(y)) continue;
|
||||
|
||||
const frameIndex = Math.floor((x - min) / step);
|
||||
const frame = frames.get(frameIndex);
|
||||
if (!frame) {
|
||||
frames.set(frameIndex, {
|
||||
sumX: x,
|
||||
sumY: y,
|
||||
count: 1,
|
||||
isArray: Array.isArray(pointData),
|
||||
});
|
||||
} else {
|
||||
frame.sumX += x;
|
||||
frame.sumY += y;
|
||||
frame.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const result: T[] = [];
|
||||
for (const frame of frames.values()) {
|
||||
const meanX = frame.sumX / frame.count;
|
||||
const meanY = frame.sumY / frame.count;
|
||||
const meanPoint = (
|
||||
frame.isArray ? [meanX, meanY] : { value: [meanX, meanY] }
|
||||
) as T;
|
||||
result.push(meanPoint);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Min/max mode: track the min and max point per frame in insertion order.
|
||||
const frames = new Map<number, MinMaxFrame>();
|
||||
// Group points into frames
|
||||
const frames = new Map<
|
||||
number,
|
||||
{ point: (typeof data)[number]; x: number; y: number }[]
|
||||
>();
|
||||
|
||||
for (const point of data) {
|
||||
const pointData = getPointData(point);
|
||||
@@ -89,39 +35,53 @@ export function downSampleLineData<
|
||||
const frameIndex = Math.floor((x - min) / step);
|
||||
const frame = frames.get(frameIndex);
|
||||
if (!frame) {
|
||||
frames.set(frameIndex, {
|
||||
minPoint: point,
|
||||
minX: x,
|
||||
minY: y,
|
||||
maxPoint: point,
|
||||
maxX: x,
|
||||
maxY: y,
|
||||
});
|
||||
frames.set(frameIndex, [{ point, x, y }]);
|
||||
} else {
|
||||
// Match the original strict-less / strict-greater comparisons so the
|
||||
// first occurrence wins on ties.
|
||||
if (y < frame.minY) {
|
||||
frame.minPoint = point;
|
||||
frame.minX = x;
|
||||
frame.minY = y;
|
||||
}
|
||||
if (y > frame.maxY) {
|
||||
frame.maxPoint = point;
|
||||
frame.maxX = x;
|
||||
frame.maxY = y;
|
||||
}
|
||||
frame.push({ point, x, y });
|
||||
}
|
||||
}
|
||||
|
||||
// Convert frames back to points
|
||||
const result: T[] = [];
|
||||
for (const frame of frames.values()) {
|
||||
// The order of the data must be preserved so max may be before min
|
||||
if (frame.minX > frame.maxX) {
|
||||
result.push(frame.maxPoint as T);
|
||||
|
||||
if (useMean) {
|
||||
// Use mean values for each frame
|
||||
for (const [_i, framePoints] of frames) {
|
||||
const sumY = framePoints.reduce((acc, p) => acc + p.y, 0);
|
||||
const meanY = sumY / framePoints.length;
|
||||
const sumX = framePoints.reduce((acc, p) => acc + p.x, 0);
|
||||
const meanX = sumX / framePoints.length;
|
||||
|
||||
const firstPoint = framePoints[0].point;
|
||||
const pointData = getPointData(firstPoint);
|
||||
const meanPoint = (
|
||||
Array.isArray(pointData) ? [meanX, meanY] : { value: [meanX, meanY] }
|
||||
) as T;
|
||||
result.push(meanPoint);
|
||||
}
|
||||
result.push(frame.minPoint as T);
|
||||
if (frame.minX < frame.maxX) {
|
||||
result.push(frame.maxPoint as T);
|
||||
} else {
|
||||
// Use min/max values for each frame
|
||||
for (const [_i, framePoints] of frames) {
|
||||
let minPoint = framePoints[0];
|
||||
let maxPoint = framePoints[0];
|
||||
|
||||
for (const p of framePoints) {
|
||||
if (p.y < minPoint.y) {
|
||||
minPoint = p;
|
||||
}
|
||||
if (p.y > maxPoint.y) {
|
||||
maxPoint = p;
|
||||
}
|
||||
}
|
||||
|
||||
// The order of the data must be preserved so max may be before min
|
||||
if (minPoint.x > maxPoint.x) {
|
||||
result.push(maxPoint.point);
|
||||
}
|
||||
result.push(minPoint.point);
|
||||
if (minPoint.x < maxPoint.x) {
|
||||
result.push(maxPoint.point);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1520,9 +1520,7 @@ export class HaChartBase extends LitElement {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
/* overflow: hidden clips descenders (e.g. "g", parentheses) with a tight
|
||||
line-height, so give the line box room to contain them */
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
line-height: 1;
|
||||
}
|
||||
@media (hover: hover) {
|
||||
.chart-legend .label.clickable:hover {
|
||||
@@ -1560,25 +1558,6 @@ export class HaChartBase extends LitElement {
|
||||
.chart-legend .legend-toggle ha-svg-icon {
|
||||
--mdc-icon-size: 18px;
|
||||
}
|
||||
/* On touch devices, enlarge the toggle tap target via taller rows and
|
||||
leading padding (which also separates it from the previous item), while
|
||||
keeping the icon tight to its own label so the pairing stays clear.
|
||||
Drop the now-pointless row gap and li padding. */
|
||||
@media (pointer: coarse) {
|
||||
.chart-legend ul {
|
||||
row-gap: 0;
|
||||
}
|
||||
/* Only grow the toggle rows, not the expand/collapse chip's row. */
|
||||
.chart-legend li:has(.legend-toggle) {
|
||||
height: 40px;
|
||||
padding: 0;
|
||||
}
|
||||
.chart-legend .legend-toggle {
|
||||
padding: 11px;
|
||||
padding-inline-end: 4px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
ha-assist-chip {
|
||||
height: 100%;
|
||||
--_label-text-weight: 500;
|
||||
|
||||
+1
-3
@@ -128,13 +128,11 @@ export const addMatterDevice = (hass: HomeAssistant) => {
|
||||
|
||||
export const commissionMatterDevice = (
|
||||
hass: HomeAssistant,
|
||||
code: string,
|
||||
networkOnly: boolean
|
||||
code: string
|
||||
): Promise<void> =>
|
||||
hass.callWS({
|
||||
type: "matter/commission",
|
||||
code,
|
||||
network_only: networkOnly,
|
||||
});
|
||||
|
||||
export const acceptSharedMatterDevice = (
|
||||
|
||||
@@ -10,21 +10,13 @@ import "../../components/ha-button";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import type { ConfigEntryMutableParams } from "../../data/config_entries";
|
||||
import { updateConfigEntry } from "../../data/config_entries";
|
||||
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { showAlertDialog } from "../generic/show-dialog-box";
|
||||
import type { ConfigEntrySystemOptionsDialogParams } from "./show-dialog-config-entry-system-options";
|
||||
|
||||
interface SystemOptionsState {
|
||||
disableNewEntities: boolean;
|
||||
disablePolling: boolean;
|
||||
}
|
||||
|
||||
@customElement("dialog-config-entry-system-options")
|
||||
class DialogConfigEntrySystemOptions extends DirtyStateProviderMixin<SystemOptionsState>()(
|
||||
LitElement
|
||||
) {
|
||||
class DialogConfigEntrySystemOptions extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _disableNewEntities!: boolean;
|
||||
@@ -46,13 +38,6 @@ class DialogConfigEntrySystemOptions extends DirtyStateProviderMixin<SystemOptio
|
||||
this._error = undefined;
|
||||
this._disableNewEntities = params.entry.pref_disable_new_entities;
|
||||
this._disablePolling = params.entry.pref_disable_polling;
|
||||
this._initDirtyTracking(
|
||||
{ type: "shallow" },
|
||||
{
|
||||
disableNewEntities: this._disableNewEntities,
|
||||
disablePolling: this._disablePolling,
|
||||
}
|
||||
);
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
@@ -83,7 +68,7 @@ class DialogConfigEntrySystemOptions extends DirtyStateProviderMixin<SystemOptio
|
||||
) || this._params.entry.domain,
|
||||
}
|
||||
)}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${this._error ? html` <div class="error">${this._error}</div> ` : ""}
|
||||
@@ -150,7 +135,7 @@ class DialogConfigEntrySystemOptions extends DirtyStateProviderMixin<SystemOptio
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${this._submitting || !this.isDirtyState}
|
||||
.disabled=${this._submitting}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.dialogs.config_entry_system_options.update"
|
||||
@@ -164,19 +149,11 @@ class DialogConfigEntrySystemOptions extends DirtyStateProviderMixin<SystemOptio
|
||||
private _disableNewEntitiesChanged(ev: Event): void {
|
||||
this._error = undefined;
|
||||
this._disableNewEntities = !(ev.target as HaSwitch).checked;
|
||||
this._updateDirtyState({
|
||||
disableNewEntities: this._disableNewEntities,
|
||||
disablePolling: this._disablePolling,
|
||||
});
|
||||
}
|
||||
|
||||
private _disablePollingChanged(ev: Event): void {
|
||||
this._error = undefined;
|
||||
this._disablePolling = !(ev.target as HaSwitch).checked;
|
||||
this._updateDirtyState({
|
||||
disableNewEntities: this._disableNewEntities,
|
||||
disablePolling: this._disablePolling,
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateEntry(): Promise<void> {
|
||||
|
||||
@@ -19,64 +19,6 @@ declare const __WB_MANIFEST__: Parameters<typeof precacheAndRoute>[0];
|
||||
const noFallBackRegEx =
|
||||
/\/(api|static|auth|frontend_latest|frontend_es5|local)\/.*/;
|
||||
|
||||
// Camera / image proxy endpoints that carry credentials in the URL.
|
||||
// We pre-validate the credential in the service worker so obviously invalid
|
||||
// requests (signature expired, token missing) never reach the server and
|
||||
// don't trigger spurious "Login attempt" warnings from http.ban after BFCache
|
||||
// restore, tab resume, network change, or any other browser-initiated replay
|
||||
// of a stale `<img>` URL.
|
||||
const proxyPathRegEx =
|
||||
/^\/api\/(camera_proxy_stream|camera_proxy|image_proxy)\//;
|
||||
|
||||
// Reject signatures this many ms before their nominal expiry to absorb small
|
||||
// client/server clock differences. Erring this direction only ever turns a
|
||||
// would-be valid request into a local 401; we cannot err the other way without
|
||||
// re-introducing the warnings this filter exists to prevent.
|
||||
const JWT_EXPIRY_SKEW_MS = 5000;
|
||||
|
||||
const base64UrlDecode = (input: string): string => {
|
||||
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
||||
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
||||
return atob(padded);
|
||||
};
|
||||
|
||||
const isJwtExpired = (jwt: string): boolean => {
|
||||
try {
|
||||
const parts = jwt.split(".");
|
||||
if (parts.length !== 3) {
|
||||
return false;
|
||||
}
|
||||
const payload = JSON.parse(base64UrlDecode(parts[1]));
|
||||
if (typeof payload.exp !== "number") {
|
||||
return false;
|
||||
}
|
||||
return payload.exp * 1000 < Date.now() + JWT_EXPIRY_SKEW_MS;
|
||||
} catch (_err) {
|
||||
// If we can't parse the JWT for any reason, defer to the server.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleProxyRequest: RouteHandler = async ({ request }) => {
|
||||
const req = request as Request;
|
||||
const url = new URL(req.url);
|
||||
|
||||
const token = url.searchParams.get("token");
|
||||
if (token === "undefined" || token === "null" || token === "") {
|
||||
return new Response(null, { status: 401, statusText: "Invalid token" });
|
||||
}
|
||||
|
||||
const authSig = url.searchParams.get("authSig");
|
||||
if (authSig && isJwtExpired(authSig)) {
|
||||
return new Response(null, {
|
||||
status: 401,
|
||||
statusText: "Signature expired",
|
||||
});
|
||||
}
|
||||
|
||||
return fetch(req);
|
||||
};
|
||||
|
||||
const initRouting = () => {
|
||||
precacheAndRoute(__WB_MANIFEST__, {
|
||||
// Ignore all URL parameters.
|
||||
@@ -117,15 +59,6 @@ const initRouting = () => {
|
||||
})
|
||||
);
|
||||
|
||||
// Short-circuit camera/image proxy requests with an expired signature or a
|
||||
// missing/undefined token so they don't hit core and get logged as invalid
|
||||
// login attempts. Registered before the generic /api route below so it wins.
|
||||
registerRoute(
|
||||
({ url, request }) =>
|
||||
proxyPathRegEx.test(url.pathname) && request.method === "GET",
|
||||
handleProxyRequest
|
||||
);
|
||||
|
||||
// Get api from network.
|
||||
registerRoute(/\/(api|auth)\/.*/, new NetworkOnly());
|
||||
|
||||
|
||||
@@ -23,28 +23,18 @@ import {
|
||||
} from "../../../data/application_credential";
|
||||
import type { IntegrationManifest } from "../../../data/integration";
|
||||
import { domainToName } from "../../../data/integration";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { documentationUrl } from "../../../util/documentation-url";
|
||||
import type { AddApplicationCredentialDialogParams } from "./show-dialog-add-application-credential";
|
||||
|
||||
interface CredentialFormState {
|
||||
domain: string;
|
||||
name: string;
|
||||
clientId: string;
|
||||
clientSecret: string;
|
||||
}
|
||||
|
||||
interface Domain {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
@customElement("dialog-add-application-credential")
|
||||
export class DialogAddApplicationCredential extends DirtyStateProviderMixin<CredentialFormState>()(
|
||||
LitElement
|
||||
) {
|
||||
export class DialogAddApplicationCredential extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _loading = false;
|
||||
@@ -86,7 +76,6 @@ export class DialogAddApplicationCredential extends DirtyStateProviderMixin<Cred
|
||||
this._error = undefined;
|
||||
this._loading = false;
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "shallow" }, this._currentState());
|
||||
this._fetchConfig();
|
||||
}
|
||||
|
||||
@@ -111,7 +100,10 @@ export class DialogAddApplicationCredential extends DirtyStateProviderMixin<Cred
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
@closed=${this._abortDialog}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
.preventScrimClose=${!!this._domain ||
|
||||
!!this._name ||
|
||||
!!this._clientId ||
|
||||
!!this._clientSecret}
|
||||
.headerTitle=${this.hass.localize(
|
||||
"ui.panel.config.application_credentials.editor.caption"
|
||||
)}
|
||||
@@ -292,7 +284,6 @@ export class DialogAddApplicationCredential extends DirtyStateProviderMixin<Cred
|
||||
ev.stopPropagation();
|
||||
this._domain = ev.detail.value;
|
||||
this._updateDescription();
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private async _updateDescription() {
|
||||
@@ -316,16 +307,6 @@ export class DialogAddApplicationCredential extends DirtyStateProviderMixin<Cred
|
||||
const name = (ev.target as any).name;
|
||||
const value = (ev.target as any).value;
|
||||
this[`_${name}`] = value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _currentState(): CredentialFormState {
|
||||
return {
|
||||
domain: this._domain || "",
|
||||
name: this._name || "",
|
||||
clientId: this._clientId || "",
|
||||
clientSecret: this._clientSecret || "",
|
||||
};
|
||||
}
|
||||
|
||||
private _abortDialog() {
|
||||
|
||||
@@ -32,7 +32,6 @@ import {
|
||||
import { extractApiErrorMessage } from "../../../../../data/hassio/common";
|
||||
import type { ObjectSelector, Selector } from "../../../../../data/selector";
|
||||
import { showConfirmationDialog } from "../../../../../dialogs/generic/show-dialog-box";
|
||||
import { DirtyStateProviderMixin } from "../../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
|
||||
@@ -57,15 +56,15 @@ const ADDON_YAML_SCHEMA = DEFAULT_SCHEMA.extend([
|
||||
const MASKED_FIELDS = ["password", "secret", "token"];
|
||||
|
||||
@customElement("supervisor-app-config")
|
||||
class SupervisorAppConfig extends DirtyStateProviderMixin<
|
||||
Record<string, unknown>
|
||||
>()(LitElement) {
|
||||
class SupervisorAppConfig extends LitElement {
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public disabled = false;
|
||||
|
||||
@state() private _configHasChanged = false;
|
||||
|
||||
@state() private _valid = true;
|
||||
|
||||
@state() private _canShowSchema = false;
|
||||
@@ -352,7 +351,9 @@ class SupervisorAppConfig extends DirtyStateProviderMixin<
|
||||
<div class="card-actions right">
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${this.disabled || !this.isDirtyState || !this._valid}
|
||||
.disabled=${this.disabled ||
|
||||
!this._configHasChanged ||
|
||||
!this._valid}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-progress-button>
|
||||
@@ -376,7 +377,6 @@ class SupervisorAppConfig extends DirtyStateProviderMixin<
|
||||
protected updated(changedProperties: PropertyValues): void {
|
||||
if (changedProperties.has("addon")) {
|
||||
this._options = { ...this.addon.options };
|
||||
this._initDirtyTracking({ type: "deep" }, this.addon.options);
|
||||
}
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
@@ -415,13 +415,11 @@ class SupervisorAppConfig extends DirtyStateProviderMixin<
|
||||
private _configChanged(ev): void {
|
||||
if (this.addon.schema && this._canShowSchema && !this._yamlMode) {
|
||||
this._valid = true;
|
||||
this._configHasChanged = true;
|
||||
this._options = ev.detail.value;
|
||||
this._updateDirtyState(ev.detail.value);
|
||||
} else {
|
||||
this._configHasChanged = true;
|
||||
this._valid = ev.detail.isValid;
|
||||
if (ev.detail.isValid) {
|
||||
this._updateDirtyState(ev.detail.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,7 +450,7 @@ class SupervisorAppConfig extends DirtyStateProviderMixin<
|
||||
};
|
||||
try {
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||
this._markDirtyStateClean();
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
@@ -471,7 +469,7 @@ class SupervisorAppConfig extends DirtyStateProviderMixin<
|
||||
}
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
if (this.disabled || !this.isDirtyState || !this._valid) {
|
||||
if (this.disabled || !this._configHasChanged || !this._valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -501,7 +499,7 @@ class SupervisorAppConfig extends DirtyStateProviderMixin<
|
||||
options,
|
||||
});
|
||||
|
||||
this._markDirtyStateClean();
|
||||
this._configHasChanged = false;
|
||||
if (this.addon?.state === "started") {
|
||||
await suggestSupervisorAppRestart(this, this.hass, this.addon);
|
||||
}
|
||||
|
||||
@@ -15,16 +15,13 @@ import type {
|
||||
} from "../../../../../data/hassio/addon";
|
||||
import { setHassioAddonOption } from "../../../../../data/hassio/addon";
|
||||
import { extractApiErrorMessage } from "../../../../../data/hassio/common";
|
||||
import { DirtyStateProviderMixin } from "../../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle } from "../../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import { supervisorAppsStyle } from "../../resources/supervisor-apps-style";
|
||||
import { suggestSupervisorAppRestart } from "../dialogs/suggestSupervisorAppRestart";
|
||||
|
||||
@customElement("supervisor-app-network")
|
||||
class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
Record<string, number | null>
|
||||
>()(LitElement) {
|
||||
class SupervisorAppNetwork extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public addon!: HassioAddonDetails;
|
||||
@@ -33,19 +30,19 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
|
||||
@state() private _showOptional = false;
|
||||
|
||||
@state() private _configHasChanged = false;
|
||||
|
||||
@state() private _error?: string;
|
||||
|
||||
@state() private _config?: Record<string, number | null>;
|
||||
@state() private _config?: Record<string, any>;
|
||||
|
||||
protected render() {
|
||||
if (!this._config) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const config = this._config;
|
||||
|
||||
const hasHiddenOptions = Object.keys(config).find(
|
||||
(entry) => config[entry] === null
|
||||
const hasHiddenOptions = Object.keys(this._config).find(
|
||||
(entry) => this._config![entry] === null
|
||||
);
|
||||
|
||||
return html`
|
||||
@@ -101,7 +98,7 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
</ha-progress-button>
|
||||
<ha-progress-button
|
||||
@click=${this._saveTapped}
|
||||
.disabled=${!this.isDirtyState || this.disabled}
|
||||
.disabled=${!this._configHasChanged || this.disabled}
|
||||
>
|
||||
${this.hass.localize("ui.common.save")}
|
||||
</ha-progress-button>
|
||||
@@ -118,10 +115,7 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
}
|
||||
|
||||
private _createSchema = memoizeOne(
|
||||
(
|
||||
config: Record<string, number | null>,
|
||||
showOptional: boolean
|
||||
): HaFormSchema[] =>
|
||||
(config: Record<string, number>, showOptional: boolean): HaFormSchema[] =>
|
||||
(showOptional
|
||||
? Object.keys(config)
|
||||
: Object.keys(config).filter((entry) => config[entry] !== null)
|
||||
@@ -147,14 +141,12 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
item.name;
|
||||
|
||||
private _setNetworkConfig(): void {
|
||||
const config = this.addon.network || {};
|
||||
this._config = config;
|
||||
this._initDirtyTracking({ type: "shallow" }, config);
|
||||
this._config = this.addon.network || {};
|
||||
}
|
||||
|
||||
private _configChanged(ev: CustomEvent): void {
|
||||
private async _configChanged(ev: CustomEvent): Promise<void> {
|
||||
this._configHasChanged = true;
|
||||
this._config = ev.detail.value;
|
||||
this._updateDirtyState(ev.detail.value);
|
||||
}
|
||||
|
||||
private async _resetTapped(ev: CustomEvent): Promise<void> {
|
||||
@@ -169,7 +161,7 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||
this._markDirtyStateClean();
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
@@ -196,14 +188,14 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
}
|
||||
|
||||
private async _saveTapped(ev: CustomEvent): Promise<void> {
|
||||
if (!this.isDirtyState || this.disabled) {
|
||||
if (!this._configHasChanged || this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const button = ev.currentTarget as any;
|
||||
|
||||
this._error = undefined;
|
||||
const networkconfiguration: Record<string, number | null> = {};
|
||||
const networkconfiguration = {};
|
||||
Object.entries(this._config!).forEach(([key, value]) => {
|
||||
networkconfiguration[key] = value ?? null;
|
||||
});
|
||||
@@ -214,7 +206,7 @@ class SupervisorAppNetwork extends DirtyStateProviderMixin<
|
||||
|
||||
try {
|
||||
await setHassioAddonOption(this.hass.callWS, this.addon.slug, data);
|
||||
this._markDirtyStateClean();
|
||||
this._configHasChanged = false;
|
||||
const eventdata = {
|
||||
success: true,
|
||||
response: undefined,
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { CSSResultGroup } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import "../../../components/entity/ha-entity-picker";
|
||||
import type { HaEntityPicker } from "../../../components/entity/ha-entity-picker";
|
||||
import "../../../components/ha-alert";
|
||||
@@ -55,20 +54,9 @@ const SENSOR_DOMAINS = ["sensor"];
|
||||
const TEMPERATURE_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_TEMPERATURE];
|
||||
const HUMIDITY_DEVICE_CLASSES = [SENSOR_DEVICE_CLASS_HUMIDITY];
|
||||
|
||||
interface AreaFormState {
|
||||
name: string;
|
||||
aliases: string[];
|
||||
labels: string[];
|
||||
picture: string | null;
|
||||
icon: string | null;
|
||||
floor: string | null;
|
||||
temperatureEntity: string | null;
|
||||
humidityEntity: string | null;
|
||||
}
|
||||
|
||||
@customElement("dialog-area-registry-detail")
|
||||
class DialogAreaDetail
|
||||
extends DirtyStateProviderMixin<AreaFormState>()(LitElement)
|
||||
extends LitElement
|
||||
implements HassDialog<AreaRegistryDetailDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -128,23 +116,9 @@ class DialogAreaDetail
|
||||
this._humidityEntity = null;
|
||||
}
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, this._currentState());
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
private _currentState(): AreaFormState {
|
||||
return {
|
||||
name: this._name,
|
||||
aliases: this._aliases,
|
||||
labels: this._labels,
|
||||
picture: this._picture,
|
||||
icon: this._icon,
|
||||
floor: this._floor,
|
||||
temperatureEntity: this._temperatureEntity,
|
||||
humidityEntity: this._humidityEntity,
|
||||
};
|
||||
}
|
||||
|
||||
public closeDialog(): boolean {
|
||||
this._open = false;
|
||||
return true;
|
||||
@@ -352,8 +326,6 @@ class DialogAreaDetail
|
||||
if (processed.floor) {
|
||||
this._floor = processed.floor;
|
||||
}
|
||||
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
protected render() {
|
||||
@@ -371,7 +343,7 @@ class DialogAreaDetail
|
||||
header-title=${entry
|
||||
? this.hass.localize("ui.panel.config.areas.editor.update_area")
|
||||
: this.hass.localize("ui.panel.config.areas.editor.create_area")}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-suggest-with-ai-button
|
||||
@@ -412,9 +384,7 @@ class DialogAreaDetail
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${nameInvalid ||
|
||||
this._submitting ||
|
||||
(!!this._params?.entry && !this.isDirtyState)}
|
||||
.disabled=${nameInvalid || this._submitting}
|
||||
>
|
||||
${entry
|
||||
? this.hass.localize("ui.common.save")
|
||||
@@ -448,43 +418,36 @@ class DialogAreaDetail
|
||||
private _nameChanged(ev: InputEvent) {
|
||||
this._error = undefined;
|
||||
this._name = (ev.target as HaInput).value ?? "";
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _floorChanged(ev) {
|
||||
this._error = undefined;
|
||||
this._floor = ev.detail.value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _iconChanged(ev) {
|
||||
this._error = undefined;
|
||||
this._icon = ev.detail.value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _labelsChanged(ev) {
|
||||
this._error = undefined;
|
||||
this._labels = ev.detail.value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
|
||||
this._error = undefined;
|
||||
this._picture = (ev.target as HaPictureUpload).value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _aliasesChanged(ev: CustomEvent): void {
|
||||
this._aliases = ev.detail.value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _sensorChanged(ev: CustomEvent): void {
|
||||
const deviceClass = (ev.target as HaEntityPicker).includeDeviceClasses![0];
|
||||
const key = `_${deviceClass}Entity`;
|
||||
this[key] = ev.detail.value || null;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
@@ -506,7 +469,6 @@ class DialogAreaDetail
|
||||
} else {
|
||||
await this._params!.updateEntry!(values);
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error =
|
||||
|
||||
@@ -15,7 +15,6 @@ import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/item/ha-list-item-button";
|
||||
import "../../../../components/item/ha-row-item";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import type {
|
||||
BackupConfig,
|
||||
BackupMutableConfig,
|
||||
@@ -83,10 +82,7 @@ const RECOMMENDED_CONFIG: BackupConfig = {
|
||||
};
|
||||
|
||||
@customElement("ha-dialog-backup-onboarding")
|
||||
class DialogBackupOnboarding
|
||||
extends DirtyStateProviderMixin<BackupConfig>()(LitElement)
|
||||
implements HassDialog
|
||||
{
|
||||
class DialogBackupOnboarding extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _open = false;
|
||||
@@ -119,7 +115,6 @@ class DialogBackupOnboarding
|
||||
}
|
||||
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, this._config!);
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
@@ -174,7 +169,6 @@ class DialogBackupOnboarding
|
||||
try {
|
||||
await this._save(true);
|
||||
this._params?.submit!(true);
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -220,7 +214,7 @@ class DialogBackupOnboarding
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
header-title=${this._stepTitle}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${isFirstStep
|
||||
@@ -299,7 +293,6 @@ class DialogBackupOnboarding
|
||||
password: this._config.create_backup.password,
|
||||
},
|
||||
};
|
||||
this._updateDirtyState(this._config);
|
||||
this._done();
|
||||
}
|
||||
|
||||
@@ -522,7 +515,6 @@ class DialogBackupOnboarding
|
||||
include_addons: data.include_addons || null,
|
||||
},
|
||||
};
|
||||
this._updateDirtyState(this._config);
|
||||
}
|
||||
|
||||
private _scheduleChanged(ev) {
|
||||
@@ -532,7 +524,6 @@ class DialogBackupOnboarding
|
||||
schedule: value.schedule,
|
||||
retention: value.retention,
|
||||
};
|
||||
this._updateDirtyState(this._config);
|
||||
}
|
||||
|
||||
private _agentsConfigChanged(ev) {
|
||||
@@ -544,7 +535,6 @@ class DialogBackupOnboarding
|
||||
agent_ids: agents,
|
||||
},
|
||||
};
|
||||
this._updateDirtyState(this._config);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
|
||||
@@ -12,17 +12,13 @@ import {
|
||||
getPreferredAgentForDownload,
|
||||
} from "../../../../data/backup";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { downloadBackupFile } from "../helper/download_backup";
|
||||
import type { DownloadDecryptedBackupDialogParams } from "./show-dialog-download-decrypted-backup";
|
||||
|
||||
@customElement("ha-dialog-download-decrypted-backup")
|
||||
class DialogDownloadDecryptedBackup
|
||||
extends DirtyStateProviderMixin<string>()(LitElement)
|
||||
implements HassDialog
|
||||
{
|
||||
class DialogDownloadDecryptedBackup extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _open = false;
|
||||
@@ -36,7 +32,6 @@ class DialogDownloadDecryptedBackup
|
||||
public showDialog(params: DownloadDecryptedBackupDialogParams): void {
|
||||
this._open = true;
|
||||
this._params = params;
|
||||
this._initDirtyTracking({ type: "shallow" }, "");
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
@@ -65,7 +60,7 @@ class DialogDownloadDecryptedBackup
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.title"
|
||||
)}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<p>
|
||||
@@ -110,11 +105,7 @@ class DialogDownloadDecryptedBackup
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._submit}
|
||||
.disabled=${!this.isDirtyState}
|
||||
>
|
||||
<ha-button slot="primaryAction" @click=${this._submit}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.download.download"
|
||||
)}
|
||||
@@ -145,7 +136,6 @@ class DialogDownloadDecryptedBackup
|
||||
this._agentId,
|
||||
this._encryptionKey
|
||||
);
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
if (err?.code === "password_incorrect") {
|
||||
@@ -165,7 +155,6 @@ class DialogDownloadDecryptedBackup
|
||||
private _keyChanged(ev) {
|
||||
this._encryptionKey = ev.currentTarget.value;
|
||||
this._error = "";
|
||||
this._updateDirtyState(this._encryptionKey);
|
||||
}
|
||||
|
||||
private get _agentId() {
|
||||
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
fetchBackupConfig,
|
||||
} from "../../../../data/backup";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import "../components/config/ha-backup-config-data";
|
||||
@@ -60,10 +59,7 @@ const STEPS = ["data", "sync"] as const;
|
||||
const DISALLOWED_AGENTS_NO_HA = [CLOUD_AGENT];
|
||||
|
||||
@customElement("ha-dialog-generate-backup")
|
||||
class DialogGenerateBackup
|
||||
extends DirtyStateProviderMixin<FormData>()(LitElement)
|
||||
implements HassDialog
|
||||
{
|
||||
class DialogGenerateBackup extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _step?: "data" | "sync";
|
||||
@@ -83,8 +79,6 @@ class DialogGenerateBackup
|
||||
this._formData = INITIAL_DATA;
|
||||
this._params = _params;
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, INITIAL_DATA);
|
||||
this._updateDirtyState(this._formData);
|
||||
|
||||
this._fetchAgents();
|
||||
this._fetchBackupConfig();
|
||||
@@ -166,7 +160,6 @@ class DialogGenerateBackup
|
||||
agents_mode: "custom",
|
||||
agent_ids: filteredAgents,
|
||||
};
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,11 +180,7 @@ class DialogGenerateBackup
|
||||
const selectedAgents = this._formData.agent_ids;
|
||||
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-dialog .open=${this._open} @closed=${this._dialogClosed}>
|
||||
<ha-dialog-header slot="header">
|
||||
${isFirstStep
|
||||
? html`
|
||||
@@ -287,7 +276,6 @@ class DialogGenerateBackup
|
||||
...this._formData!,
|
||||
data,
|
||||
};
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
|
||||
private _renderSync() {
|
||||
@@ -382,7 +370,6 @@ class DialogGenerateBackup
|
||||
...this._formData!,
|
||||
agents_mode: value,
|
||||
};
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
|
||||
private _agentsChanged(ev) {
|
||||
@@ -390,7 +377,6 @@ class DialogGenerateBackup
|
||||
...this._formData!,
|
||||
agent_ids: ev.detail.value,
|
||||
};
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
|
||||
private _nameChanged(ev: InputEvent) {
|
||||
@@ -398,7 +384,6 @@ class DialogGenerateBackup
|
||||
...this._formData!,
|
||||
name: (ev.target as HaInput).value ?? "",
|
||||
};
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
|
||||
private _disabledAgentIds() {
|
||||
@@ -444,7 +429,6 @@ class DialogGenerateBackup
|
||||
}
|
||||
|
||||
this._params!.submit?.(params);
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import type {
|
||||
} from "../../../../components/ha-form/types";
|
||||
import { extractApiErrorMessage } from "../../../../data/hassio/common";
|
||||
import { changeMountOptions } from "../../../../data/supervisor/mounts";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LocalBackupLocationDialogParams } from "./show-dialog-local-backup-location";
|
||||
@@ -26,14 +25,8 @@ const SCHEMA = [
|
||||
},
|
||||
] as const satisfies HaFormSchema[];
|
||||
|
||||
interface LocalBackupLocationFormState {
|
||||
default_backup_mount: string | null | undefined;
|
||||
}
|
||||
|
||||
@customElement("dialog-local-backup-location")
|
||||
class LocalBackupLocationDialog extends DirtyStateProviderMixin<LocalBackupLocationFormState>()(
|
||||
LitElement
|
||||
) {
|
||||
class LocalBackupLocationDialog extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _dialogParams?: LocalBackupLocationDialogParams;
|
||||
@@ -51,10 +44,6 @@ class LocalBackupLocationDialog extends DirtyStateProviderMixin<LocalBackupLocat
|
||||
): Promise<void> {
|
||||
this._dialogParams = dialogParams;
|
||||
this._open = true;
|
||||
this._initDirtyTracking(
|
||||
{ type: "shallow" },
|
||||
{ default_backup_mount: undefined }
|
||||
);
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -79,7 +68,7 @@ class LocalBackupLocationDialog extends DirtyStateProviderMixin<LocalBackupLocat
|
||||
header-title=${this.hass.localize(
|
||||
`ui.panel.config.backup.dialogs.local_backup_location.title`
|
||||
)}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${this._error
|
||||
@@ -113,7 +102,7 @@ class LocalBackupLocationDialog extends DirtyStateProviderMixin<LocalBackupLocat
|
||||
${this.hass.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
.disabled=${this._waiting || !this.isDirtyState}
|
||||
.disabled=${this._waiting || !this._data}
|
||||
slot="primaryAction"
|
||||
@click=${this._changeMount}
|
||||
>
|
||||
@@ -136,9 +125,6 @@ class LocalBackupLocationDialog extends DirtyStateProviderMixin<LocalBackupLocat
|
||||
this._data = {
|
||||
default_backup_mount: newLocation === "/backup" ? null : newLocation,
|
||||
};
|
||||
this._updateDirtyState({
|
||||
default_backup_mount: this._data.default_backup_mount,
|
||||
});
|
||||
}
|
||||
|
||||
private async _changeMount() {
|
||||
@@ -154,7 +140,6 @@ class LocalBackupLocationDialog extends DirtyStateProviderMixin<LocalBackupLocat
|
||||
this._waiting = false;
|
||||
return;
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.show_encryption_key.title"
|
||||
)}
|
||||
prevent-scrim-close
|
||||
@closed=${this.closeDialog}
|
||||
>
|
||||
<ha-icon-button
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
type BackupUploadFileFormData,
|
||||
} from "../../../../data/backup";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { showAlertDialog } from "../../../lovelace/custom-card-helpers";
|
||||
@@ -29,7 +28,7 @@ import type { UploadBackupDialogParams } from "./show-dialog-upload-backup";
|
||||
|
||||
@customElement("ha-dialog-upload-backup")
|
||||
export class DialogUploadBackup
|
||||
extends DirtyStateProviderMixin<BackupUploadFileFormData>()(LitElement)
|
||||
extends LitElement
|
||||
implements HassDialog<UploadBackupDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -48,8 +47,6 @@ export class DialogUploadBackup
|
||||
this._params = params;
|
||||
this._formData = INITIAL_UPLOAD_FORM_DATA;
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "shallow" }, INITIAL_UPLOAD_FORM_DATA);
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
|
||||
private _dialogClosed() {
|
||||
@@ -67,6 +64,10 @@ export class DialogUploadBackup
|
||||
return true;
|
||||
}
|
||||
|
||||
private _formValid() {
|
||||
return this._formData?.file !== undefined;
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._params || !this._formData) {
|
||||
return nothing;
|
||||
@@ -78,7 +79,7 @@ export class DialogUploadBackup
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.title"
|
||||
)}
|
||||
.preventScrimClose=${this.isDirtyState || this._uploading}
|
||||
?prevent-scrim-close=${this._uploading}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${this._error
|
||||
@@ -111,7 +112,7 @@ export class DialogUploadBackup
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._upload}
|
||||
.disabled=${!this.isDirtyState || this._uploading}
|
||||
.disabled=${!this._formValid() || this._uploading}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.backup.dialogs.upload.action"
|
||||
@@ -130,13 +131,11 @@ export class DialogUploadBackup
|
||||
...this._formData!,
|
||||
file,
|
||||
};
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
|
||||
private _filesCleared() {
|
||||
this._error = undefined;
|
||||
this._formData = INITIAL_UPLOAD_FORM_DATA;
|
||||
this._updateDirtyState(this._formData);
|
||||
}
|
||||
|
||||
private async _upload() {
|
||||
@@ -162,7 +161,6 @@ export class DialogUploadBackup
|
||||
try {
|
||||
await uploadBackup(this.hass, file, agentIds);
|
||||
this._params!.submit?.();
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = err.message;
|
||||
|
||||
@@ -18,7 +18,6 @@ import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-tooltip";
|
||||
import "../../../components/input/ha-input-search";
|
||||
import type { HaInputSearch } from "../../../components/input/ha-input-search";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { getConfigFlowHandlers } from "../../../data/config_flow";
|
||||
import { createCounter } from "../../../data/counter";
|
||||
import { createInputBoolean } from "../../../data/input_boolean";
|
||||
@@ -102,9 +101,7 @@ const HELPERS: HelperCreators = {
|
||||
};
|
||||
|
||||
@customElement("dialog-helper-detail")
|
||||
export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
Helper | undefined
|
||||
>()(LitElement) {
|
||||
export class DialogHelperDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _item?: Helper;
|
||||
@@ -140,7 +137,6 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
this._item = undefined;
|
||||
if (this._domain && this._domain in HELPERS) {
|
||||
await HELPERS[this._domain].import();
|
||||
this._initDirtyTracking({ type: "deep" }, undefined);
|
||||
}
|
||||
this._open = true;
|
||||
await this.updateComplete;
|
||||
@@ -297,7 +293,6 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
header-title=${this._domain
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.helpers.dialog.create_platform",
|
||||
@@ -369,7 +364,6 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
|
||||
private _valueChanged(ev: CustomEvent): void {
|
||||
this._item = ev.detail.value;
|
||||
this._updateDirtyState(this._item);
|
||||
}
|
||||
|
||||
private async _createItem(): Promise<void> {
|
||||
@@ -389,7 +383,6 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
entityId: `${this._domain}.${createdEntity.id}`,
|
||||
});
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = err.message || "Unknown error";
|
||||
@@ -417,7 +410,6 @@ export class DialogHelperDetail extends DirtyStateProviderMixin<
|
||||
try {
|
||||
await HELPERS[domain].import();
|
||||
this._domain = domain;
|
||||
this._initDirtyTracking({ type: "deep" }, undefined);
|
||||
} finally {
|
||||
this._loading = false;
|
||||
}
|
||||
|
||||
+1
-1
@@ -283,7 +283,7 @@ class DialogMatterAddDevice extends LitElement {
|
||||
const savedStep = this._step;
|
||||
try {
|
||||
this._step = "commissioning";
|
||||
await commissionMatterDevice(this.hass, code, true);
|
||||
await commissionMatterDevice(this.hass, code);
|
||||
} catch (_err) {
|
||||
showToast(this, {
|
||||
message: this.hass.localize(
|
||||
|
||||
@@ -211,7 +211,7 @@ class MatterOptionsPage extends LitElement {
|
||||
this._error = undefined;
|
||||
this._redirectOnNewMatterDevice();
|
||||
try {
|
||||
await commissionMatterDevice(this.hass, code, false);
|
||||
await commissionMatterDevice(this.hass, code);
|
||||
} catch (err: any) {
|
||||
this._error = err.message;
|
||||
this._stopRedirect();
|
||||
|
||||
+2
-8
@@ -6,16 +6,13 @@ import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-dialog";
|
||||
import type { LovelaceStrategyConfig } from "../../../../data/lovelace/config/strategy";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import "../../../lovelace/editor/dashboard-strategy-editor/hui-dashboard-strategy-element-editor";
|
||||
import type { LovelaceDashboardConfigureStrategyDialogParams } from "./show-dialog-lovelace-dashboard-configure-strategy";
|
||||
|
||||
@customElement("dialog-lovelace-dashboard-configure-strategy")
|
||||
export class DialogLovelaceDashboardConfigureStrategy extends DirtyStateProviderMixin<LovelaceStrategyConfig>()(
|
||||
LitElement
|
||||
) {
|
||||
export class DialogLovelaceDashboardConfigureStrategy extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: LovelaceDashboardConfigureStrategyDialogParams;
|
||||
@@ -32,7 +29,6 @@ export class DialogLovelaceDashboardConfigureStrategy extends DirtyStateProvider
|
||||
this._params = params;
|
||||
this._data = params.config.strategy;
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -53,7 +49,7 @@ export class DialogLovelaceDashboardConfigureStrategy extends DirtyStateProvider
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
|
||||
)}
|
||||
@@ -84,7 +80,6 @@ export class DialogLovelaceDashboardConfigureStrategy extends DirtyStateProvider
|
||||
|
||||
private _handleConfigChanged(ev: CustomEvent): void {
|
||||
this._data = ev.detail.config;
|
||||
this._updateDirtyState(this._data!);
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
@@ -97,7 +92,6 @@ export class DialogLovelaceDashboardConfigureStrategy extends DirtyStateProvider
|
||||
strategy: this._data,
|
||||
});
|
||||
this._submitting = false;
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,16 +14,13 @@ import type {
|
||||
LovelaceDashboardCreateParams,
|
||||
LovelaceDashboardMutableParams,
|
||||
} from "../../../../data/lovelace/dashboard";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceDashboardDetailsDialogParams } from "./show-dialog-lovelace-dashboard-detail";
|
||||
import { pickAvailableDashboardUrlPath } from "./pick-available-dashboard-url-path";
|
||||
|
||||
@customElement("dialog-lovelace-dashboard-detail")
|
||||
export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
Partial<LovelaceDashboard>
|
||||
>()(LitElement) {
|
||||
export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: LovelaceDashboardDetailsDialogParams;
|
||||
@@ -45,7 +42,6 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
this._open = true;
|
||||
if (this._params.dashboard) {
|
||||
this._data = this._params.dashboard;
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
} else {
|
||||
const suggestions = this._params.suggestions;
|
||||
this._data = {
|
||||
@@ -55,12 +51,9 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
require_admin: false,
|
||||
mode: "storage",
|
||||
};
|
||||
// New dashboards have no saved baseline, so track against an emptyobject to mark them dirty from the outset (keeps Create enabled).
|
||||
this._initDirtyTracking({ type: "deep" }, {});
|
||||
if (suggestions?.title) {
|
||||
this._fillUrlPath(suggestions.title);
|
||||
}
|
||||
this._updateDirtyState(this._data!);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +72,6 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const yamlMode = this._params.dashboard?.mode === "yaml";
|
||||
|
||||
const titleInvalid = !this._data.title || !this._data.title.trim();
|
||||
|
||||
const cancelButton = html`
|
||||
@@ -104,11 +95,11 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.new_dashboard"
|
||||
)}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div>
|
||||
${yamlMode
|
||||
${this._params.dashboard?.mode === "yaml"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.cant_edit_yaml"
|
||||
)
|
||||
@@ -151,12 +142,10 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateDashboard}
|
||||
.disabled=${!yamlMode &&
|
||||
((this._error && "url_path" in this._error) ||
|
||||
titleInvalid ||
|
||||
this._submitting ||
|
||||
!this.isDirtyState)}
|
||||
?autofocus=${yamlMode}
|
||||
.disabled=${(this._error && "url_path" in this._error) ||
|
||||
titleInvalid ||
|
||||
this._submitting}
|
||||
?autofocus=${this._params.dashboard?.mode === "yaml"}
|
||||
>
|
||||
${this._params.urlPath
|
||||
? this._params.dashboard?.mode === "storage"
|
||||
@@ -262,7 +251,6 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
} else {
|
||||
this._data = value;
|
||||
}
|
||||
this._updateDirtyState(this._data!);
|
||||
}
|
||||
|
||||
private _fillUrlPath(title: string) {
|
||||
@@ -282,7 +270,6 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
? pickAvailableDashboardUrlPath(baseSlug, taken)
|
||||
: baseSlug,
|
||||
};
|
||||
this._updateDirtyState(this._data!);
|
||||
}
|
||||
|
||||
private async _updateDashboard() {
|
||||
@@ -305,7 +292,6 @@ export class DialogLovelaceDashboardDetail extends DirtyStateProviderMixin<
|
||||
this._data as LovelaceDashboardCreateParams
|
||||
);
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
let localizedErrorMessage: string | undefined;
|
||||
|
||||
@@ -13,7 +13,6 @@ import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-dialog";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { PanelMutableParams } from "../../../../data/panel";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { PanelDetailDialogParams } from "./show-dialog-panel-detail";
|
||||
@@ -26,9 +25,7 @@ interface PanelDetailData {
|
||||
}
|
||||
|
||||
@customElement("dialog-panel-detail")
|
||||
export class DialogPanelDetail extends DirtyStateProviderMixin<PanelDetailData>()(
|
||||
LitElement
|
||||
) {
|
||||
export class DialogPanelDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: PanelDetailDialogParams;
|
||||
@@ -51,7 +48,6 @@ export class DialogPanelDetail extends DirtyStateProviderMixin<PanelDetailData>(
|
||||
show_in_sidebar: params.showInSidebar,
|
||||
};
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -74,7 +70,7 @@ export class DialogPanelDetail extends DirtyStateProviderMixin<PanelDetailData>(
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.panel_detail.edit_panel"
|
||||
)}
|
||||
@@ -118,7 +114,7 @@ export class DialogPanelDetail extends DirtyStateProviderMixin<PanelDetailData>(
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updatePanel}
|
||||
.disabled=${titleInvalid || this._submitting || !this.isDirtyState}
|
||||
.disabled=${titleInvalid || this._submitting}
|
||||
>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.lovelace.dashboards.detail.update"
|
||||
@@ -175,7 +171,6 @@ export class DialogPanelDetail extends DirtyStateProviderMixin<PanelDetailData>(
|
||||
private _valueChanged(ev: CustomEvent) {
|
||||
this._error = undefined;
|
||||
this._data = ev.detail.value;
|
||||
this._updateDirtyState(this._data!);
|
||||
}
|
||||
|
||||
private async _handleError(err: any) {
|
||||
@@ -233,7 +228,6 @@ export class DialogPanelDetail extends DirtyStateProviderMixin<PanelDetailData>(
|
||||
if (Object.keys(updates).length > 0) {
|
||||
await this._params!.updatePanel(updates);
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._handleError(err);
|
||||
|
||||
@@ -9,7 +9,6 @@ import "../../../../components/ha-form/ha-form";
|
||||
import "../../../../components/ha-button";
|
||||
import type { SchemaUnion } from "../../../../components/ha-form/types";
|
||||
import type { LovelaceResourcesMutableParams } from "../../../../data/lovelace/resource";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { LovelaceResourceDetailsDialogParams } from "./show-dialog-lovelace-resource-detail";
|
||||
|
||||
@@ -31,9 +30,7 @@ const detectResourceType = (url?: string) => {
|
||||
};
|
||||
|
||||
@customElement("dialog-lovelace-resource-detail")
|
||||
export class DialogLovelaceResourceDetail extends DirtyStateProviderMixin<
|
||||
Partial<LovelaceResourcesMutableParams>
|
||||
>()(LitElement) {
|
||||
export class DialogLovelaceResourceDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: LovelaceResourceDetailsDialogParams;
|
||||
@@ -60,7 +57,6 @@ export class DialogLovelaceResourceDetail extends DirtyStateProviderMixin<
|
||||
};
|
||||
}
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -87,7 +83,7 @@ export class DialogLovelaceResourceDetail extends DirtyStateProviderMixin<
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
header-title=${dialogTitle}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
@@ -123,10 +119,7 @@ export class DialogLovelaceResourceDetail extends DirtyStateProviderMixin<
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateResource}
|
||||
.disabled=${urlInvalid ||
|
||||
!this._data?.res_type ||
|
||||
this._submitting ||
|
||||
!this.isDirtyState}
|
||||
.disabled=${urlInvalid || !this._data?.res_type || this._submitting}
|
||||
>
|
||||
${this._params.resource
|
||||
? this.hass!.localize(
|
||||
@@ -210,7 +203,6 @@ export class DialogLovelaceResourceDetail extends DirtyStateProviderMixin<
|
||||
if (!this._data!.res_type) {
|
||||
const type = detectResourceType(this._data!.url);
|
||||
if (!type) {
|
||||
this._updateDirtyState(this._data!);
|
||||
return;
|
||||
}
|
||||
this._data = {
|
||||
@@ -218,7 +210,6 @@ export class DialogLovelaceResourceDetail extends DirtyStateProviderMixin<
|
||||
res_type: type,
|
||||
};
|
||||
}
|
||||
this._updateDirtyState(this._data!);
|
||||
}
|
||||
|
||||
private async _updateResource() {
|
||||
@@ -235,8 +226,7 @@ export class DialogLovelaceResourceDetail extends DirtyStateProviderMixin<
|
||||
this._data! as LovelaceResourcesMutableParams
|
||||
);
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
this._params = undefined;
|
||||
} catch (err: any) {
|
||||
this._error = { base: err?.message || "Unknown error" };
|
||||
} finally {
|
||||
|
||||
@@ -13,7 +13,6 @@ import "../../../components/ha-picture-upload";
|
||||
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||
import "../../../components/input/ha-input";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { adminChangeUsername } from "../../../data/auth";
|
||||
import type { PersonMutableParams } from "../../../data/person";
|
||||
import type { User } from "../../../data/user";
|
||||
@@ -45,20 +44,8 @@ const cropOptions: CropOptions = {
|
||||
aspectRatio: 1,
|
||||
};
|
||||
|
||||
interface PersonFormState {
|
||||
name: string;
|
||||
picture: string | null;
|
||||
userId: string | undefined;
|
||||
deviceTrackers: string[];
|
||||
isAdmin: boolean | undefined;
|
||||
localOnly: boolean | undefined;
|
||||
}
|
||||
|
||||
@customElement("dialog-person-detail")
|
||||
class DialogPersonDetail
|
||||
extends DirtyStateProviderMixin<PersonFormState>()(LitElement)
|
||||
implements HassDialog
|
||||
{
|
||||
class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -117,21 +104,9 @@ class DialogPersonDetail
|
||||
this._picture = null;
|
||||
}
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, this._currentState());
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
private _currentState(): PersonFormState {
|
||||
return {
|
||||
name: this._name,
|
||||
picture: this._picture,
|
||||
userId: this._userId,
|
||||
deviceTrackers: this._deviceTrackers,
|
||||
isAdmin: this._isAdmin,
|
||||
localOnly: this._localOnly,
|
||||
};
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
this._open = false;
|
||||
return true;
|
||||
@@ -159,7 +134,7 @@ class DialogPersonDetail
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
header-title=${this._params.entry
|
||||
? this._params.entry.name
|
||||
: this.hass!.localize("ui.panel.config.person.detail.new_person")}
|
||||
@@ -287,7 +262,7 @@ class DialogPersonDetail
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${nameInvalid || this._submitting || !this.isDirtyState}
|
||||
.disabled=${nameInvalid || this._submitting}
|
||||
>
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.common.save")
|
||||
@@ -392,17 +367,14 @@ class DialogPersonDetail
|
||||
private _nameChanged(ev: InputEvent) {
|
||||
this._error = undefined;
|
||||
this._name = (ev.target as HTMLInputElement).value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _adminChanged(ev): void {
|
||||
this._isAdmin = ev.target.checked;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _localOnlyChanged(ev): void {
|
||||
this._localOnly = ev.target.checked;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private async _allowLoginChanged(ev): Promise<void> {
|
||||
@@ -421,7 +393,6 @@ class DialogPersonDetail
|
||||
this._userId = user.id;
|
||||
this._isAdmin = user.group_ids.includes(SYSTEM_GROUP_ID_ADMIN);
|
||||
this._localOnly = user.local_only;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
},
|
||||
name: this._name,
|
||||
@@ -450,20 +421,17 @@ class DialogPersonDetail
|
||||
this._user = undefined;
|
||||
this._isAdmin = undefined;
|
||||
this._localOnly = undefined;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
}
|
||||
|
||||
private _deviceTrackersChanged(ev: ValueChangedEvent<string[]>) {
|
||||
this._error = undefined;
|
||||
this._deviceTrackers = ev.detail.value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private _pictureChanged(ev: ValueChangedEvent<string | null>) {
|
||||
this._error = undefined;
|
||||
this._picture = (ev.target as HaPictureUpload).value;
|
||||
this._updateDirtyState(this._currentState());
|
||||
}
|
||||
|
||||
private async _changePassword() {
|
||||
@@ -559,7 +527,6 @@ class DialogPersonDetail
|
||||
await this._params!.createEntry?.(values);
|
||||
this._personExists = true;
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = err ? err.message : "Unknown error";
|
||||
|
||||
@@ -20,24 +20,12 @@ import {
|
||||
createUser,
|
||||
deleteUser,
|
||||
} from "../../../data/user";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../types";
|
||||
import type { AddUserDialogParams } from "./show-dialog-add-user";
|
||||
|
||||
interface AddUserFormState {
|
||||
name?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
passwordConfirm?: string;
|
||||
isAdmin?: boolean;
|
||||
localOnly?: boolean;
|
||||
}
|
||||
|
||||
@customElement("dialog-add-user")
|
||||
export class DialogAddUser extends DirtyStateProviderMixin<AddUserFormState>()(
|
||||
LitElement
|
||||
) {
|
||||
export class DialogAddUser extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _loading = false;
|
||||
@@ -82,18 +70,6 @@ export class DialogAddUser extends DirtyStateProviderMixin<AddUserFormState>()(
|
||||
}
|
||||
|
||||
this._open = true;
|
||||
|
||||
this._initDirtyTracking(
|
||||
{ type: "shallow" },
|
||||
{
|
||||
name: this._name,
|
||||
username: this._username,
|
||||
password: "",
|
||||
passwordConfirm: "",
|
||||
isAdmin: false,
|
||||
localOnly: false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
protected firstUpdated(changedProperties: PropertyValues<this>) {
|
||||
@@ -113,7 +89,7 @@ export class DialogAddUser extends DirtyStateProviderMixin<AddUserFormState>()(
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.users.add_user.caption"
|
||||
)}
|
||||
@@ -266,7 +242,6 @@ export class DialogAddUser extends DirtyStateProviderMixin<AddUserFormState>()(
|
||||
|
||||
if (parts.length) {
|
||||
this._username = parts[0].toLowerCase();
|
||||
this._publishDirtyState();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,30 +249,16 @@ export class DialogAddUser extends DirtyStateProviderMixin<AddUserFormState>()(
|
||||
this._error = undefined;
|
||||
const target = ev.target as HaInput;
|
||||
this[`_${target.name}`] = target.value;
|
||||
this._publishDirtyState();
|
||||
}
|
||||
|
||||
private async _adminChanged(ev: Event): Promise<void> {
|
||||
const target = ev.target as HaSwitch;
|
||||
this._isAdmin = target.checked;
|
||||
this._publishDirtyState();
|
||||
}
|
||||
|
||||
private _localOnlyChanged(ev: Event): void {
|
||||
const target = ev.target as HaSwitch;
|
||||
this._localOnly = target.checked;
|
||||
this._publishDirtyState();
|
||||
}
|
||||
|
||||
private _publishDirtyState(): void {
|
||||
this._updateDirtyState({
|
||||
name: this._name,
|
||||
username: this._username,
|
||||
password: this._password,
|
||||
passwordConfirm: this._passwordConfirm,
|
||||
isAdmin: this._isAdmin,
|
||||
localOnly: this._localOnly,
|
||||
});
|
||||
}
|
||||
|
||||
private async _createUser(ev: Event) {
|
||||
@@ -345,7 +306,6 @@ export class DialogAddUser extends DirtyStateProviderMixin<AddUserFormState>()(
|
||||
},
|
||||
];
|
||||
this._params!.userAddedCallback(user);
|
||||
this._markDirtyStateClean();
|
||||
this._close();
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import type { SchemaUnion } from "../../../components/ha-form/types";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog";
|
||||
import { adminChangePassword } from "../../../data/auth";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showToast } from "../../../util/toast";
|
||||
@@ -44,9 +43,7 @@ interface FormData {
|
||||
}
|
||||
|
||||
@customElement("dialog-admin-change-password")
|
||||
class DialogAdminChangePassword extends DirtyStateProviderMixin<FormData>()(
|
||||
LitElement
|
||||
) {
|
||||
class DialogAdminChangePassword extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: AdminChangePasswordDialogParams;
|
||||
@@ -68,10 +65,7 @@ class DialogAdminChangePassword extends DirtyStateProviderMixin<FormData>()(
|
||||
this._userId = params.userId;
|
||||
this._data = undefined;
|
||||
this._error = undefined;
|
||||
this._submitting = false;
|
||||
this._success = false;
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "shallow" }, {});
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -123,7 +117,7 @@ class DialogAdminChangePassword extends DirtyStateProviderMixin<FormData>()(
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
header-title=${this.hass.localize(
|
||||
"ui.panel.config.users.change_password.caption"
|
||||
)}
|
||||
@@ -179,7 +173,6 @@ class DialogAdminChangePassword extends DirtyStateProviderMixin<FormData>()(
|
||||
|
||||
private _valueChanged(ev) {
|
||||
this._data = ev.detail.value;
|
||||
this._updateDirtyState(this._data ?? {});
|
||||
this._validate();
|
||||
}
|
||||
|
||||
@@ -192,7 +185,6 @@ class DialogAdminChangePassword extends DirtyStateProviderMixin<FormData>()(
|
||||
this._userId!,
|
||||
this._data.new_password
|
||||
);
|
||||
this._markDirtyStateClean();
|
||||
this._success = true;
|
||||
} catch (err: any) {
|
||||
showToast(this, {
|
||||
|
||||
@@ -24,23 +24,13 @@ import {
|
||||
showAlertDialog,
|
||||
showPromptDialog,
|
||||
} from "../../../dialogs/generic/show-dialog-box";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { showAdminChangePasswordDialog } from "./show-dialog-admin-change-password";
|
||||
import type { UserDetailDialogParams } from "./show-dialog-user-detail";
|
||||
|
||||
interface UserDetailFormState {
|
||||
name: string;
|
||||
isAdmin?: boolean;
|
||||
localOnly?: boolean;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
@customElement("dialog-user-detail")
|
||||
class DialogUserDetail extends DirtyStateProviderMixin<UserDetailFormState>()(
|
||||
LitElement
|
||||
) {
|
||||
class DialogUserDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _name!: string;
|
||||
@@ -67,15 +57,6 @@ class DialogUserDetail extends DirtyStateProviderMixin<UserDetailFormState>()(
|
||||
this._localOnly = params.entry.local_only;
|
||||
this._isActive = params.entry.is_active;
|
||||
this._open = true;
|
||||
this._initDirtyTracking(
|
||||
{ type: "shallow" },
|
||||
{
|
||||
name: this._name,
|
||||
isAdmin: this._isAdmin,
|
||||
localOnly: this._localOnly,
|
||||
isActive: this._isActive,
|
||||
}
|
||||
);
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@@ -88,164 +69,177 @@ class DialogUserDetail extends DirtyStateProviderMixin<UserDetailFormState>()(
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
header-title=${user.name}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<div>
|
||||
${this._error
|
||||
? html`<div class="error">${this._error}</div>`
|
||||
: nothing}
|
||||
${
|
||||
this._error
|
||||
? html`<div class="error">${this._error}</div>`
|
||||
: nothing
|
||||
}
|
||||
<div class="secondary">
|
||||
${this.hass.localize("ui.panel.config.users.editor.id")}:
|
||||
${user.id}<br />
|
||||
</div>
|
||||
${badges.length === 0
|
||||
? nothing
|
||||
: html`
|
||||
<div class="badge-container">
|
||||
${badges.map(
|
||||
([icon, label]) => html`
|
||||
<ha-label>
|
||||
<ha-svg-icon slot="icon" .path=${icon}></ha-svg-icon>
|
||||
${label}
|
||||
</ha-label>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`}
|
||||
<div class="form">
|
||||
${!user.system_generated
|
||||
? html`
|
||||
<ha-input
|
||||
autofocus
|
||||
.value=${this._name}
|
||||
@input=${this._nameChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.users.editor.name"
|
||||
${
|
||||
badges.length === 0
|
||||
? nothing
|
||||
: html`
|
||||
<div class="badge-container">
|
||||
${badges.map(
|
||||
([icon, label]) => html`
|
||||
<ha-label>
|
||||
<ha-svg-icon slot="icon" .path=${icon}></ha-svg-icon>
|
||||
${label}
|
||||
</ha-label>
|
||||
`
|
||||
)}
|
||||
></ha-input>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.username"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">${user.username}</span>
|
||||
${this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.path=${mdiPencil}
|
||||
@click=${this._changeUsername}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_username"
|
||||
)}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-row-item>
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
${!user.system_generated && this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.password"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">************</span>
|
||||
${this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.path=${mdiPencil}
|
||||
@click=${this._changePassword}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_password"
|
||||
)}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing}
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.disabled=${user.system_generated || user.is_owner}
|
||||
.checked=${this._isActive}
|
||||
@change=${this._activeChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_access_only"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_access_only_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.disabled=${user.system_generated}
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.admin"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.admin_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.disabled=${user.system_generated || user.is_owner}
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
${!this._isAdmin && !user.system_generated
|
||||
}
|
||||
<div class="form">
|
||||
${
|
||||
!user.system_generated
|
||||
? html`
|
||||
<ha-input
|
||||
autofocus
|
||||
.value=${this._name}
|
||||
@input=${this._nameChanged}
|
||||
.label=${this.hass!.localize(
|
||||
"ui.panel.config.users.editor.name"
|
||||
)}
|
||||
></ha-input>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.username"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">${user.username}</span>
|
||||
${this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.path=${mdiPencil}
|
||||
@click=${this._changeUsername}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_username"
|
||||
)}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
!user.system_generated && this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.password"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text">************</span>
|
||||
${this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-icon-button
|
||||
slot="end"
|
||||
.path=${mdiPencil}
|
||||
@click=${this._changePassword}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.users.editor.change_password"
|
||||
)}
|
||||
>
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.disabled=${user.system_generated || user.is_owner}
|
||||
.checked=${this._isActive}
|
||||
@change=${this._activeChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_access_only"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_access_only_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.disabled=${user.system_generated}
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.admin"
|
||||
)}</span
|
||||
>
|
||||
<span slot="supporting-text"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.admin_description"
|
||||
)}</span
|
||||
>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.disabled=${user.system_generated || user.is_owner}
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
></ha-switch>
|
||||
</ha-switch>
|
||||
</ha-row-item>
|
||||
${
|
||||
!this._isAdmin && !user.system_generated
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.users_privileges_note"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
${
|
||||
user.system_generated
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.users_privileges_note"
|
||||
"ui.panel.config.users.editor.system_generated_read_only_users"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
</div>
|
||||
${user.system_generated
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.users.editor.system_generated_read_only_users"
|
||||
)}
|
||||
</ha-alert>
|
||||
`
|
||||
: nothing}
|
||||
: nothing
|
||||
}
|
||||
</div>
|
||||
|
||||
<ha-dialog-footer slot="footer">
|
||||
@@ -254,19 +248,18 @@ class DialogUserDetail extends DirtyStateProviderMixin<UserDetailFormState>()(
|
||||
variant="danger"
|
||||
appearance="plain"
|
||||
@click=${this._deleteEntry}
|
||||
.disabled=${this._submitting ||
|
||||
user.system_generated ||
|
||||
user.is_owner}
|
||||
.disabled=${
|
||||
this._submitting || user.system_generated || user.is_owner
|
||||
}
|
||||
>
|
||||
${this.hass!.localize("ui.panel.config.users.editor.delete_user")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${!this._name ||
|
||||
this._submitting ||
|
||||
user.system_generated ||
|
||||
!this.isDirtyState}
|
||||
.disabled=${
|
||||
!this._name || this._submitting || user.system_generated
|
||||
}
|
||||
>
|
||||
${this.hass!.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
@@ -278,31 +271,18 @@ class DialogUserDetail extends DirtyStateProviderMixin<UserDetailFormState>()(
|
||||
private _nameChanged(ev: InputEvent) {
|
||||
this._error = undefined;
|
||||
this._name = (ev.target as HaInput).value ?? "";
|
||||
this._publishDirtyState();
|
||||
}
|
||||
|
||||
private _adminChanged(ev): void {
|
||||
this._isAdmin = ev.target.checked;
|
||||
this._publishDirtyState();
|
||||
}
|
||||
|
||||
private _localOnlyChanged(ev): void {
|
||||
this._localOnly = ev.target.checked;
|
||||
this._publishDirtyState();
|
||||
}
|
||||
|
||||
private _activeChanged(ev): void {
|
||||
this._isActive = ev.target.checked;
|
||||
this._publishDirtyState();
|
||||
}
|
||||
|
||||
private _publishDirtyState(): void {
|
||||
this._updateDirtyState({
|
||||
name: this._name,
|
||||
isAdmin: this._isAdmin,
|
||||
localOnly: this._localOnly,
|
||||
isActive: this._isActive,
|
||||
});
|
||||
}
|
||||
|
||||
private async _updateEntry() {
|
||||
@@ -316,7 +296,6 @@ class DialogUserDetail extends DirtyStateProviderMixin<UserDetailFormState>()(
|
||||
],
|
||||
local_only: this._localOnly,
|
||||
});
|
||||
this._markDirtyStateClean();
|
||||
this._close();
|
||||
} catch (err: any) {
|
||||
this._error = err?.message || "Unknown error";
|
||||
@@ -329,7 +308,6 @@ class DialogUserDetail extends DirtyStateProviderMixin<UserDetailFormState>()(
|
||||
this._submitting = true;
|
||||
try {
|
||||
if (await this._params!.removeEntry()) {
|
||||
this._markDirtyStateClean();
|
||||
this._close();
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -11,7 +11,6 @@ import "../../../components/ha-form/ha-form";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import type {
|
||||
AssistPipeline,
|
||||
AssistPipelineMutableParams,
|
||||
@@ -29,9 +28,7 @@ import type { VoiceAssistantPipelineDetailsDialogParams } from "./show-dialog-vo
|
||||
import type { HaDropdownSelectEvent } from "../../../components/ha-dropdown";
|
||||
|
||||
@customElement("dialog-voice-assistant-pipeline-detail")
|
||||
export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
|
||||
Partial<AssistPipeline>
|
||||
>()(LitElement) {
|
||||
export class DialogVoiceAssistantPipelineDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: VoiceAssistantPipelineDetailsDialogParams;
|
||||
@@ -65,7 +62,6 @@ export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
|
||||
|
||||
this._hideWakeWord =
|
||||
this._params.hideWakeWord || !this._data.wake_word_entity;
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,7 +98,6 @@ export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
|
||||
stt_engine: this._params.pipeline?.stt_engine || sstDefault,
|
||||
tts_engine: this._params.pipeline?.tts_engine || ttsDefault,
|
||||
};
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
}
|
||||
|
||||
public closeDialog(): void {
|
||||
@@ -150,7 +145,7 @@ export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
header-title=${title}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
${!this._hideWakeWord ||
|
||||
@@ -239,7 +234,6 @@ export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
|
||||
slot="primaryAction"
|
||||
@click=${this._updatePipeline}
|
||||
.loading=${this._submitting}
|
||||
.disabled=${!this.isDirtyState}
|
||||
>
|
||||
${isExistingPipeline
|
||||
? this.hass.localize(
|
||||
@@ -272,7 +266,6 @@ export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
|
||||
value[key] = ev.detail.value[key];
|
||||
});
|
||||
this._data = { ...this._data, ...value };
|
||||
this._updateDirtyState(this._data);
|
||||
}
|
||||
|
||||
private async _updatePipeline() {
|
||||
@@ -306,7 +299,6 @@ export class DialogVoiceAssistantPipelineDetail extends DirtyStateProviderMixin<
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("No createPipeline function provided");
|
||||
}
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = err?.message || "Unknown error";
|
||||
|
||||
@@ -8,7 +8,6 @@ import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-form/ha-form";
|
||||
import "../../../components/ha-button";
|
||||
import type { HomeZoneMutableParams } from "../../../data/zone";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { HomeZoneDetailDialogParams } from "./show-dialog-home-zone-detail";
|
||||
@@ -22,9 +21,7 @@ const SCHEMA = [
|
||||
];
|
||||
|
||||
@customElement("dialog-home-zone-detail")
|
||||
class DialogHomeZoneDetail extends DirtyStateProviderMixin<HomeZoneMutableParams>()(
|
||||
LitElement
|
||||
) {
|
||||
class DialogHomeZoneDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _error?: Record<string, string>;
|
||||
@@ -46,7 +43,6 @@ class DialogHomeZoneDetail extends DirtyStateProviderMixin<HomeZoneMutableParams
|
||||
longitude: this.hass.config.longitude,
|
||||
radius: this.hass.config.radius,
|
||||
};
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
@@ -75,7 +71,7 @@ class DialogHomeZoneDetail extends DirtyStateProviderMixin<HomeZoneMutableParams
|
||||
header-title=${this.hass!.localize("ui.common.edit_item", {
|
||||
name: this._data.name,
|
||||
})}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-form
|
||||
@@ -98,7 +94,7 @@ class DialogHomeZoneDetail extends DirtyStateProviderMixin<HomeZoneMutableParams
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${!valid || this._submitting || !this.isDirtyState}
|
||||
.disabled=${!valid || this._submitting}
|
||||
>
|
||||
${this.hass!.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
@@ -124,7 +120,6 @@ class DialogHomeZoneDetail extends DirtyStateProviderMixin<HomeZoneMutableParams
|
||||
value.radius = value.location.radius;
|
||||
delete value.location;
|
||||
this._data = value;
|
||||
this._updateDirtyState(value);
|
||||
}
|
||||
|
||||
private _computeLabel = (): string => "";
|
||||
|
||||
@@ -11,15 +11,12 @@ import "../../../components/ha-button";
|
||||
import type { SchemaUnion } from "../../../components/ha-form/types";
|
||||
import type { ZoneMutableParams } from "../../../data/zone";
|
||||
import { getZoneEditorInitData } from "../../../data/zone";
|
||||
import { DirtyStateProviderMixin } from "../../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyleDialog } from "../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import type { ZoneDetailDialogParams } from "./show-dialog-zone-detail";
|
||||
|
||||
@customElement("dialog-zone-detail")
|
||||
class DialogZoneDetail extends DirtyStateProviderMixin<ZoneMutableParams>()(
|
||||
LitElement
|
||||
) {
|
||||
class DialogZoneDetail extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _error?: Record<string, string>;
|
||||
@@ -56,7 +53,6 @@ class DialogZoneDetail extends DirtyStateProviderMixin<ZoneMutableParams>()(
|
||||
radius: 100,
|
||||
};
|
||||
}
|
||||
this._initDirtyTracking({ type: "deep" }, this._data);
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
@@ -97,7 +93,7 @@ class DialogZoneDetail extends DirtyStateProviderMixin<ZoneMutableParams>()(
|
||||
name: this._params.entry.name,
|
||||
})
|
||||
: this.hass!.localize("ui.panel.config.zone.detail.new_zone")}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-form
|
||||
@@ -135,7 +131,7 @@ class DialogZoneDetail extends DirtyStateProviderMixin<ZoneMutableParams>()(
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._updateEntry}
|
||||
.disabled=${!valid || this._submitting || !this.isDirtyState}
|
||||
.disabled=${!valid || this._submitting}
|
||||
>
|
||||
${this._params.entry
|
||||
? this.hass!.localize("ui.common.save")
|
||||
@@ -193,7 +189,6 @@ class DialogZoneDetail extends DirtyStateProviderMixin<ZoneMutableParams>()(
|
||||
delete value.icon;
|
||||
}
|
||||
this._data = value;
|
||||
this._updateDirtyState(value);
|
||||
}
|
||||
|
||||
private _computeLabel = (
|
||||
|
||||
@@ -32,7 +32,6 @@ import type { EnergyDevicesGraphCardConfig } from "../types";
|
||||
import { hasConfigChanged } from "../../common/has-changed";
|
||||
import type { HaECOption } from "../../../../resources/echarts/echarts";
|
||||
import "../../../../components/ha-card";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { measureTextWidth } from "../../../../util/text";
|
||||
import "../../../../components/ha-icon-button";
|
||||
@@ -190,11 +189,9 @@ export class HuiEnergyDevicesGraphCard
|
||||
)}
|
||||
.height=${`${Math.max(modes.includes("pie") ? 300 : 100, (this._legendData?.length || 0) * 28 + 50)}px`}
|
||||
.extraComponents=${[PieChart]}
|
||||
click-label-for-more-info
|
||||
@chart-click=${this._handleChartClick}
|
||||
@dataset-hidden=${this._datasetHidden}
|
||||
@dataset-unhidden=${this._datasetUnhidden}
|
||||
@legend-label-click=${this._handleLegendLabelClick}
|
||||
></ha-chart-base>
|
||||
</div>
|
||||
</ha-card>
|
||||
@@ -546,20 +543,11 @@ export class HuiEnergyDevicesGraphCard
|
||||
chartData.splice(this._config.max_devices);
|
||||
}
|
||||
|
||||
this._legendData = chartData.map((d) => {
|
||||
const id = (d as any).id as string;
|
||||
return {
|
||||
...d,
|
||||
name: this._getDeviceName(d.name),
|
||||
value: `${formatNumber(d.value[0], this.hass.locale)} kWh`,
|
||||
// Untracked is synthetic and external statistics aren't real entities,
|
||||
// so their labels can't open more-info; fall back to toggling visibility.
|
||||
noLabelClick:
|
||||
id === "untracked" ||
|
||||
isExternalStatistic(id) ||
|
||||
!(id in this.hass.states),
|
||||
};
|
||||
});
|
||||
this._legendData = chartData.map((d) => ({
|
||||
...d,
|
||||
name: this._getDeviceName(d.name),
|
||||
value: `${formatNumber(d.value[0], this.hass.locale)} kWh`,
|
||||
}));
|
||||
// filter out hidden stats in place
|
||||
for (let i = chartData.length - 1; i >= 0; i--) {
|
||||
if (this._hiddenStats.includes((chartData[i] as any).id)) {
|
||||
@@ -591,11 +579,7 @@ export class HuiEnergyDevicesGraphCard
|
||||
e.detail.event?.target?.type === "tspan" // label
|
||||
) {
|
||||
const id = (e.detail.data as any).id as string;
|
||||
if (
|
||||
id !== "untracked" &&
|
||||
!isExternalStatistic(id) &&
|
||||
this.hass.states[id]
|
||||
) {
|
||||
if (id !== "untracked") {
|
||||
fireEvent(this, "hass-more-info", {
|
||||
entityId: id,
|
||||
});
|
||||
@@ -603,16 +587,6 @@ export class HuiEnergyDevicesGraphCard
|
||||
}
|
||||
}
|
||||
|
||||
private _handleLegendLabelClick(
|
||||
ev: HASSDomEvent<HASSDomEvents["legend-label-click"]>
|
||||
) {
|
||||
const entityId = ev.detail.id;
|
||||
if (isExternalStatistic(entityId) || !this.hass.states[entityId]) {
|
||||
return;
|
||||
}
|
||||
fireEvent(this, "hass-more-info", { entityId });
|
||||
}
|
||||
|
||||
private _handleChartTypeChange(): void {
|
||||
if (!this._chartType) {
|
||||
return;
|
||||
|
||||
@@ -49,9 +49,6 @@ const addEntityId = (entities: Set<string>, entity) => {
|
||||
};
|
||||
|
||||
const addEntities = (entities: Set<string>, obj) => {
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
if (obj.entity) {
|
||||
addEntityId(entities, obj.entity);
|
||||
}
|
||||
|
||||
+2
-13
@@ -12,7 +12,6 @@ import "../../../../../components/ha-dropdown";
|
||||
import "../../../../../components/ha-dropdown-item";
|
||||
import "../../../../../components/ha-icon-button";
|
||||
import type { LovelaceStrategyConfig } from "../../../../../data/lovelace/config/strategy";
|
||||
import { DirtyStateProviderMixin } from "../../../../../mixins/dirty-state-provider-mixin";
|
||||
import {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
@@ -28,9 +27,7 @@ import type { DashboardStrategyEditorDialogParams } from "./show-dialog-dashboar
|
||||
import type { HaDropdownSelectEvent } from "../../../../../components/ha-dropdown";
|
||||
|
||||
@customElement("dialog-dashboard-strategy-editor")
|
||||
class DialogDashboardStrategyEditor extends DirtyStateProviderMixin<LovelaceStrategyConfig>()(
|
||||
LitElement
|
||||
) {
|
||||
class DialogDashboardStrategyEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: DashboardStrategyEditorDialogParams;
|
||||
@@ -52,7 +49,6 @@ class DialogDashboardStrategyEditor extends DirtyStateProviderMixin<LovelaceStra
|
||||
this._params = params;
|
||||
this._strategyConfig = params.config.strategy;
|
||||
this._open = true;
|
||||
this._initDirtyTracking({ type: "deep" }, this._strategyConfig);
|
||||
await this.updateComplete;
|
||||
}
|
||||
|
||||
@@ -72,7 +68,6 @@ class DialogDashboardStrategyEditor extends DirtyStateProviderMixin<LovelaceStra
|
||||
ev.stopPropagation();
|
||||
this._guiModeAvailable = ev.detail.guiModeAvailable;
|
||||
this._strategyConfig = ev.detail.config as LovelaceStrategyConfig;
|
||||
this._updateDirtyState(this._strategyConfig);
|
||||
}
|
||||
|
||||
private _handleGUIModeChanged(ev: HASSDomEvent<GUIModeChangedEvent>): void {
|
||||
@@ -87,7 +82,6 @@ class DialogDashboardStrategyEditor extends DirtyStateProviderMixin<LovelaceStra
|
||||
strategy: this._strategyConfig!,
|
||||
});
|
||||
showSaveSuccessToast(this, this.hass);
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
@@ -143,7 +137,6 @@ class DialogDashboardStrategyEditor extends DirtyStateProviderMixin<LovelaceStra
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
header-title=${title || "-"}
|
||||
header-subtitle=${ifDefined(this._params.title)}
|
||||
width="large"
|
||||
@@ -202,11 +195,7 @@ class DialogDashboardStrategyEditor extends DirtyStateProviderMixin<LovelaceStra
|
||||
>
|
||||
${this.hass!.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
?disabled=${!this.isDirtyState}
|
||||
>
|
||||
<ha-button slot="primaryAction" @click=${this._save}>
|
||||
${this.hass!.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
|
||||
@@ -30,7 +30,6 @@ import {
|
||||
} from "../../../../data/lovelace/config/view";
|
||||
import { showAlertDialog } from "../../../../dialogs/generic/show-dialog-box";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { DirtyStateProviderMixin } from "../../../../mixins/dirty-state-provider-mixin";
|
||||
import {
|
||||
haStyleDialog,
|
||||
haStyleDialogFixedTop,
|
||||
@@ -54,7 +53,7 @@ const TABS = ["tab-settings", "tab-visibility"] as const;
|
||||
|
||||
@customElement("hui-dialog-edit-section")
|
||||
export class HuiDialogEditSection
|
||||
extends DirtyStateProviderMixin<LovelaceSectionRawConfig>()(LitElement)
|
||||
extends LitElement
|
||||
implements HassDialog<EditSectionDialogParams>
|
||||
{
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -97,7 +96,6 @@ export class HuiDialogEditSection
|
||||
this._viewConfig = findLovelaceContainer(this._params.lovelaceConfig, [
|
||||
this._params.viewIndex,
|
||||
]);
|
||||
this._initDirtyTracking({ type: "deep" }, this._config);
|
||||
}
|
||||
|
||||
public closeDialog() {
|
||||
@@ -161,7 +159,7 @@ export class HuiDialogEditSection
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
.preventScrimClose=${this.isDirtyState}
|
||||
prevent-scrim-close
|
||||
@keydown=${this._ignoreKeydown}
|
||||
@closed=${this._dialogClosed}
|
||||
class=${classMap({
|
||||
@@ -233,11 +231,7 @@ export class HuiDialogEditSection
|
||||
${this.hass!.localize("ui.common.cancel")}
|
||||
</ha-button>
|
||||
|
||||
<ha-button
|
||||
slot="primaryAction"
|
||||
@click=${this._save}
|
||||
?disabled=${!this.isDirtyState}
|
||||
>
|
||||
<ha-button slot="primaryAction" @click=${this._save}>
|
||||
${this.hass!.localize("ui.common.save")}
|
||||
</ha-button>
|
||||
</ha-dialog-footer>
|
||||
@@ -248,7 +242,6 @@ export class HuiDialogEditSection
|
||||
private _configChanged(ev: CustomEvent): void {
|
||||
ev.stopPropagation();
|
||||
this._config = ev.detail.value;
|
||||
this._updateDirtyState(this._config!);
|
||||
}
|
||||
|
||||
private _handleTabChanged(ev: CustomEvent): void {
|
||||
@@ -406,7 +399,6 @@ export class HuiDialogEditSection
|
||||
return;
|
||||
}
|
||||
this._config = ev.detail.value;
|
||||
this._updateDirtyState(this._config!);
|
||||
}
|
||||
|
||||
private _ignoreKeydown(ev: KeyboardEvent) {
|
||||
@@ -431,7 +423,6 @@ export class HuiDialogEditSection
|
||||
);
|
||||
|
||||
this._params.saveConfig(newConfig);
|
||||
this._markDirtyStateClean();
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { undoDepth } from "@codemirror/commands";
|
||||
import { mdiClose } from "@mdi/js";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
showAlertDialog,
|
||||
showConfirmationDialog,
|
||||
} from "../../dialogs/generic/show-dialog-box";
|
||||
import { DirtyStateProviderMixin } from "../../mixins/dirty-state-provider-mixin";
|
||||
import { haStyle } from "../../resources/styles";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { Lovelace } from "./types";
|
||||
@@ -33,9 +33,7 @@ const strategyStruct = type({
|
||||
});
|
||||
|
||||
@customElement("hui-editor")
|
||||
class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
LitElement
|
||||
) {
|
||||
class LovelaceFullConfigEditor extends LitElement {
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
@@ -46,6 +44,8 @@ class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
|
||||
@state() private _saving?: boolean;
|
||||
|
||||
@state() private _changed?: boolean;
|
||||
|
||||
private _config?: LovelaceRawConfig;
|
||||
|
||||
private _yamlError?: string;
|
||||
@@ -66,10 +66,10 @@ class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
slot="actionItems"
|
||||
class="save-button
|
||||
${classMap({
|
||||
saved: this._saving === false || this.isDirtyState,
|
||||
saved: this._saving === false || this._changed === true,
|
||||
})}"
|
||||
>
|
||||
${this.isDirtyState
|
||||
${this._changed
|
||||
? this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.unsaved_changes"
|
||||
)
|
||||
@@ -78,7 +78,7 @@ class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
<ha-button
|
||||
slot="actionItems"
|
||||
@click=${this._handleSave}
|
||||
.disabled=${!this.isDirtyState}
|
||||
.disabled=${!this._changed}
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.save"
|
||||
)}</ha-button
|
||||
@@ -98,7 +98,7 @@ class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
|
||||
protected firstUpdated(changedProps: PropertyValues<this>) {
|
||||
super.firstUpdated(changedProps);
|
||||
this._setValue();
|
||||
this.yamlEditor.setValue(this.lovelace!.rawConfig);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
@@ -110,19 +110,10 @@ class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
oldLovelace.rawConfig !== this.lovelace.rawConfig &&
|
||||
!deepEqual(oldLovelace.rawConfig, this.lovelace.rawConfig)
|
||||
) {
|
||||
this._setValue();
|
||||
this.yamlEditor.setValue(this.lovelace!.rawConfig);
|
||||
}
|
||||
}
|
||||
|
||||
private _setValue() {
|
||||
this.yamlEditor.setValue(this.lovelace!.rawConfig);
|
||||
// Baseline the dirty check against the loaded YAML so it resets on save.
|
||||
this._initDirtyTracking(
|
||||
{ type: "custom", compare: (a, b) => a === b },
|
||||
this.yamlEditor.yaml
|
||||
);
|
||||
}
|
||||
|
||||
static get styles(): CSSResultGroup {
|
||||
return [
|
||||
haStyle,
|
||||
@@ -167,17 +158,17 @@ class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
private _yamlChanged(ev: CustomEvent) {
|
||||
this._config = ev.detail.isValid ? ev.detail.value : undefined;
|
||||
this._yamlError = ev.detail.errorMsg;
|
||||
this._updateDirtyState(this.yamlEditor.yaml);
|
||||
if (this.isDirtyState && !window.onbeforeunload) {
|
||||
this._changed = undoDepth(this.yamlEditor.codemirror!.state) > 0;
|
||||
if (this._changed && !window.onbeforeunload) {
|
||||
window.onbeforeunload = () => true;
|
||||
} else if (!this.isDirtyState && window.onbeforeunload) {
|
||||
} else if (!this._changed && window.onbeforeunload) {
|
||||
window.onbeforeunload = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async _closeEditor() {
|
||||
if (
|
||||
this.isDirtyState &&
|
||||
this._changed &&
|
||||
!(await showConfirmationDialog(this, {
|
||||
text: this.hass.localize(
|
||||
"ui.panel.lovelace.editor.raw_editor.confirm_unsaved_changes"
|
||||
@@ -288,7 +279,7 @@ class LovelaceFullConfigEditor extends DirtyStateProviderMixin<string>()(
|
||||
});
|
||||
}
|
||||
window.onbeforeunload = null;
|
||||
this._markDirtyStateClean();
|
||||
this._changed = false;
|
||||
this._saving = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -60,24 +60,16 @@ export const createLogMessage = async (
|
||||
// - a possible list of aggregated errors
|
||||
if (error instanceof Error) {
|
||||
lines.push(error.toString() || messageFallback);
|
||||
let stackLines: (string | undefined)[];
|
||||
try {
|
||||
stackLines = (await fromError(error))
|
||||
.slice(0, MAX_STACK_FRAMES)
|
||||
.map((frame) => {
|
||||
frame.fileName ??= "";
|
||||
if (URL.canParse(frame.fileName)) {
|
||||
frame.fileName = new URL(frame.fileName).pathname;
|
||||
}
|
||||
frame.fileName = frame.fileName.replace(REMOVAL_PATHS, "");
|
||||
return frame.toString();
|
||||
});
|
||||
} catch {
|
||||
// stacktrace-js cannot always parse a stack (for example a DOMException
|
||||
// with no, or an unrecognized, stack), so fall back to the raw stack
|
||||
// instead of letting the error logger itself throw.
|
||||
stackLines = error.stack ? [error.stack] : [];
|
||||
}
|
||||
const stackLines = (await fromError(error))
|
||||
.slice(0, MAX_STACK_FRAMES)
|
||||
.map((frame) => {
|
||||
frame.fileName ??= "";
|
||||
if (URL.canParse(frame.fileName)) {
|
||||
frame.fileName = new URL(frame.fileName).pathname;
|
||||
}
|
||||
frame.fileName = frame.fileName.replace(REMOVAL_PATHS, "");
|
||||
return frame.toString();
|
||||
});
|
||||
lines.push(...(stackLines.length > 0 ? stackLines : [stackFallback]));
|
||||
// @ts-expect-error Requires library bump to ES2022
|
||||
if (error.cause) {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createLogMessage } from "../../src/resources/log-message";
|
||||
|
||||
const fromError = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("stacktrace-js", () => ({ fromError }));
|
||||
|
||||
describe("createLogMessage", () => {
|
||||
beforeEach(() => {
|
||||
fromError.mockReset();
|
||||
});
|
||||
|
||||
it("includes the error message and parsed stack frames", async () => {
|
||||
fromError.mockResolvedValue([
|
||||
{ fileName: "https://example.com/foo.js", toString: () => "at foo.js" },
|
||||
]);
|
||||
const error = new Error("boom");
|
||||
|
||||
const message = await createLogMessage(error);
|
||||
|
||||
expect(message).toContain("Error: boom");
|
||||
expect(message).toContain("at foo.js");
|
||||
});
|
||||
|
||||
it("does not throw when stacktrace-js cannot parse the stack", async () => {
|
||||
fromError.mockRejectedValue(new Error("Cannot parse given Error object"));
|
||||
const error = new Error("boom");
|
||||
error.stack = "Error: boom\n at <anonymous>";
|
||||
|
||||
const message = await createLogMessage(error);
|
||||
|
||||
expect(message).toContain("Error: boom");
|
||||
// Falls back to the raw stack instead of crashing the logger.
|
||||
expect(message).toContain("at <anonymous>");
|
||||
});
|
||||
|
||||
it("falls back to the provided stack fallback when no stack is available", async () => {
|
||||
fromError.mockRejectedValue(new Error("Cannot parse given Error object"));
|
||||
const error = new Error("boom");
|
||||
error.stack = undefined;
|
||||
|
||||
const message = await createLogMessage(
|
||||
error,
|
||||
undefined,
|
||||
undefined,
|
||||
"@unknown:0:0"
|
||||
);
|
||||
|
||||
expect(message).toContain("@unknown:0:0");
|
||||
});
|
||||
});
|
||||
@@ -1352,14 +1352,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/lint@npm:6.9.7, @codemirror/lint@npm:^6.0.0":
|
||||
version: 6.9.7
|
||||
resolution: "@codemirror/lint@npm:6.9.7"
|
||||
"@codemirror/lint@npm:6.9.6, @codemirror/lint@npm:^6.0.0":
|
||||
version: 6.9.6
|
||||
resolution: "@codemirror/lint@npm:6.9.6"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.0.0"
|
||||
"@codemirror/view": "npm:^6.42.0"
|
||||
crelt: "npm:^1.0.5"
|
||||
checksum: 10/f1af8295e1741a8d0c155cd7552c35d21c859d8848571fa084f5a6f2a50cac51ed25f04e423cab0f094e11b4828a54de4383a97b6b8cf6a242c5941034bffa04
|
||||
checksum: 10/70ed80eaec81038c014a89d8b4ba17396562b7dd541882fa981e2f81f7cdcbd67725a7c055c7772ece3bd052a276976d873f71746fc550b1aede7e18faa32f93
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -1383,15 +1383,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@codemirror/view@npm:6.43.1, @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.1
|
||||
resolution: "@codemirror/view@npm:6.43.1"
|
||||
"@codemirror/view@npm:6.43.0, @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.0
|
||||
resolution: "@codemirror/view@npm:6.43.0"
|
||||
dependencies:
|
||||
"@codemirror/state": "npm:^6.6.0"
|
||||
crelt: "npm:^1.0.6"
|
||||
style-mod: "npm:^4.1.0"
|
||||
w3c-keyname: "npm:^2.2.4"
|
||||
checksum: 10/a867a26cb511f161a8ef60b2574afae2561cfe94abd822e0af2a6e4bd94d1ff46f0d5085428c89f6f6b80a52482c8d23f80362173491903f49fb93462a216b95
|
||||
checksum: 10/7cfeebe1507f71a960dfb2d5152400507d28ed5827680bc73e0a093bfba9a796c2e559c960fd2b046379fac31ff0b59663dfc481baadf1d6ececd71eb5b48014
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4548,105 +4548,105 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/eslint-plugin@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.61.0"
|
||||
"@typescript-eslint/eslint-plugin@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/eslint-plugin@npm:8.60.1"
|
||||
dependencies:
|
||||
"@eslint-community/regexpp": "npm:^4.12.2"
|
||||
"@typescript-eslint/scope-manager": "npm:8.61.0"
|
||||
"@typescript-eslint/type-utils": "npm:8.61.0"
|
||||
"@typescript-eslint/utils": "npm:8.61.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.61.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.1"
|
||||
"@typescript-eslint/type-utils": "npm:8.60.1"
|
||||
"@typescript-eslint/utils": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
ignore: "npm:^7.0.5"
|
||||
natural-compare: "npm:^1.4.0"
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
"@typescript-eslint/parser": ^8.61.0
|
||||
"@typescript-eslint/parser": ^8.60.1
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/ca7fbaa2f03ec15bdbf39d2e4d42f1b682085f23830591d1d6c3d9f497fdda497341b2fa67c8d366514a3c22807557e45e7afe1ee70cef527b184250e5422e8f
|
||||
checksum: 10/f3633bb2700bc32299578baeaf6650418656229be256147ba9d1ab09b34ef2b7fed83804ef4d2439e9189dbdcb89399d67bc8fea55262be6caa32114be048538
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/parser@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/parser@npm:8.61.0"
|
||||
"@typescript-eslint/parser@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/parser@npm:8.60.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager": "npm:8.61.0"
|
||||
"@typescript-eslint/types": "npm:8.61.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.61.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.61.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
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/82060c36786339867d63337708a08bd4bc65569313998bd086dbe6b901664082c7e40d6b6e085296a459cd4fc1d064479ef570b51e1eb113688bb152a7a6d689
|
||||
checksum: 10/f9c484c4a3897015328f328a1c6ee778d113dd134201f635c0421cb72efe6e63f3a68524aff0df6e19e76ff93daf5cabd946e67f12f10dddcf19bda534aa68dc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/project-service@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/project-service@npm:8.61.0"
|
||||
"@typescript-eslint/project-service@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/project-service@npm:8.60.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.61.0"
|
||||
"@typescript-eslint/types": "npm:^8.61.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:^8.60.1"
|
||||
"@typescript-eslint/types": "npm:^8.60.1"
|
||||
debug: "npm:^4.4.3"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/b7d7e973b565f604af43b8afb3ca1c3fbe6fcf16863bde83b42417a196ba9f3a5a3f5d39bf57ed96b8ce577047064d93c353ecb21db5e95dce69f81335c9cd81
|
||||
checksum: 10/fec693dd79c3a1e6a24091127a37af4eb9d9cee8192cf2a434adae48543eadff834bc0623b5b563c8b592b250bc080570f9e7b42807252ea898442c525beeee9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/scope-manager@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.61.0"
|
||||
"@typescript-eslint/scope-manager@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/scope-manager@npm:8.60.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.61.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.61.0"
|
||||
checksum: 10/295e306665d64f0330fede3fe72febd65c67c3083d747149b66097aa6f7d517f25731dc1dbec900b15768c40f92b082f501296e7524855fe82697f40b8d23ce1
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
checksum: 10/7228c110410ff8cfc01e96d8f17c986f8b4dd447fe3a3291baaab8fe946026ccdf0291865f788f18cf538ab49bfc067fe797708b6b8590104a65f7e69f921cc5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.61.0, @typescript-eslint/tsconfig-utils@npm:^8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.61.0"
|
||||
"@typescript-eslint/tsconfig-utils@npm:8.60.1, @typescript-eslint/tsconfig-utils@npm:^8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/tsconfig-utils@npm:8.60.1"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/f678ff5ec887a27d8e590e0c67403b12e372a027ab036dcfc1e3ef614d3bed7a3c455a65fa0a87ff7dae5b0ad1c49cf4aa40639cc368d7eb424efe8349d9cb9f
|
||||
checksum: 10/afc78b19b856a71dc4e493f931ae44e1a91dc6441a14cb92e4063db880892f3874768f9d347d4b2f45362f2090e4455407c70f42027d77ddc85d6cba95cdb76c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/type-utils@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.61.0"
|
||||
"@typescript-eslint/type-utils@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/type-utils@npm:8.60.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.61.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.61.0"
|
||||
"@typescript-eslint/utils": "npm:8.61.0"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
"@typescript-eslint/utils": "npm:8.60.1"
|
||||
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/8290e5fc26241dfd5aeeffad0fb9857a3fa1f9c8107dfb01638970297e0e17be6088f0fd2d6fc7d450e9879afaa7e23f4111182bcf0b625eba74fdf13100b19e
|
||||
checksum: 10/6f426263be597063831bf308e52328e8d387af5db955a09cb85fde1c72f5b1b36a365133b9c9a74330e5e948e59bf9a9b82605f4c9c4e3bf9b6cb7f4c37e4b18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/types@npm:8.61.0, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/types@npm:8.61.0"
|
||||
checksum: 10/8e1e1cf5d092beed1a974b3b5d7cc20219ad3e4501b85bbef5bec1c81ab50b09ee70093ad2195c3061c499e804d63aac38dcc20293342b1fa774ba743c0d63bf
|
||||
"@typescript-eslint/types@npm:8.60.1, @typescript-eslint/types@npm:^8.56.0, @typescript-eslint/types@npm:^8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/types@npm:8.60.1"
|
||||
checksum: 10/c603417e621b5b1263c2f60fad9e202d560fd07fce7f40e9a356c0530e5eaf0ff1a9af865237bf93aa18a5a4e2f034ee0cce0fe6c070f08df33e35a099bdea47
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/typescript-estree@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.61.0"
|
||||
"@typescript-eslint/typescript-estree@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/typescript-estree@npm:8.60.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service": "npm:8.61.0"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.61.0"
|
||||
"@typescript-eslint/types": "npm:8.61.0"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.61.0"
|
||||
"@typescript-eslint/project-service": "npm:8.60.1"
|
||||
"@typescript-eslint/tsconfig-utils": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/visitor-keys": "npm:8.60.1"
|
||||
debug: "npm:^4.4.3"
|
||||
minimatch: "npm:^10.2.2"
|
||||
semver: "npm:^7.7.3"
|
||||
@@ -4654,32 +4654,32 @@ __metadata:
|
||||
ts-api-utils: "npm:^2.5.0"
|
||||
peerDependencies:
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/6d5ab7850226de23ab26d94388f729e792413f5a9e704c8c685b66eb20946efeb290cda91195f062e1065bc20129ec8f5955768316660132087347e66dec0d1a
|
||||
checksum: 10/9c3a56266aadf589bc6e770cd04cb3f55b1ee1507dcacda61866408c656ae4462aa7e11baf39eb939bc4d1e3b843cf58e60f3ebdeb3e75f042ff0f6fb39c311b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/utils@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/utils@npm:8.61.0"
|
||||
"@typescript-eslint/utils@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/utils@npm:8.60.1"
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils": "npm:^4.9.1"
|
||||
"@typescript-eslint/scope-manager": "npm:8.61.0"
|
||||
"@typescript-eslint/types": "npm:8.61.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.61.0"
|
||||
"@typescript-eslint/scope-manager": "npm:8.60.1"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/50ff451edb8e5dee92bbab11a75cbd570715623d89094d0541ddfbef208248e82d2f9478d1e09fb9c94496069afd4db9521384b77f7aaa63970f7edfebddfba9
|
||||
checksum: 10/a75f8714995b6280b4c15ca957bbc6634862453461111e4a2a07b8bc72b51a504484a9b957fc5b7a646c4bf09f1e414a0c52cd3b6798c42fb8c4de83b1b5a364
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@typescript-eslint/visitor-keys@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.61.0"
|
||||
"@typescript-eslint/visitor-keys@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "@typescript-eslint/visitor-keys@npm:8.60.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/types": "npm:8.61.0"
|
||||
"@typescript-eslint/types": "npm:8.60.1"
|
||||
eslint-visitor-keys: "npm:^5.0.0"
|
||||
checksum: 10/243018d9d8b1918d2863e50eec6628c792ccda05ad5534f5153fc783b7f54cdb8a58d758eb74260d113274bfab8bb38ad4664f3db9e7d3f844cdffbe6e47e285
|
||||
checksum: 10/6d120b4a790477ae0291e69f6457686c71b929cc40519148f6b6c7fbc09604b15821ae8cf1005aa23acec5105b4016db256a68d68f30eda8d6c24d4fdb0ede86
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -8442,10 +8442,10 @@ __metadata:
|
||||
"@codemirror/lang-jinja": "npm:6.0.1"
|
||||
"@codemirror/lang-yaml": "npm:6.1.3"
|
||||
"@codemirror/language": "npm:6.12.3"
|
||||
"@codemirror/lint": "npm:6.9.7"
|
||||
"@codemirror/lint": "npm:6.9.6"
|
||||
"@codemirror/search": "npm:6.7.0"
|
||||
"@codemirror/state": "npm:6.6.0"
|
||||
"@codemirror/view": "npm:6.43.1"
|
||||
"@codemirror/view": "npm:6.43.0"
|
||||
"@date-fns/tz": "npm:1.5.0"
|
||||
"@egjs/hammerjs": "npm:2.0.17"
|
||||
"@eslint/js": "npm:10.0.1"
|
||||
@@ -8570,7 +8570,7 @@ __metadata:
|
||||
node-vibrant: "npm:4.0.4"
|
||||
object-hash: "npm:3.0.0"
|
||||
pinst: "npm:3.0.0"
|
||||
prettier: "npm:3.8.4"
|
||||
prettier: "npm:3.8.3"
|
||||
punycode: "npm:2.3.1"
|
||||
qr-scanner: "npm:1.4.2"
|
||||
qrcode: "npm:1.5.4"
|
||||
@@ -8587,7 +8587,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.61.0"
|
||||
typescript-eslint: "npm:8.60.1"
|
||||
vite-tsconfig-paths: "npm:6.1.1"
|
||||
vitest: "npm:4.1.8"
|
||||
webpack-stats-plugin: "npm:1.1.3"
|
||||
@@ -11350,12 +11350,12 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prettier@npm:3.8.4":
|
||||
version: 3.8.4
|
||||
resolution: "prettier@npm:3.8.4"
|
||||
"prettier@npm:3.8.3":
|
||||
version: 3.8.3
|
||||
resolution: "prettier@npm:3.8.3"
|
||||
bin:
|
||||
prettier: bin/prettier.cjs
|
||||
checksum: 10/54684a3cc6689238692b29fab541c01934af7677be94c02293ba49981a1ac121c8bebe2a865f0c3b963e99d208f847c53aed354cc0ce8750e2d45791d64506c5
|
||||
checksum: 10/4b3b12cbb29e4c96bed936e5d070167552500c18d37676fb3e0caae6199c42860662608e4dc116230698f6e2bb0267ef2548158224c92d40f188d309d72fdd6f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13461,18 +13461,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"typescript-eslint@npm:8.61.0":
|
||||
version: 8.61.0
|
||||
resolution: "typescript-eslint@npm:8.61.0"
|
||||
"typescript-eslint@npm:8.60.1":
|
||||
version: 8.60.1
|
||||
resolution: "typescript-eslint@npm:8.60.1"
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.61.0"
|
||||
"@typescript-eslint/parser": "npm:8.61.0"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.61.0"
|
||||
"@typescript-eslint/utils": "npm:8.61.0"
|
||||
"@typescript-eslint/eslint-plugin": "npm:8.60.1"
|
||||
"@typescript-eslint/parser": "npm:8.60.1"
|
||||
"@typescript-eslint/typescript-estree": "npm:8.60.1"
|
||||
"@typescript-eslint/utils": "npm:8.60.1"
|
||||
peerDependencies:
|
||||
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||
typescript: ">=4.8.4 <6.1.0"
|
||||
checksum: 10/5a21c6ef76400ea30a47629087787834abc1c17e4b406465dfd8c204ef635556f8e3a775d89c46f9eb175ebd6a218284685e935877a2b148c482f0478627bdf9
|
||||
checksum: 10/e12091ab2540b817c76b0ec6aad92e341f810310bec2b24bc95780aee106049c05363998f6ea52ed066130c8afc41dca1627f56e4c1df1dd519f4d4ca0ce4910
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Reference in New Issue
Block a user