mirror of
https://github.com/home-assistant/frontend.git
synced 2026-05-14 13:17:19 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b391d6d36d | |||
| 500ce18ae5 | |||
| b413a7742c | |||
| e84373fdbd | |||
| caaee14856 | |||
| 28f04df81d | |||
| 48a8c5b2d5 | |||
| 45312ba7fd | |||
| b5dad80e19 | |||
| ae85263d91 | |||
| c5000bcdde | |||
| 5e085c70b0 | |||
| 71fc44284c | |||
| b7e1e23eaa | |||
| 2ee7c6fc2a |
@@ -0,0 +1,40 @@
|
||||
import type { TooltipPositionCallback } from "echarts/types/dist/shared";
|
||||
|
||||
export const TOOLTIP_GAP_PX = 12;
|
||||
export const TOOLTIP_TOP_OFFSET_PX = 10;
|
||||
|
||||
/**
|
||||
* Pins the tooltip near the top of the chart and offsets it horizontally
|
||||
* from the cursor so it never covers the data point being inspected.
|
||||
* For axis-trigger time-series tooltips where the cursor's Y is uncorrelated
|
||||
* with the displayed content.
|
||||
*/
|
||||
export const sideTooltipPosition: TooltipPositionCallback = (
|
||||
point,
|
||||
_params,
|
||||
dom,
|
||||
_rect,
|
||||
size
|
||||
) => {
|
||||
const [cursorX] = point;
|
||||
const [viewW, viewH] = size.viewSize;
|
||||
const [tipW, tipH] = size.contentSize;
|
||||
|
||||
const rtl =
|
||||
dom instanceof HTMLElement && getComputedStyle(dom).direction === "rtl";
|
||||
|
||||
const rightOfCursor = cursorX + TOOLTIP_GAP_PX;
|
||||
const leftOfCursor = cursorX - TOOLTIP_GAP_PX - tipW;
|
||||
|
||||
let x = rtl ? leftOfCursor : rightOfCursor;
|
||||
const overflowsRight = x + tipW > viewW;
|
||||
const overflowsLeft = x < 0;
|
||||
if (overflowsRight || overflowsLeft) {
|
||||
x = rtl ? rightOfCursor : leftOfCursor;
|
||||
}
|
||||
x = Math.max(0, Math.min(x, viewW - tipW));
|
||||
|
||||
const y = Math.max(0, Math.min(TOOLTIP_TOP_OFFSET_PX, viewH - tipH));
|
||||
|
||||
return [x, y];
|
||||
};
|
||||
@@ -11,6 +11,8 @@ import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { LineChartEntity, LineChartState } from "../../data/history";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
|
||||
import { sideTooltipPosition } from "./chart-tooltip-position";
|
||||
import { computeYAxisFractionDigits } from "./y-axis-fraction-digits";
|
||||
import type { ECOption } from "../../resources/echarts/echarts";
|
||||
import { formatDateTimeWithSeconds } from "../../common/datetime/format_date_time";
|
||||
import {
|
||||
@@ -116,9 +118,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
|
||||
private _chartTime: Date = new Date();
|
||||
|
||||
private _previousYAxisLabelValue = 0;
|
||||
|
||||
private _yAxisMaximumFractionDigits = 0;
|
||||
private _yAxisFractionDigits = 1;
|
||||
|
||||
protected render() {
|
||||
return html`
|
||||
@@ -413,8 +413,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
renderMode: "html",
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
position: sideTooltipPosition,
|
||||
confine: true,
|
||||
formatter: this._renderTooltip,
|
||||
},
|
||||
@@ -436,6 +435,14 @@ export class StateHistoryChartLine extends LitElement {
|
||||
const datasets: LineSeriesOption[] = [];
|
||||
const entityIds: string[] = [];
|
||||
const datasetToDataIndex: number[] = [];
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number | null | undefined) => {
|
||||
if (typeof v === "number" && Number.isFinite(v)) {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
}
|
||||
};
|
||||
if (entityStates.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -471,6 +478,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
d.data!.push([timestamp, prevValues[i]]);
|
||||
}
|
||||
d.data!.push([timestamp, datavalues[i]]);
|
||||
trackY(datavalues[i]);
|
||||
});
|
||||
prevValues = datavalues;
|
||||
};
|
||||
@@ -821,6 +829,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
const currentValue = stateObj ? safeParseFloat(stateObj.state) : null;
|
||||
if (currentValue !== null) {
|
||||
data[0].data!.push([now, currentValue]);
|
||||
trackY(currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,6 +837,7 @@ export class StateHistoryChartLine extends LitElement {
|
||||
Array.prototype.push.apply(datasets, data);
|
||||
});
|
||||
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = datasets;
|
||||
this._entityIds = entityIds;
|
||||
this._datasetToDataIndex = datasetToDataIndex;
|
||||
@@ -861,20 +871,8 @@ export class StateHistoryChartLine extends LitElement {
|
||||
}
|
||||
|
||||
private _formatYAxisLabel = (value: number) => {
|
||||
// show the first significant digit for tiny values
|
||||
const maximumFractionDigits = Math.max(
|
||||
1,
|
||||
// use the difference to the previous value to determine the number of significant digits #25526
|
||||
-Math.floor(
|
||||
Math.log10(Math.abs(value - this._previousYAxisLabelValue || 1))
|
||||
)
|
||||
);
|
||||
this._yAxisMaximumFractionDigits = Math.max(
|
||||
this._yAxisMaximumFractionDigits,
|
||||
maximumFractionDigits
|
||||
);
|
||||
const label = formatNumber(value, this.hass.locale, {
|
||||
maximumFractionDigits: this._yAxisMaximumFractionDigits,
|
||||
maximumFractionDigits: this._yAxisFractionDigits,
|
||||
});
|
||||
const width = measureTextWidth(label, 12) + 5;
|
||||
if (width > this._yWidth) {
|
||||
@@ -884,7 +882,6 @@ export class StateHistoryChartLine extends LitElement {
|
||||
chartIndex: this.chartIndex,
|
||||
});
|
||||
}
|
||||
this._previousYAxisLabelValue = value;
|
||||
return label;
|
||||
};
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { computeRTL } from "../../common/util/compute_rtl";
|
||||
import type { TimelineEntity } from "../../data/history";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base";
|
||||
import { sideTooltipPosition } from "./chart-tooltip-position";
|
||||
import { computeTimelineColor } from "./timeline-color";
|
||||
import type { ECOption } from "../../resources/echarts/echarts";
|
||||
import echarts from "../../resources/echarts/echarts";
|
||||
@@ -263,8 +264,7 @@ export class StateHistoryChartTimeline extends LitElement {
|
||||
},
|
||||
tooltip: {
|
||||
renderMode: "html",
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
position: sideTooltipPosition,
|
||||
confine: true,
|
||||
formatter: this._renderTooltip,
|
||||
},
|
||||
|
||||
@@ -39,7 +39,9 @@ import type { HomeAssistant } from "../../types";
|
||||
import { getPeriodicAxisLabelConfig } from "./axis-label";
|
||||
import type { CustomLegendOption } from "./ha-chart-base";
|
||||
import "./ha-chart-base";
|
||||
import { sideTooltipPosition } from "./chart-tooltip-position";
|
||||
import { fillDataGapsAndRoundCaps } from "./round-caps";
|
||||
import { computeYAxisFractionDigits } from "./y-axis-fraction-digits";
|
||||
|
||||
export const supportedStatTypeMap: Record<StatisticType, StatisticType> = {
|
||||
mean: "mean",
|
||||
@@ -130,7 +132,7 @@ export class StatisticsChart extends LitElement {
|
||||
|
||||
private _computedStyle?: CSSStyleDeclaration;
|
||||
|
||||
private _previousYAxisLabelValue = 0;
|
||||
private _yAxisFractionDigits = 1;
|
||||
|
||||
protected shouldUpdate(changedProps: PropertyValues<this>): boolean {
|
||||
return changedProps.size > 1 || !changedProps.has("hass");
|
||||
@@ -459,8 +461,7 @@ export class StatisticsChart extends LitElement {
|
||||
tooltip: {
|
||||
trigger: "axis",
|
||||
renderMode: "html",
|
||||
position: "bottom",
|
||||
align: "center",
|
||||
position: sideTooltipPosition,
|
||||
confine: true,
|
||||
formatter: this._renderTooltip,
|
||||
},
|
||||
@@ -495,6 +496,14 @@ export class StatisticsChart extends LitElement {
|
||||
const chartStacked = this.chartType.endsWith("stack");
|
||||
const statisticsData = Object.entries(this.statisticsData);
|
||||
const totalDataSets: typeof this._chartData = [];
|
||||
let yMin = Infinity;
|
||||
let yMax = -Infinity;
|
||||
const trackY = (v: number | null | undefined) => {
|
||||
if (typeof v === "number" && Number.isFinite(v)) {
|
||||
if (v < yMin) yMin = v;
|
||||
if (v > yMax) yMax = v;
|
||||
}
|
||||
};
|
||||
const legendData: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -600,6 +609,9 @@ export class StatisticsChart extends LitElement {
|
||||
d.data!.push([prevEndTime, null]);
|
||||
}
|
||||
d.data!.push([start, ...dataValues[i]!]);
|
||||
// For band-top rows dataValues[i] is [diff, top]; the actual Y is
|
||||
// the last element. For regular rows it's [value]. Same call works.
|
||||
trackY(dataValues[i][dataValues[i].length - 1]);
|
||||
} else {
|
||||
let time = start;
|
||||
if (centerBars) {
|
||||
@@ -610,6 +622,7 @@ export class StatisticsChart extends LitElement {
|
||||
// Data value should always be a scalar for bar charts. Pass in
|
||||
// real start time as extra value to allow formatting tooltip.
|
||||
d.data!.push([time, dataValues[i][0]!, start, end]);
|
||||
trackY(dataValues[i][0]);
|
||||
}
|
||||
});
|
||||
prevValues = dataValues;
|
||||
@@ -822,6 +835,7 @@ export class StatisticsChart extends LitElement {
|
||||
val.push(currentValue);
|
||||
}
|
||||
statDataSets[i].data!.push([now, ...val]);
|
||||
trackY(val[val.length - 1]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -855,6 +869,7 @@ export class StatisticsChart extends LitElement {
|
||||
});
|
||||
});
|
||||
|
||||
this._yAxisFractionDigits = computeYAxisFractionDigits(yMin, yMax);
|
||||
this._chartData = totalDataSets;
|
||||
if (legendData.length !== this._legendData?.length) {
|
||||
// only update the legend if it has changed or it will trigger options update
|
||||
@@ -888,21 +903,10 @@ export class StatisticsChart extends LitElement {
|
||||
return Math.abs(value) < 1 ? value : roundingFn(value);
|
||||
}
|
||||
|
||||
private _formatYAxisLabel = (value: number) => {
|
||||
// show the first significant digit for tiny values
|
||||
const maximumFractionDigits = Math.max(
|
||||
1,
|
||||
// use the difference to the previous value to determine the number of significant digits #25526
|
||||
-Math.floor(
|
||||
Math.log10(Math.abs(value - this._previousYAxisLabelValue || 1))
|
||||
)
|
||||
);
|
||||
const label = formatNumber(value, this.hass.locale, {
|
||||
maximumFractionDigits,
|
||||
private _formatYAxisLabel = (value: number) =>
|
||||
formatNumber(value, this.hass.locale, {
|
||||
maximumFractionDigits: this._yAxisFractionDigits,
|
||||
});
|
||||
this._previousYAxisLabelValue = value;
|
||||
return label;
|
||||
};
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// Derive the number of decimal digits to use for Y-axis labels from the
|
||||
// observed data range. We estimate the tick interval as `range / 10` (twice
|
||||
// ECharts' default splitNumber of 5, as a safety margin against finer "nice"
|
||||
// intervals), then derive `ceil(-log10(interval))`.
|
||||
export function computeYAxisFractionDigits(min: number, max: number): number {
|
||||
const range = max - min;
|
||||
if (!Number.isFinite(range) || range <= 0) return 1;
|
||||
return Math.max(0, Math.ceil(-Math.log10(range / 10)));
|
||||
}
|
||||
@@ -142,6 +142,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
private async _getStatisticIds() {
|
||||
this.statisticIds = await getStatisticIds(this.hass, this.statisticTypes);
|
||||
this._picker?.requestUpdate();
|
||||
this._valueRenderer = this._makeValueRenderer();
|
||||
}
|
||||
|
||||
private _getItems = () =>
|
||||
@@ -317,7 +318,7 @@ export class HaStatisticPicker extends LitElement {
|
||||
}
|
||||
);
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = (value) => {
|
||||
private _renderValue(value: string) {
|
||||
const statisticId = value;
|
||||
|
||||
const item = this._computeItem(statisticId);
|
||||
@@ -341,7 +342,13 @@ export class HaStatisticPicker extends LitElement {
|
||||
? html`<span slot="supporting-text">${item.secondary}</span>`
|
||||
: nothing}
|
||||
`;
|
||||
};
|
||||
}
|
||||
|
||||
private _makeValueRenderer(): PickerValueRenderer {
|
||||
return (value) => this._renderValue(value);
|
||||
}
|
||||
|
||||
private _valueRenderer: PickerValueRenderer = this._makeValueRenderer();
|
||||
|
||||
private _computeItem(statisticId: string): StatisticComboBoxItem {
|
||||
const stateObj = this.hass.states[statisticId];
|
||||
|
||||
@@ -5,10 +5,10 @@ import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import type { Analytics, AnalyticsPreferences } from "../data/analytics";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import "./ha-md-list-item";
|
||||
import "./ha-switch";
|
||||
import "./ha-tooltip";
|
||||
import type { HaSwitch } from "./ha-switch";
|
||||
import "./ha-tooltip";
|
||||
import "./item/ha-row-item";
|
||||
|
||||
const ADDITIONAL_PREFERENCES = ["usage", "statistics"] as const;
|
||||
|
||||
@@ -33,7 +33,7 @@ export class HaAnalytics extends LitElement {
|
||||
const baseEnabled = !loading && this.analytics!.preferences.base;
|
||||
|
||||
return html`
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.base.title`
|
||||
@@ -52,10 +52,10 @@ export class HaAnalytics extends LitElement {
|
||||
.disabled=${loading}
|
||||
name="base"
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
${ADDITIONAL_PREFERENCES.map(
|
||||
(preference) => html`
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.${preference}.title`
|
||||
@@ -81,10 +81,10 @@ export class HaAnalytics extends LitElement {
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.need_base_enabled`
|
||||
)}
|
||||
</ha-tooltip>`}
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`
|
||||
)}
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.localize(
|
||||
`ui.panel.${this.translationKeyPanel}.analytics.preferences.diagnostics.title`
|
||||
@@ -103,7 +103,7 @@ export class HaAnalytics extends LitElement {
|
||||
.disabled=${loading}
|
||||
name="diagnostics"
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -139,10 +139,8 @@ export class HaAnalytics extends LitElement {
|
||||
color: var(--error-color);
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -27,6 +27,7 @@ import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, ReactiveElement, render } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { consume } from "@lit/context";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { stopPropagation } from "../common/dom/stop_propagation";
|
||||
@@ -43,7 +44,14 @@ import type {
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { showToast } from "../util/toast";
|
||||
import { documentationUrl } from "../util/documentation-url";
|
||||
import { labelsContext } from "../data/context";
|
||||
import {
|
||||
internationalizationContext,
|
||||
registriesContext,
|
||||
statesContext,
|
||||
labelsContext,
|
||||
configContext,
|
||||
formattersContext,
|
||||
} from "../data/context";
|
||||
import type { LabelRegistryEntry } from "../data/label/label_registry";
|
||||
import "./ha-code-editor-completion-items";
|
||||
import type { CompletionItem } from "./ha-code-editor-completion-items";
|
||||
@@ -78,8 +86,6 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
|
||||
@property() public mode = "yaml";
|
||||
|
||||
public hass?: HomeAssistant;
|
||||
|
||||
// eslint-disable-next-line lit/no-native-attributes
|
||||
@property({ type: Boolean }) public autofocus = false;
|
||||
|
||||
@@ -123,9 +129,29 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
|
||||
@state() private _canCopy = false;
|
||||
|
||||
@consume({ context: labelsContext, subscribe: true })
|
||||
@state()
|
||||
private _labels?: LabelRegistryEntry[];
|
||||
@consume({ context: configContext, subscribe: true })
|
||||
private _config?: ContextType<typeof configContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n?: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: labelsContext, subscribe: true })
|
||||
private _labels?: ContextType<typeof labelsContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: registriesContext, subscribe: true })
|
||||
private _registries?: ContextType<typeof registriesContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: formattersContext, subscribe: true })
|
||||
private _formatters?: ContextType<typeof formattersContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: statesContext, subscribe: true })
|
||||
private _states?: ContextType<typeof statesContext>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
private _loadedCodeMirror?: typeof import("../resources/codemirror");
|
||||
@@ -189,9 +215,9 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
const line = doc.lineAt(pos);
|
||||
const message = `${
|
||||
err.reason ||
|
||||
this.hass?.localize("ui.components.yaml-editor.error") ||
|
||||
this._i18n?.localize("ui.components.yaml-editor.error") ||
|
||||
"YAML syntax error"
|
||||
}${err.mark ? ` (${this.hass?.localize("ui.components.yaml-editor.error_location", { line: err.mark.line + 1, column: err.mark.column + 1 })})` : ""}`;
|
||||
}${err.mark ? ` (${this._i18n?.localize("ui.components.yaml-editor.error_location", { line: err.mark.line + 1, column: err.mark.column + 1 })})` : ""}`;
|
||||
diagnostics = [{ from: pos, to: line.to, severity: "error", message }];
|
||||
}
|
||||
this.codemirror.dispatch(
|
||||
@@ -396,8 +422,8 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
this._loadedCodeMirror!.haJinjaHoverSource(
|
||||
view,
|
||||
pos,
|
||||
this.hass ? documentationUrl(this.hass, "") : undefined,
|
||||
this.hass ? this._hassArgHoverContext() : undefined
|
||||
this._config ? documentationUrl(this._config, "") : undefined,
|
||||
this._hassArgHoverContext()
|
||||
),
|
||||
{ hoverTime: 300 }
|
||||
),
|
||||
@@ -408,7 +434,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
const completionSources: CompletionSource[] = [
|
||||
this._loadedCodeMirror.haJinjaCompletionSource,
|
||||
];
|
||||
if (this.autocompleteEntities && this.hass) {
|
||||
if (this.autocompleteEntities) {
|
||||
completionSources.push(this._entityCompletions.bind(this));
|
||||
}
|
||||
if (this.autocompleteIcons) {
|
||||
@@ -447,12 +473,12 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
private _fullscreenLabel(): string {
|
||||
if (this._isFullscreen) {
|
||||
return (
|
||||
this.hass?.localize("ui.components.yaml-editor.exit_fullscreen") ||
|
||||
this._i18n?.localize("ui.components.yaml-editor.exit_fullscreen") ||
|
||||
"Exit fullscreen"
|
||||
);
|
||||
}
|
||||
return (
|
||||
this.hass?.localize("ui.components.yaml-editor.enter_fullscreen") ||
|
||||
this._i18n?.localize("ui.components.yaml-editor.enter_fullscreen") ||
|
||||
"Enter fullscreen"
|
||||
);
|
||||
}
|
||||
@@ -507,7 +533,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
{
|
||||
id: "test",
|
||||
label:
|
||||
this.hass?.localize(
|
||||
this._i18n?.localize(
|
||||
`ui.components.yaml-editor.test_${this.testing ? "off" : "on"}`
|
||||
) || "Test",
|
||||
path: this.testing ? mdiBugOutline : mdiBug,
|
||||
@@ -518,14 +544,14 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
{
|
||||
id: "undo",
|
||||
disabled: !this._canUndo,
|
||||
label: this.hass?.localize("ui.common.undo") || "Undo",
|
||||
label: this._i18n?.localize("ui.common.undo") || "Undo",
|
||||
path: mdiUndo,
|
||||
action: (e: Event) => this._handleUndoClick(e),
|
||||
},
|
||||
{
|
||||
id: "redo",
|
||||
disabled: !this._canRedo,
|
||||
label: this.hass?.localize("ui.common.redo") || "Redo",
|
||||
label: this._i18n?.localize("ui.common.redo") || "Redo",
|
||||
path: mdiRedo,
|
||||
action: (e: Event) => this._handleRedoClick(e),
|
||||
},
|
||||
@@ -533,7 +559,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
id: "copy",
|
||||
disabled: !this._canCopy,
|
||||
label:
|
||||
this.hass?.localize("ui.components.yaml-editor.copy_to_clipboard") ||
|
||||
this._i18n?.localize("ui.components.yaml-editor.copy_to_clipboard") ||
|
||||
"Copy to Clipboard",
|
||||
path: mdiContentCopy,
|
||||
action: (e: Event) => this._handleClipboardClick(e),
|
||||
@@ -541,7 +567,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
{
|
||||
id: "find-replace",
|
||||
label:
|
||||
this.hass?.localize("ui.components.yaml-editor.find_and_replace") ||
|
||||
this._i18n?.localize("ui.components.yaml-editor.find_and_replace") ||
|
||||
"Find and replace",
|
||||
path: mdiFindReplace,
|
||||
action: (e: Event) => this._handleFindReplaceClick(e),
|
||||
@@ -583,7 +609,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
await copyToClipboard(this.value);
|
||||
showToast(this, {
|
||||
message:
|
||||
this.hass?.localize("ui.common.copied_clipboard") ||
|
||||
this._i18n?.localize("ui.common.copied_clipboard") ||
|
||||
"Copied to clipboard",
|
||||
});
|
||||
}
|
||||
@@ -651,12 +677,11 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds a HassArgHoverContext from the current hass object so that
|
||||
* Builds a HassArgHoverContext from the context objects so that
|
||||
* haJinjaHoverSource can resolve entity / device / area friendly names
|
||||
* without importing the full HomeAssistant type into the resource file.
|
||||
*/
|
||||
private _hassArgHoverContext(): HassArgHoverContext {
|
||||
const hass = this.hass!;
|
||||
const labelMap: Record<
|
||||
string,
|
||||
{ name: string; description?: string | null }
|
||||
@@ -668,27 +693,33 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
};
|
||||
}
|
||||
return {
|
||||
states: hass.states as HassArgHoverContext["states"],
|
||||
devices: hass.devices as HassArgHoverContext["devices"],
|
||||
areas: hass.areas as HassArgHoverContext["areas"],
|
||||
floors: hass.floors as HassArgHoverContext["floors"],
|
||||
entities: hass.entities as HassArgHoverContext["entities"],
|
||||
states: this._states as HassArgHoverContext["states"],
|
||||
devices: this._registries?.devices as HassArgHoverContext["devices"],
|
||||
areas: this._registries?.areas as HassArgHoverContext["areas"],
|
||||
floors: this._registries?.floors as HassArgHoverContext["floors"],
|
||||
entities: this._registries?.entities as HassArgHoverContext["entities"],
|
||||
labels: labelMap,
|
||||
formatEntityState: (entityId) =>
|
||||
hass.formatEntityState(hass.states[entityId]),
|
||||
this._formatters!.formatEntityState(this._states![entityId]),
|
||||
formatEntityName: (entityId) => {
|
||||
const stateObj = hass.states[entityId];
|
||||
const stateObj = this._states?.[entityId];
|
||||
return (
|
||||
(stateObj?.attributes.friendly_name as string | undefined) ??
|
||||
hass.entities[entityId]?.name ??
|
||||
this._registries?.entities?.[entityId]?.name ??
|
||||
undefined
|
||||
);
|
||||
},
|
||||
formatAttributeName: (entityId, attribute) =>
|
||||
hass.formatEntityAttributeName(hass.states[entityId], attribute),
|
||||
this._formatters!.formatEntityAttributeName(
|
||||
this._states![entityId],
|
||||
attribute
|
||||
),
|
||||
formatAttributeValue: (entityId, attribute) =>
|
||||
hass.formatEntityAttributeValue(hass.states[entityId], attribute),
|
||||
localize: (key) => hass.localize(key as never),
|
||||
this._formatters!.formatEntityAttributeValue(
|
||||
this._states![entityId],
|
||||
attribute
|
||||
),
|
||||
localize: (key) => this._i18n!.localize(key as never),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -698,49 +729,51 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
? completion.apply
|
||||
: completion.label;
|
||||
const context = getEntityContext(
|
||||
this.hass!.states[key],
|
||||
this.hass!.entities,
|
||||
this.hass!.devices,
|
||||
this.hass!.areas,
|
||||
this.hass!.floors
|
||||
this._states![key],
|
||||
this._registries!.entities,
|
||||
this._registries!.devices,
|
||||
this._registries!.areas,
|
||||
this._registries!.floors
|
||||
);
|
||||
|
||||
const completionInfo = document.createElement("div");
|
||||
completionInfo.classList.add("completion-info");
|
||||
|
||||
const formattedState = this.hass!.formatEntityState(this.hass!.states[key]);
|
||||
const formattedState = this._formatters!.formatEntityState(
|
||||
this._states![key]
|
||||
);
|
||||
|
||||
const completionItems: CompletionItem[] = [
|
||||
{
|
||||
label: this.hass!.localize(
|
||||
label: this._i18n!.localize(
|
||||
"ui.components.entity.entity-state-picker.state"
|
||||
),
|
||||
value: formattedState,
|
||||
subValue:
|
||||
// If the state exactly matches the formatted state, don't show the raw state
|
||||
this.hass!.states[key].state === formattedState
|
||||
this._states![key].state === formattedState
|
||||
? undefined
|
||||
: this.hass!.states[key].state,
|
||||
: this._states![key].state,
|
||||
},
|
||||
];
|
||||
|
||||
if (context.device && context.device.name) {
|
||||
completionItems.push({
|
||||
label: this.hass!.localize("ui.components.device-picker.device"),
|
||||
label: this._i18n!.localize("ui.components.device-picker.device"),
|
||||
value: context.device.name,
|
||||
});
|
||||
}
|
||||
|
||||
if (context.area && context.area.name) {
|
||||
completionItems.push({
|
||||
label: this.hass!.localize("ui.components.area-picker.area"),
|
||||
label: this._i18n!.localize("ui.components.area-picker.area"),
|
||||
value: context.area.name,
|
||||
});
|
||||
}
|
||||
|
||||
if (context.floor && context.floor.name) {
|
||||
completionItems.push({
|
||||
label: this.hass!.localize("ui.components.floor-picker.floor"),
|
||||
label: this._i18n!.localize("ui.components.floor-picker.floor"),
|
||||
value: context.floor.name,
|
||||
});
|
||||
}
|
||||
@@ -761,15 +794,15 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
entityId: string,
|
||||
attribute: string
|
||||
): CompletionInfo | null => {
|
||||
if (!this.hass) return null;
|
||||
const stateObj = this.hass.states[entityId];
|
||||
if (!this._states || !this._formatters) return null;
|
||||
const stateObj = this._states[entityId];
|
||||
if (!stateObj) return null;
|
||||
|
||||
const translatedName = this.hass.formatEntityAttributeName(
|
||||
const translatedName = this._formatters.formatEntityAttributeName(
|
||||
stateObj,
|
||||
attribute
|
||||
);
|
||||
const formattedValue = this.hass.formatEntityAttributeValue(
|
||||
const formattedValue = this._formatters.formatEntityAttributeValue(
|
||||
stateObj,
|
||||
attribute
|
||||
);
|
||||
@@ -809,9 +842,9 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
completion: Completion
|
||||
): CompletionInfo | Promise<CompletionInfo> | null => {
|
||||
if (
|
||||
this.hass &&
|
||||
this._states &&
|
||||
typeof completion.apply === "string" &&
|
||||
completion.apply in this.hass.states
|
||||
completion.apply in this._states
|
||||
) {
|
||||
return this._renderInfo(completion);
|
||||
}
|
||||
@@ -1020,7 +1053,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
private _statesDotNotationCompletions(
|
||||
context: CompletionContext
|
||||
): CompletionResult | null | undefined {
|
||||
if (!this.hass) return undefined;
|
||||
if (!this._states) return undefined;
|
||||
|
||||
const { state: editorState, pos } = context;
|
||||
const tree = this._loadedCodeMirror!.syntaxTree(editorState);
|
||||
@@ -1129,9 +1162,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
case 0: {
|
||||
// states. → offer all unique domains
|
||||
const domains = [
|
||||
...new Set(
|
||||
Object.keys(this.hass.states).map((id) => id.split(".")[0])
|
||||
),
|
||||
...new Set(Object.keys(this._states).map((id) => id.split(".")[0])),
|
||||
].sort();
|
||||
return {
|
||||
from: completionFrom,
|
||||
@@ -1142,7 +1173,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
case 1: {
|
||||
// states.<domain>. → offer entity object_ids for that domain
|
||||
const [domain] = segments;
|
||||
const entities = Object.keys(this.hass.states)
|
||||
const entities = Object.keys(this._states)
|
||||
.filter((id) => id.startsWith(`${domain}.`))
|
||||
.map((id) => id.split(".").slice(1).join("."));
|
||||
if (!entities.length) return { from: completionFrom, options: [] };
|
||||
@@ -1172,7 +1203,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
}
|
||||
// Offer attribute names from the entity's state object
|
||||
const entityId = `${domain}.${entity}`;
|
||||
const entityState = this.hass.states[entityId];
|
||||
const entityState = this._states[entityId];
|
||||
if (!entityState) return { from: completionFrom, options: [] };
|
||||
const attrNames = Object.keys(entityState.attributes).sort();
|
||||
return {
|
||||
@@ -1342,8 +1373,8 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
): CompletionResult {
|
||||
const from = stringNode.from + 1;
|
||||
const empty: CompletionResult = { from, options: [] };
|
||||
if (!entityId || !this.hass) return empty;
|
||||
const entityState = this.hass.states[entityId];
|
||||
if (!entityId || !this._states) return empty;
|
||||
const entityState = this._states[entityId];
|
||||
if (!entityState) return empty;
|
||||
const attrs = Object.keys(entityState.attributes).sort();
|
||||
if (!attrs.length) return empty;
|
||||
@@ -1363,7 +1394,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
from: number;
|
||||
to: number;
|
||||
}): CompletionResult | null {
|
||||
const states = this._getStates(this.hass!.states);
|
||||
const states = this._getStates(this._states!);
|
||||
if (!states?.length) return null;
|
||||
// from is stringNode.from + 1 to skip the opening quote character.
|
||||
const from = stringNode.from + 1;
|
||||
@@ -1397,8 +1428,8 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
from: number;
|
||||
to: number;
|
||||
}): CompletionResult | null {
|
||||
if (!this.hass?.devices) return null;
|
||||
const devices = this._getDevices(this.hass.devices);
|
||||
if (!this._registries?.devices) return null;
|
||||
const devices = this._getDevices(this._registries.devices);
|
||||
if (!devices.length) return null;
|
||||
return {
|
||||
from: stringNode.from + 1,
|
||||
@@ -1426,8 +1457,8 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
from: number;
|
||||
to: number;
|
||||
}): CompletionResult | null {
|
||||
if (!this.hass?.areas) return null;
|
||||
const areas = this._getAreas(this.hass.areas);
|
||||
if (!this._registries?.areas) return null;
|
||||
const areas = this._getAreas(this._registries.areas);
|
||||
if (!areas.length) return null;
|
||||
return {
|
||||
from: stringNode.from + 1,
|
||||
@@ -1455,8 +1486,8 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
from: number;
|
||||
to: number;
|
||||
}): CompletionResult | null {
|
||||
if (!this.hass?.floors) return null;
|
||||
const floors = this._getFloors(this.hass.floors);
|
||||
if (!this._registries?.floors) return null;
|
||||
const floors = this._getFloors(this._registries.floors);
|
||||
if (!floors.length) return null;
|
||||
return {
|
||||
from: stringNode.from + 1,
|
||||
@@ -1556,7 +1587,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
|
||||
// If cursor is after the entity field, show all entities
|
||||
if (context.pos >= afterField) {
|
||||
const states = this._getStates(this.hass!.states);
|
||||
const states = this._getStates(this._states!);
|
||||
|
||||
if (!states || !states.length) {
|
||||
return null;
|
||||
@@ -1611,7 +1642,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
const afterListMarker = currentLine.from + listItemMatch[0].length;
|
||||
|
||||
if (context.pos >= afterListMarker) {
|
||||
const states = this._getStates(this.hass!.states);
|
||||
const states = this._getStates(this._states!);
|
||||
|
||||
if (!states || !states.length) {
|
||||
return null;
|
||||
@@ -1671,7 +1702,7 @@ export class HaCodeEditor extends ReactiveElement {
|
||||
return null;
|
||||
}
|
||||
|
||||
const states = this._getStates(this.hass!.states);
|
||||
const states = this._getStates(this._states!);
|
||||
|
||||
if (!states || !states.length) {
|
||||
return null;
|
||||
|
||||
+239
-123
@@ -1,34 +1,109 @@
|
||||
import { DrawerBase } from "@material/mwc-drawer/mwc-drawer-base";
|
||||
import { styles } from "@material/mwc-drawer/mwc-drawer.css";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { css } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "@home-assistant/webawesome/dist/components/drawer/drawer";
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html } from "lit";
|
||||
import { customElement, property, query } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { HASSDomEvent } from "../common/dom/fire_event";
|
||||
import { SwipeGestureRecognizer } from "../common/util/swipe-gesture-recognizer";
|
||||
|
||||
declare global {
|
||||
interface HASSDomEvents {
|
||||
"hass-drawer-closed": undefined;
|
||||
"hass-layout-transition": { active: boolean; reason?: string };
|
||||
}
|
||||
interface HTMLElementEventMap {
|
||||
"hass-drawer-closed": HASSDomEvent<HASSDomEvents["hass-drawer-closed"]>;
|
||||
"hass-layout-transition": HASSDomEvent<
|
||||
HASSDomEvents["hass-layout-transition"]
|
||||
>;
|
||||
}
|
||||
}
|
||||
|
||||
const blockingElements = (document as any).$blockingElements;
|
||||
|
||||
@customElement("ha-drawer")
|
||||
export class HaDrawer extends DrawerBase {
|
||||
@property() public direction: "ltr" | "rtl" = "ltr";
|
||||
export class HaDrawer extends LitElement {
|
||||
private static readonly _SWIPE_AXIS_TOLERANCE = 32;
|
||||
|
||||
private _mc?: HammerManager;
|
||||
@property({ reflect: true }) public direction: "ltr" | "rtl" = "ltr";
|
||||
|
||||
private _rtlStyle?: HTMLElement;
|
||||
@property() public type = "";
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public open = false;
|
||||
|
||||
@query("wa-drawer") private _modalDrawer?: HTMLElement;
|
||||
|
||||
@query(".sidebar-shell") private _sidebarShell?: HTMLElement;
|
||||
|
||||
private _sidebarTransitionActive = false;
|
||||
|
||||
private _transitionTarget?: HTMLElement;
|
||||
|
||||
private _gestureRecognizer = new SwipeGestureRecognizer({
|
||||
velocitySwipeThreshold: 0.35,
|
||||
});
|
||||
|
||||
private _touchStartY = 0;
|
||||
|
||||
private _touchDeltaY = 0;
|
||||
|
||||
private get _modal() {
|
||||
return this.type === "modal";
|
||||
}
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return this._modal
|
||||
? html`
|
||||
<slot name="appContent"></slot>
|
||||
<wa-drawer
|
||||
placement="start"
|
||||
.open=${this.open}
|
||||
light-dismiss
|
||||
without-header
|
||||
@touchstart=${this._handleTouchStart}
|
||||
@wa-after-hide=${this._handleAfterHide}
|
||||
>
|
||||
<slot></slot>
|
||||
</wa-drawer>
|
||||
`
|
||||
: html`
|
||||
<div class="layout">
|
||||
<div class="sidebar-shell">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="app-content">
|
||||
<slot name="appContent"></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
protected updated(_: PropertyValues<this>) {
|
||||
this._syncTransitionListeners();
|
||||
|
||||
if (!this.open) {
|
||||
this._resetSwipeTracking();
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._syncTransitionListeners();
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._removeTransitionListeners();
|
||||
this._unregisterSwipeHandlers();
|
||||
}
|
||||
|
||||
private _handleAfterHide(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this.open = false;
|
||||
fireEvent(this, "hass-drawer-closed");
|
||||
}
|
||||
|
||||
private _closeModalDrawer() {
|
||||
this.open = false;
|
||||
}
|
||||
|
||||
private _handleDrawerTransitionStart = (ev: TransitionEvent) => {
|
||||
if (ev.propertyName !== "width" || this._sidebarTransitionActive) {
|
||||
return;
|
||||
@@ -51,150 +126,191 @@ export class HaDrawer extends DrawerBase {
|
||||
});
|
||||
};
|
||||
|
||||
protected createAdapter() {
|
||||
return {
|
||||
...super.createAdapter(),
|
||||
trapFocus: () => {
|
||||
blockingElements.push(this);
|
||||
this.appContent.inert = true;
|
||||
document.body.style.overflow = "hidden";
|
||||
},
|
||||
releaseFocus: () => {
|
||||
blockingElements.remove(this);
|
||||
this.appContent.inert = false;
|
||||
document.body.style.overflow = "";
|
||||
},
|
||||
};
|
||||
private _handleTouchStart = (ev: TouchEvent) => {
|
||||
if (!this._modal || !this.open) {
|
||||
return;
|
||||
}
|
||||
|
||||
const drawer = this._modalDrawer;
|
||||
const dialog = drawer?.shadowRoot?.querySelector(
|
||||
"dialog"
|
||||
) as HTMLDialogElement | null;
|
||||
|
||||
if (!dialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
const path = ev.composedPath();
|
||||
|
||||
if (!path.includes(dialog)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.stopPropagation();
|
||||
this._startSwipeTracking(ev.touches[0].clientX, ev.touches[0].clientY);
|
||||
};
|
||||
|
||||
private _startSwipeTracking(clientX: number, clientY: number) {
|
||||
document.addEventListener("touchmove", this._handleTouchMove, {
|
||||
passive: true,
|
||||
});
|
||||
document.addEventListener("touchend", this._handleTouchEnd);
|
||||
document.addEventListener("touchcancel", this._handleTouchEnd);
|
||||
|
||||
this._touchStartY = clientY;
|
||||
this._touchDeltaY = 0;
|
||||
this._gestureRecognizer.start(clientX);
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
super.updated(changedProps);
|
||||
if (changedProps.has("direction")) {
|
||||
this.mdcRoot.dir = this.direction;
|
||||
if (this.direction === "rtl") {
|
||||
this._rtlStyle = document.createElement("style");
|
||||
this._rtlStyle.innerHTML = `
|
||||
.mdc-drawer--animate {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
.mdc-drawer--opening {
|
||||
transform: translateX(0);
|
||||
}
|
||||
.mdc-drawer--closing {
|
||||
transform: translateX(100%);
|
||||
}
|
||||
`;
|
||||
private _handleTouchMove = (ev: TouchEvent) => {
|
||||
const currentX = ev.touches[0].clientX;
|
||||
const currentY = ev.touches[0].clientY;
|
||||
this._touchDeltaY = Math.abs(currentY - this._touchStartY);
|
||||
this._gestureRecognizer.move(currentX);
|
||||
};
|
||||
|
||||
this.shadowRoot!.appendChild(this._rtlStyle);
|
||||
} else if (this._rtlStyle) {
|
||||
this.shadowRoot!.removeChild(this._rtlStyle);
|
||||
private _handleTouchEnd = () => {
|
||||
this._unregisterSwipeHandlers();
|
||||
|
||||
const result = this._gestureRecognizer.end();
|
||||
const isHorizontalGesture =
|
||||
Math.abs(result.delta) >
|
||||
this._touchDeltaY + HaDrawer._SWIPE_AXIS_TOLERANCE;
|
||||
|
||||
if (!isHorizontalGesture) {
|
||||
this._resetSwipeTracking();
|
||||
return;
|
||||
}
|
||||
|
||||
const drawerDialog = this._modalDrawer?.shadowRoot?.querySelector(
|
||||
'[part="dialog"]'
|
||||
) as HTMLElement | null;
|
||||
const drawerWidth = drawerDialog?.offsetWidth || 0;
|
||||
|
||||
if (result.isSwipe) {
|
||||
const closeByVelocity =
|
||||
this.direction === "rtl"
|
||||
? result.isDownwardSwipe
|
||||
: !result.isDownwardSwipe;
|
||||
|
||||
if (closeByVelocity) {
|
||||
this._closeModalDrawer();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (changedProps.has("open") && this.open && this.type === "modal") {
|
||||
this._setupSwipe();
|
||||
} else if (this._mc) {
|
||||
this._mc.destroy();
|
||||
this._mc = undefined;
|
||||
const closeByDistance =
|
||||
drawerWidth > 0 &&
|
||||
(this.direction === "rtl"
|
||||
? result.delta > 0 && Math.abs(result.delta) > drawerWidth * 0.5
|
||||
: result.delta < 0 && Math.abs(result.delta) > drawerWidth * 0.5);
|
||||
|
||||
if (closeByDistance) {
|
||||
this._closeModalDrawer();
|
||||
}
|
||||
};
|
||||
|
||||
private _unregisterSwipeHandlers() {
|
||||
document.removeEventListener("touchmove", this._handleTouchMove);
|
||||
document.removeEventListener("touchend", this._handleTouchEnd);
|
||||
document.removeEventListener("touchcancel", this._handleTouchEnd);
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
super.firstUpdated();
|
||||
this.mdcRoot?.addEventListener(
|
||||
private _resetSwipeTracking() {
|
||||
this._unregisterSwipeHandlers();
|
||||
this._gestureRecognizer.reset();
|
||||
this._touchStartY = 0;
|
||||
this._touchDeltaY = 0;
|
||||
}
|
||||
|
||||
private _syncTransitionListeners() {
|
||||
if (this._transitionTarget === this._sidebarShell) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._removeTransitionListeners();
|
||||
|
||||
if (!this._sidebarShell) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._transitionTarget = this._sidebarShell;
|
||||
this._transitionTarget.addEventListener(
|
||||
"transitionstart",
|
||||
this._handleDrawerTransitionStart
|
||||
);
|
||||
this.mdcRoot?.addEventListener(
|
||||
this._transitionTarget.addEventListener(
|
||||
"transitionend",
|
||||
this._handleDrawerTransitionEnd
|
||||
);
|
||||
this.mdcRoot?.addEventListener(
|
||||
this._transitionTarget.addEventListener(
|
||||
"transitioncancel",
|
||||
this._handleDrawerTransitionEnd
|
||||
);
|
||||
}
|
||||
|
||||
public disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this.mdcRoot?.removeEventListener(
|
||||
private _removeTransitionListeners() {
|
||||
if (!this._transitionTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._transitionTarget.removeEventListener(
|
||||
"transitionstart",
|
||||
this._handleDrawerTransitionStart
|
||||
);
|
||||
this.mdcRoot?.removeEventListener(
|
||||
this._transitionTarget.removeEventListener(
|
||||
"transitionend",
|
||||
this._handleDrawerTransitionEnd
|
||||
);
|
||||
this.mdcRoot?.removeEventListener(
|
||||
this._transitionTarget.removeEventListener(
|
||||
"transitioncancel",
|
||||
this._handleDrawerTransitionEnd
|
||||
);
|
||||
this._transitionTarget = undefined;
|
||||
}
|
||||
|
||||
private async _setupSwipe() {
|
||||
const hammer = await import("../resources/hammer");
|
||||
this._mc = new hammer.Manager(document, {
|
||||
touchAction: "pan-y",
|
||||
});
|
||||
this._mc.add(
|
||||
new hammer.Swipe({
|
||||
direction:
|
||||
this.direction === "rtl"
|
||||
? hammer.DIRECTION_RIGHT
|
||||
: hammer.DIRECTION_LEFT,
|
||||
})
|
||||
);
|
||||
this._mc.on("swipeleft swiperight", () => {
|
||||
fireEvent(this, "hass-toggle-menu", { open: false });
|
||||
});
|
||||
}
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
static override styles = [
|
||||
styles,
|
||||
css`
|
||||
.mdc-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
border-color: var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||
inset-inline-start: 0 !important;
|
||||
inset-inline-end: initial !important;
|
||||
transition-property: transform, width;
|
||||
transition-duration:
|
||||
var(--mdc-drawer-transition-duration, 0.2s),
|
||||
var(--ha-animation-duration-normal);
|
||||
transition-timing-function:
|
||||
var(
|
||||
--mdc-drawer-transition-timing-function,
|
||||
cubic-bezier(0.4, 0, 0.2, 1)
|
||||
),
|
||||
ease;
|
||||
}
|
||||
.mdc-drawer.mdc-drawer--modal.mdc-drawer--open {
|
||||
z-index: 200;
|
||||
}
|
||||
.mdc-drawer-app-content {
|
||||
overflow: unset;
|
||||
flex: none;
|
||||
padding-left: var(--mdc-drawer-width);
|
||||
padding-inline-start: var(--mdc-drawer-width);
|
||||
padding-inline-end: initial;
|
||||
direction: var(--direction);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
transition:
|
||||
padding-left var(--ha-animation-duration-normal) ease,
|
||||
padding-inline-start var(--ha-animation-duration-normal) ease;
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
/* Use 1ms instead of "none" so the transitionend event still fires.
|
||||
The MDC drawer foundation relies on it to complete the close cycle. */
|
||||
.mdc-drawer,
|
||||
.mdc-drawer-app-content {
|
||||
transition: 1ms;
|
||||
}
|
||||
}
|
||||
`,
|
||||
];
|
||||
.layout {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sidebar-shell {
|
||||
position: fixed;
|
||||
width: var(--ha-sidebar-width);
|
||||
height: 100%;
|
||||
border-inline-end: 1px solid var(--divider-color, rgba(0, 0, 0, 0.12));
|
||||
box-sizing: border-box;
|
||||
transition: width var(--ha-animation-duration-normal) ease;
|
||||
}
|
||||
|
||||
.app-content {
|
||||
overflow: unset;
|
||||
min-width: 0;
|
||||
padding-inline-start: var(--ha-sidebar-width);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
transition:
|
||||
padding-inline-start var(--ha-animation-duration-normal) ease,
|
||||
width var(--ha-animation-duration-normal) ease;
|
||||
}
|
||||
|
||||
wa-drawer {
|
||||
--size: var(--ha-sidebar-width, 256px);
|
||||
--show-duration: var(--ha-animation-duration-normal);
|
||||
--hide-duration: var(--ha-animation-duration-normal);
|
||||
}
|
||||
|
||||
wa-drawer::part(body) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -188,7 +188,6 @@ export class HaObjectSelector extends LitElement {
|
||||
}
|
||||
|
||||
return html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.readonly=${this.disabled}
|
||||
.label=${this.label}
|
||||
.required=${this.required}
|
||||
|
||||
@@ -101,7 +101,6 @@ export class HaTemplateSelector extends LitElement {
|
||||
: nothing}
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
.hass=${this.hass}
|
||||
.value=${this.value}
|
||||
.readOnly=${this.disabled}
|
||||
.placeholder=${this.placeholder || "{{ ... }}"}
|
||||
|
||||
@@ -545,7 +545,6 @@ export class HaServiceControl extends LitElement {
|
||||
: ""}
|
||||
${shouldRenderServiceDataYaml
|
||||
? html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.service-control.action_data"
|
||||
)}
|
||||
|
||||
@@ -3,14 +3,16 @@ import { DEFAULT_SCHEMA, dump, load } from "js-yaml";
|
||||
import type { CSSResultGroup, PropertyValues } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { consume } from "@lit/context";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import { copyToClipboard } from "../common/util/copy-clipboard";
|
||||
import { haStyle } from "../resources/styles";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { showToast } from "../util/toast";
|
||||
import "./ha-button";
|
||||
import "./ha-code-editor";
|
||||
import type { HaCodeEditor } from "./ha-code-editor";
|
||||
import { internationalizationContext } from "../data/context";
|
||||
|
||||
const isEmpty = (obj: Record<string, unknown>): boolean => {
|
||||
if (typeof obj !== "object" || obj === null) {
|
||||
@@ -26,8 +28,6 @@ const isEmpty = (obj: Record<string, unknown>): boolean => {
|
||||
|
||||
@customElement("ha-yaml-editor")
|
||||
export class HaYamlEditor extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property() public value?: any;
|
||||
|
||||
@property({ attribute: false }) public yamlSchema: Schema = DEFAULT_SCHEMA;
|
||||
@@ -59,6 +59,10 @@ export class HaYamlEditor extends LitElement {
|
||||
|
||||
@state() private _yaml = "";
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n?: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@query("ha-code-editor") _codeEditor?: HaCodeEditor;
|
||||
|
||||
public setValue(value): void {
|
||||
@@ -112,7 +116,6 @@ export class HaYamlEditor extends LitElement {
|
||||
? html`<p>${this.label}${this.required ? " *" : ""}</p>`
|
||||
: nothing}
|
||||
<ha-code-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._yaml}
|
||||
.readOnly=${this.readOnly}
|
||||
.disableFullscreen=${this.disableFullscreen}
|
||||
@@ -132,7 +135,7 @@ export class HaYamlEditor extends LitElement {
|
||||
${this.copyClipboard
|
||||
? html`
|
||||
<ha-button appearance="plain" @click=${this._copyYaml}>
|
||||
${this.hass.localize(
|
||||
${this._i18n!.localize(
|
||||
"ui.components.yaml-editor.copy_to_clipboard"
|
||||
)}
|
||||
</ha-button>
|
||||
@@ -163,7 +166,7 @@ export class HaYamlEditor extends LitElement {
|
||||
// Invalid YAML
|
||||
isValid = false;
|
||||
yamlError = err;
|
||||
errorMsg = `${this.hass.localize("ui.components.yaml-editor.error", { reason: err.reason })}${err.mark ? ` (${this.hass.localize("ui.components.yaml-editor.error_location", { line: err.mark.line + 1, column: err.mark.column + 1 })})` : ""}`;
|
||||
errorMsg = `${this._i18n!.localize("ui.components.yaml-editor.error", { reason: err.reason })}${err.mark ? ` (${this._i18n!.localize("ui.components.yaml-editor.error_location", { line: err.mark.line + 1, column: err.mark.column + 1 })})` : ""}`;
|
||||
}
|
||||
} else {
|
||||
parsed = {};
|
||||
@@ -201,7 +204,7 @@ export class HaYamlEditor extends LitElement {
|
||||
if (this.yaml) {
|
||||
await copyToClipboard(this.yaml);
|
||||
showToast(this, {
|
||||
message: this.hass.localize("ui.common.copied_clipboard"),
|
||||
message: this._i18n!.localize("ui.common.copied_clipboard"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,10 +130,6 @@ export class HaRowItem extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
font-size: var(--ha-font-size-m);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
--ha-row-item-padding-block: var(--ha-space-3);
|
||||
--ha-row-item-padding-inline: var(--ha-space-4);
|
||||
--ha-row-item-gap: var(--ha-space-4);
|
||||
--ha-row-item-min-height: 48px;
|
||||
}
|
||||
:host([disabled]) {
|
||||
color: var(--disabled-text-color);
|
||||
@@ -144,10 +140,10 @@ export class HaRowItem extends LitElement {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--ha-row-item-gap);
|
||||
padding-block: var(--ha-row-item-padding-block);
|
||||
padding-inline: var(--ha-row-item-padding-inline);
|
||||
min-height: var(--ha-row-item-min-height);
|
||||
gap: var(--ha-row-item-gap, var(--ha-space-4));
|
||||
padding-block: var(--ha-row-item-padding-block, var(--ha-space-3));
|
||||
padding-inline: var(--ha-row-item-padding-inline, var(--ha-space-4));
|
||||
min-height: var(--ha-row-item-min-height, 48px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.content {
|
||||
|
||||
@@ -292,14 +292,12 @@ export class HaListBase extends LitElement {
|
||||
static styles: CSSResultGroup = css`
|
||||
:host {
|
||||
display: block;
|
||||
--ha-list-gap: 0;
|
||||
--ha-list-padding: 0;
|
||||
}
|
||||
.base {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ha-list-gap);
|
||||
padding: var(--ha-list-padding);
|
||||
gap: var(--ha-list-gap, 0);
|
||||
padding: var(--ha-list-padding, 0);
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
@@ -121,15 +121,15 @@ export class HaListSelectable extends HaListBase {
|
||||
|
||||
public updateListItems() {
|
||||
super.updateListItems();
|
||||
this._syncItemSelectedState();
|
||||
this._syncItemSelectedState(true);
|
||||
}
|
||||
|
||||
private _sortedSelectedIndices(): number[] {
|
||||
return [...this._selectedIndices!].sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
private _syncItemSelectedState() {
|
||||
if (!this._selectedIndices) {
|
||||
private _syncItemSelectedState(reset = false): void {
|
||||
if (!this._selectedIndices || reset) {
|
||||
this._selectedIndices = new Set<number>();
|
||||
this.items.forEach((item, i) => {
|
||||
const opt = item as HaListItemOption;
|
||||
|
||||
@@ -22,8 +22,6 @@ import "../../ha-adaptive-dialog";
|
||||
import "../../ha-dialog-header";
|
||||
import "../../ha-icon-button";
|
||||
import "../../ha-icon-next";
|
||||
import "../../ha-md-list";
|
||||
import "../../ha-md-list-item";
|
||||
import "../../ha-svg-icon";
|
||||
import "../../list/ha-list-base";
|
||||
import "../ha-target-picker-item-row";
|
||||
|
||||
@@ -28,8 +28,6 @@ import "../ha-domain-icon";
|
||||
import { floorDefaultIconPath } from "../ha-floor-icon";
|
||||
import "../ha-icon";
|
||||
import "../ha-icon-button";
|
||||
import "../ha-md-list";
|
||||
import "../ha-md-list-item";
|
||||
import "../ha-state-icon";
|
||||
import "../ha-tooltip";
|
||||
|
||||
|
||||
@@ -5,19 +5,15 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-icon-button";
|
||||
import type { TraceExtended } from "../../data/trace";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-trace-blueprint-config")
|
||||
export class HaTraceBlueprintConfig extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public trace!: TraceExtended;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-code-editor
|
||||
.value=${dump(this.trace.blueprint_inputs || "").trimRight()}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
|
||||
@@ -5,19 +5,15 @@ import { customElement, property } from "lit/decorators";
|
||||
import "../ha-code-editor";
|
||||
import "../ha-icon-button";
|
||||
import type { TraceExtended } from "../../data/trace";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-trace-config")
|
||||
export class HaTraceConfig extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public trace!: TraceExtended;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-code-editor
|
||||
.value=${dump(this.trace.config).trimRight()}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
|
||||
@@ -271,7 +271,6 @@ export class HaTracePathDetails extends LitElement {
|
||||
return config
|
||||
? html`<ha-code-editor
|
||||
.value=${dump(config).trimEnd()}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
dir="ltr"
|
||||
></ha-code-editor>`
|
||||
@@ -311,7 +310,6 @@ export class HaTracePathDetails extends LitElement {
|
||||
: html`<ha-code-editor
|
||||
read-only
|
||||
dir="ltr"
|
||||
.hass=${this.hass}
|
||||
.value=${dump(trace.changed_variables).trimEnd()}
|
||||
></ha-code-editor>`}
|
||||
`
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
HomeAssistantApi,
|
||||
HomeAssistantConfig,
|
||||
HomeAssistantConnection,
|
||||
HomeAssistantFormatters,
|
||||
HomeAssistantInternationalization,
|
||||
HomeAssistantRegistries,
|
||||
HomeAssistantUI,
|
||||
@@ -63,6 +64,14 @@ export const uiContext = createContext<HomeAssistantUI>("hassUi");
|
||||
*/
|
||||
export const configContext = createContext<HomeAssistantConfig>("hassConfig");
|
||||
|
||||
/**
|
||||
* Entity formatting functions: `formatEntityState`, `formatEntityStateToParts`,
|
||||
* `formatEntityAttributeValue`, `formatEntityAttributeValueToParts`,
|
||||
* `formatEntityAttributeName`, and `formatEntityName`.
|
||||
*/
|
||||
export const formattersContext =
|
||||
createContext<HomeAssistantFormatters>("hassFormatters");
|
||||
|
||||
/**
|
||||
* Map of all entities in the entity registry, keyed by entity ID.
|
||||
*/
|
||||
|
||||
@@ -3,6 +3,7 @@ import type {
|
||||
HomeAssistantApi,
|
||||
HomeAssistantConfig,
|
||||
HomeAssistantConnection,
|
||||
HomeAssistantFormatters,
|
||||
HomeAssistantInternationalization,
|
||||
HomeAssistantRegistries,
|
||||
HomeAssistantUI,
|
||||
@@ -156,6 +157,32 @@ const updateConfig = (
|
||||
return value;
|
||||
};
|
||||
|
||||
const updateFormatters = (
|
||||
hass: HomeAssistant,
|
||||
value?: HomeAssistantFormatters
|
||||
): HomeAssistantFormatters => {
|
||||
if (
|
||||
!value ||
|
||||
value.formatEntityState !== hass.formatEntityState ||
|
||||
value.formatEntityStateToParts !== hass.formatEntityStateToParts ||
|
||||
value.formatEntityAttributeValue !== hass.formatEntityAttributeValue ||
|
||||
value.formatEntityAttributeValueToParts !==
|
||||
hass.formatEntityAttributeValueToParts ||
|
||||
value.formatEntityAttributeName !== hass.formatEntityAttributeName ||
|
||||
value.formatEntityName !== hass.formatEntityName
|
||||
) {
|
||||
return {
|
||||
formatEntityState: hass.formatEntityState,
|
||||
formatEntityStateToParts: hass.formatEntityStateToParts,
|
||||
formatEntityAttributeValue: hass.formatEntityAttributeValue,
|
||||
formatEntityAttributeValueToParts: hass.formatEntityAttributeValueToParts,
|
||||
formatEntityAttributeName: hass.formatEntityAttributeName,
|
||||
formatEntityName: hass.formatEntityName,
|
||||
};
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
export const updateHassGroups = {
|
||||
registries: updateRegistries,
|
||||
internationalization: updateInternationalization,
|
||||
@@ -163,4 +190,5 @@ export const updateHassGroups = {
|
||||
connection: updateConnection,
|
||||
ui: updateUi,
|
||||
config: updateConfig,
|
||||
formatters: updateFormatters,
|
||||
};
|
||||
|
||||
@@ -182,6 +182,8 @@ export interface GasSourceTypeEnergyPreference {
|
||||
entity_energy_price: string | null;
|
||||
number_energy_price: number | null;
|
||||
unit_of_measurement?: string | null;
|
||||
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface WaterSourceTypeEnergyPreference {
|
||||
@@ -200,6 +202,8 @@ export interface WaterSourceTypeEnergyPreference {
|
||||
entity_energy_price: string | null;
|
||||
number_energy_price: number | null;
|
||||
unit_of_measurement?: string | null;
|
||||
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export type EnergySource =
|
||||
|
||||
@@ -154,7 +154,7 @@ export const getRecorderInfo = (conn: Connection) =>
|
||||
});
|
||||
|
||||
export const getStatisticIds = (
|
||||
hass: HomeAssistant,
|
||||
hass: Pick<HomeAssistant, "callWS">,
|
||||
statistic_type?: "mean" | "sum"
|
||||
) =>
|
||||
hass.callWS<StatisticsMetaData[]>({
|
||||
@@ -227,7 +227,7 @@ export const fetchStatistic = (
|
||||
rolling_window: period.rolling_window,
|
||||
});
|
||||
|
||||
export const validateStatistics = (hass: HomeAssistant) =>
|
||||
export const validateStatistics = (hass: Pick<HomeAssistant, "callWS">) =>
|
||||
hass.callWS<StatisticsValidationResults>({
|
||||
type: "recorder/validate_statistics",
|
||||
});
|
||||
@@ -245,7 +245,10 @@ export const updateStatisticsMetadata = (
|
||||
unit_class,
|
||||
});
|
||||
|
||||
export const clearStatistics = (hass: HomeAssistant, statistic_ids: string[]) =>
|
||||
export const clearStatistics = (
|
||||
hass: Pick<HomeAssistant, "callWS">,
|
||||
statistic_ids: string[]
|
||||
) =>
|
||||
hass.callWS<undefined>({
|
||||
type: "recorder/clear_statistics",
|
||||
statistic_ids,
|
||||
@@ -369,5 +372,5 @@ export const getDisplayUnit = (
|
||||
export const isExternalStatistic = (statisticsId: string): boolean =>
|
||||
statisticsId.includes(":");
|
||||
|
||||
export const updateStatisticsIssues = (hass: HomeAssistant) =>
|
||||
export const updateStatisticsIssues = (hass: Pick<HomeAssistant, "callWS">) =>
|
||||
hass.callWS<undefined>({ type: "recorder/update_statistics_issues" });
|
||||
|
||||
@@ -2,11 +2,11 @@ import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-bottom-sheet";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-md-list";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/ha-dialog";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-svg-icon";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-base";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import type { HassDialog } from "../make-dialog-manager";
|
||||
import type { ListItemsDialogParams } from "./show-list-items-dialog";
|
||||
@@ -51,41 +51,30 @@ export class ListItemsDialog
|
||||
|
||||
const content = html`
|
||||
<div class="container">
|
||||
<ha-md-list>
|
||||
<ha-list-base>
|
||||
${this._params.items.map(
|
||||
(item) => html`
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
@click=${this._itemClicked}
|
||||
.item=${item}
|
||||
>
|
||||
<ha-list-item-button @click=${this._itemClicked} .item=${item}>
|
||||
${item.iconPath
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.path=${item.iconPath}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: item.icon
|
||||
? html`
|
||||
<ha-icon
|
||||
icon=${item.icon}
|
||||
slot="start"
|
||||
class="item-icon"
|
||||
></ha-icon>
|
||||
`
|
||||
? html` <ha-icon icon=${item.icon} slot="start"></ha-icon> `
|
||||
: nothing}
|
||||
<span class="headline">${item.label}</span>
|
||||
<span slot="headline">${item.label}</span>
|
||||
${item.description
|
||||
? html`
|
||||
<span class="supporting-text">${item.description}</span>
|
||||
<span slot="supporting-text">${item.description}</span>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-list-base>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -113,12 +102,16 @@ export class ListItemsDialog
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
ha-dialog {
|
||||
ha-dialog,
|
||||
ha-bottom-sheet {
|
||||
/* Place above other dialogs */
|
||||
--dialog-z-index: 104;
|
||||
--dialog-content-padding: 0;
|
||||
--md-list-item-leading-space: 24px;
|
||||
--md-list-item-trailing-space: 24px;
|
||||
--ha-row-item-padding-inline: var(--ha-space-6);
|
||||
}
|
||||
|
||||
ha-bottom-sheet {
|
||||
--ha-bottom-sheet-content-padding: var(--ha-space-4) 0 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,9 @@ import "../../../components/ha-alert";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-faded";
|
||||
import "../../../components/ha-markdown";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import "../../../components/progress/ha-progress-bar";
|
||||
import type { BackupConfig } from "../../../data/backup";
|
||||
import { fetchBackupConfig } from "../../../data/backup";
|
||||
@@ -274,24 +273,22 @@ class MoreInfoUpdate extends LitElement {
|
||||
<div class="footer">
|
||||
${createBackupTexts
|
||||
? html`
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">${createBackupTexts.title}</span>
|
||||
${createBackupTexts.description
|
||||
? html`
|
||||
<span slot="supporting-text">
|
||||
${createBackupTexts.description}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.checked=${this._createBackup}
|
||||
@change=${this._createBackupChanged}
|
||||
.disabled=${updateIsInstalling(this.stateObj)}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<ha-row-item>
|
||||
<span slot="headline">${createBackupTexts.title}</span>
|
||||
${createBackupTexts.description
|
||||
? html`
|
||||
<span slot="supporting-text">
|
||||
${createBackupTexts.description}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<ha-switch
|
||||
slot="end"
|
||||
.checked=${this._createBackup}
|
||||
@change=${this._createBackupChanged}
|
||||
.disabled=${updateIsInstalling(this.stateObj)}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing}
|
||||
<div class="actions">
|
||||
@@ -484,20 +481,9 @@ class MoreInfoUpdate extends LitElement {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
ha-row-item {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: calc(var(--ha-space-4) * -1);
|
||||
margin-top: calc(var(--ha-space-1) * -1);
|
||||
--md-sys-color-surface: var(
|
||||
--ha-dialog-surface-background,
|
||||
var(--mdc-theme-surface, #fff)
|
||||
);
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: var(--ha-space-6);
|
||||
--md-list-item-trailing-space: var(--ha-space-6);
|
||||
--ha-row-item-padding-inline: var(--ha-space-6);
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
||||
@@ -2,8 +2,9 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-icon";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/ha-spinner";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-base";
|
||||
import type {
|
||||
ExternalEntityAddToAction,
|
||||
ExternalEntityAddToActions,
|
||||
@@ -90,24 +91,23 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="actions-list">
|
||||
${this._externalActions.actions.map(
|
||||
<ha-list-base>
|
||||
${this._externalActions?.actions.map(
|
||||
(action) => html`
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
<ha-list-item-button
|
||||
.disabled=${!action.enabled}
|
||||
.action=${action}
|
||||
@click=${this._actionSelected}
|
||||
>
|
||||
<ha-icon slot="start" .icon=${action.mdi_icon}></ha-icon>
|
||||
<span>${action.name}</span>
|
||||
<span slot="headline">${action.name}</span>
|
||||
${action.details
|
||||
? html`<span slot="supporting-text">${action.details}</span>`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
</ha-list-base>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -125,11 +125,6 @@ export class HaMoreInfoAddTo extends LitElement {
|
||||
padding: var(--ha-space-8);
|
||||
}
|
||||
|
||||
.actions-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
ha-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -57,7 +57,6 @@ class HaMoreInfoDetails extends LitElement {
|
||||
<div class="content">
|
||||
${this.yamlMode
|
||||
? html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.value=${yamlData}
|
||||
read-only
|
||||
auto-update
|
||||
|
||||
@@ -23,12 +23,16 @@ export class HuiNotificationDrawer extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@state() private _notifications: PersistentNotification[] = [];
|
||||
|
||||
@state() private _open = false;
|
||||
@state() public _open = false;
|
||||
|
||||
@state() private _drawerOpen = false;
|
||||
|
||||
@query("ha-drawer") private _drawer?: HaDrawer;
|
||||
|
||||
private _unsubNotifications?: UnsubscribeFunc;
|
||||
|
||||
private _openAnimationFrame?: number;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
window.addEventListener("location-changed", this.closeDialog);
|
||||
@@ -37,6 +41,10 @@ export class HuiNotificationDrawer extends KeyboardShortcutMixin(LitElement) {
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
window.removeEventListener("location-changed", this.closeDialog);
|
||||
if (this._openAnimationFrame !== undefined) {
|
||||
cancelAnimationFrame(this._openAnimationFrame);
|
||||
this._openAnimationFrame = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
showDialog({ narrow }) {
|
||||
@@ -51,22 +59,21 @@ export class HuiNotificationDrawer extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
);
|
||||
this.style.setProperty(
|
||||
"--mdc-drawer-width",
|
||||
"--ha-sidebar-width",
|
||||
`min(100vw, calc(${narrow ? window.innerWidth + "px" : "500px"} + var(--safe-area-inset-left, 0px)))`
|
||||
);
|
||||
this._open = true;
|
||||
}
|
||||
|
||||
closeDialog = () => {
|
||||
if (this._drawer) {
|
||||
if (this._drawerOpen && this._drawer) {
|
||||
this._drawer.open = false;
|
||||
this._drawerOpen = false;
|
||||
return;
|
||||
}
|
||||
if (this._unsubNotifications) {
|
||||
this._unsubNotifications();
|
||||
this._unsubNotifications = undefined;
|
||||
}
|
||||
this._notifications = [];
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
this._drawerOpen = false;
|
||||
this._open = false;
|
||||
this._finalizeClose();
|
||||
};
|
||||
|
||||
public willUpdate(changedProps: PropertyValues<this>): void {
|
||||
@@ -77,6 +84,17 @@ export class HuiNotificationDrawer extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
}
|
||||
|
||||
protected updated(changedProps: PropertyValues<this>) {
|
||||
super.updated(changedProps);
|
||||
|
||||
if (changedProps.has("_open") && this._open && !this._drawerOpen) {
|
||||
this._openAnimationFrame = requestAnimationFrame(() => {
|
||||
this._openAnimationFrame = undefined;
|
||||
this._drawerOpen = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._open) {
|
||||
return nothing;
|
||||
@@ -104,8 +122,8 @@ export class HuiNotificationDrawer extends KeyboardShortcutMixin(LitElement) {
|
||||
return html`
|
||||
<ha-drawer
|
||||
type="modal"
|
||||
open
|
||||
@MDCDrawer:closed=${this._dialogClosed}
|
||||
.open=${this._drawerOpen}
|
||||
@hass-drawer-closed=${this._dialogClosed}
|
||||
.direction=${computeRTLDirection(this.hass)}
|
||||
>
|
||||
<ha-header-bar>
|
||||
@@ -157,7 +175,9 @@ export class HuiNotificationDrawer extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
private _dialogClosed(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
this._drawerOpen = false;
|
||||
this._open = false;
|
||||
this._finalizeClose();
|
||||
}
|
||||
|
||||
private _dismissAll() {
|
||||
@@ -165,6 +185,19 @@ export class HuiNotificationDrawer extends KeyboardShortcutMixin(LitElement) {
|
||||
this.closeDialog();
|
||||
}
|
||||
|
||||
private _finalizeClose() {
|
||||
if (this._openAnimationFrame !== undefined) {
|
||||
cancelAnimationFrame(this._openAnimationFrame);
|
||||
this._openAnimationFrame = undefined;
|
||||
}
|
||||
if (this._unsubNotifications) {
|
||||
this._unsubNotifications();
|
||||
this._unsubNotifications = undefined;
|
||||
}
|
||||
this._notifications = [];
|
||||
fireEvent(this, "dialog-closed", { dialog: this.localName });
|
||||
}
|
||||
|
||||
protected supportedSingleKeyShortcuts(): SupportedShortcuts {
|
||||
return {
|
||||
Escape: () => this.closeDialog(),
|
||||
|
||||
@@ -10,14 +10,15 @@ import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { isComponentLoaded } from "../../common/config/is_component_loaded";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/animation/ha-fade-in";
|
||||
import "../../components/ha-adaptive-dialog";
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-expansion-panel";
|
||||
import "../../components/animation/ha-fade-in";
|
||||
import "../../components/ha-icon-next";
|
||||
import "../../components/ha-md-list";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/ha-spinner";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import type { HaListItemButton } from "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-base";
|
||||
import "../../components/progress/ha-progress-bar";
|
||||
import { fetchBackupInfo } from "../../data/backup";
|
||||
import type { BackupManagerState } from "../../data/backup_manager";
|
||||
@@ -130,9 +131,8 @@ class DialogRestart extends LitElement {
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<ha-md-list dialogInitialFocus>
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
<ha-list-base dialogInitialFocus>
|
||||
<ha-list-item-button
|
||||
@click=${this._reload}
|
||||
.disabled=${this._loadingBackupInfo}
|
||||
>
|
||||
@@ -148,9 +148,8 @@ class DialogRestart extends LitElement {
|
||||
<ha-svg-icon .path=${mdiAutoFix}></ha-svg-icon>
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
.action=${"restart"}
|
||||
@click=${this._handleAction}
|
||||
.disabled=${this._loadingBackupInfo}
|
||||
@@ -167,18 +166,17 @@ class DialogRestart extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
<ha-expansion-panel
|
||||
.header=${this.hass.localize(
|
||||
"ui.dialogs.restart.advanced_options"
|
||||
)}
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-list-base>
|
||||
${showRebootShutdown
|
||||
? html`
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
<ha-list-item-button
|
||||
.action=${"reboot"}
|
||||
@click=${this._handleAction}
|
||||
.disabled=${this._loadingBackupInfo}
|
||||
@@ -197,9 +195,8 @@ class DialogRestart extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button
|
||||
.action=${"shutdown"}
|
||||
@click=${this._handleAction}
|
||||
.disabled=${this._loadingBackupInfo}
|
||||
@@ -218,11 +215,10 @@ class DialogRestart extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
: nothing}
|
||||
<ha-md-list-item
|
||||
type="button"
|
||||
<ha-list-item-button
|
||||
.action=${"restart-safe-mode"}
|
||||
@click=${this._handleAction}
|
||||
.disabled=${this._loadingBackupInfo}
|
||||
@@ -244,8 +240,8 @@ class DialogRestart extends LitElement {
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
</ha-expansion-panel>
|
||||
`}
|
||||
</div>
|
||||
@@ -324,16 +320,13 @@ class DialogRestart extends LitElement {
|
||||
}
|
||||
};
|
||||
|
||||
private async _handleAction(ev) {
|
||||
private async _handleAction(ev: Event) {
|
||||
if (this._loadingBackupInfo) {
|
||||
return;
|
||||
}
|
||||
this._loadingBackupInfo = true;
|
||||
const action = ev.currentTarget.action as
|
||||
| "restart"
|
||||
| "reboot"
|
||||
| "shutdown"
|
||||
| "restart-safe-mode";
|
||||
const action = (ev.currentTarget as HaListItemButton & { action: string })
|
||||
.action as "restart" | "reboot" | "shutdown" | "restart-safe-mode";
|
||||
|
||||
const backupState = await this._loadBackupState();
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../common/dom/fire_event";
|
||||
import "../../components/ha-md-list";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/item/ha-list-item-button";
|
||||
import type { HaListItemButton } from "../../components/item/ha-list-item-button";
|
||||
import "../../components/list/ha-list-base";
|
||||
import type { AssistSatelliteConfiguration } from "../../data/assist_satellite";
|
||||
import { setWakeWords } from "../../data/assist_satellite";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
@@ -35,28 +36,28 @@ export class HaVoiceAssistantSetupStepChangeWakeWord extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<ha-md-list>
|
||||
<ha-list-base>
|
||||
${this.assistConfiguration!.available_wake_words.map(
|
||||
(wakeWord) =>
|
||||
html`<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
html`<ha-list-item-button
|
||||
@click=${this._wakeWordPicked}
|
||||
.value=${wakeWord.id}
|
||||
>
|
||||
${wakeWord.wake_word}
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>`
|
||||
</ha-list-item-button>`
|
||||
)}
|
||||
</ha-md-list>`;
|
||||
</ha-list-base>`;
|
||||
}
|
||||
|
||||
private async _wakeWordPicked(ev) {
|
||||
private async _wakeWordPicked(ev: Event) {
|
||||
if (!this.assistEntityId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wakeWordId = ev.currentTarget.value;
|
||||
const wakeWordId = (
|
||||
ev.currentTarget as HaListItemButton & { value: string }
|
||||
).value;
|
||||
|
||||
await setWakeWords(this.hass, this.assistEntityId, [wakeWordId]);
|
||||
this._nextStep();
|
||||
@@ -75,7 +76,7 @@ export class HaVoiceAssistantSetupStepChangeWakeWord extends LitElement {
|
||||
.padding {
|
||||
padding: 24px;
|
||||
}
|
||||
ha-md-list {
|
||||
ha-list-base {
|
||||
width: 100%;
|
||||
text-align: initial;
|
||||
margin-bottom: 24px;
|
||||
|
||||
@@ -363,9 +363,6 @@ export class HaVoiceAssistantSetupStepSuccess extends LitElement {
|
||||
static styles = [
|
||||
AssistantSetupStyles,
|
||||
css`
|
||||
ha-md-list-item {
|
||||
text-align: initial;
|
||||
}
|
||||
ha-tts-voice-picker {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class HomeAssistantMain extends LitElement {
|
||||
.type=${sidebarNarrow ? "modal" : ""}
|
||||
.open=${sidebarNarrow ? this._drawerOpen : false}
|
||||
.direction=${computeRTLDirection(this.hass)}
|
||||
@MDCDrawer:closed=${this._drawerClosed}
|
||||
@hass-drawer-closed=${this._drawerClosed}
|
||||
>
|
||||
<ha-sidebar
|
||||
.hass=${this.hass}
|
||||
@@ -152,16 +152,16 @@ export class HomeAssistantMain extends LitElement {
|
||||
color: var(--primary-text-color);
|
||||
/* remove the grey tap highlights in iOS on the fullscreen touch targets */
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
--mdc-drawer-width: calc(56px + var(--safe-area-inset-left, 0px));
|
||||
--mdc-top-app-bar-width: calc(100% - var(--mdc-drawer-width));
|
||||
--ha-sidebar-width: calc(56px + var(--safe-area-inset-left, 0px));
|
||||
--mdc-top-app-bar-width: calc(100% - var(--ha-sidebar-width));
|
||||
--safe-area-content-inset-left: 0px;
|
||||
--safe-area-content-inset-right: var(--safe-area-inset-right);
|
||||
}
|
||||
:host([expanded]) {
|
||||
--mdc-drawer-width: calc(256px + var(--safe-area-inset-left, 0px));
|
||||
--ha-sidebar-width: calc(256px + var(--safe-area-inset-left, 0px));
|
||||
}
|
||||
:host([modal]) {
|
||||
--mdc-drawer-width: unset;
|
||||
--ha-sidebar-width: unset;
|
||||
--mdc-top-app-bar-width: unset;
|
||||
--safe-area-content-inset-left: var(--safe-area-inset-left);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../common/dom/fire_event";
|
||||
import type { LocalizeFunc } from "../common/translations/localize";
|
||||
import "../components/ha-button";
|
||||
import "../components/ha-icon-button-next";
|
||||
import "../components/ha-md-list";
|
||||
import "../components/ha-md-list-item";
|
||||
import "../components/ha-icon-next";
|
||||
import "../components/item/ha-list-item-button";
|
||||
import "../components/list/ha-list-base";
|
||||
import type { HomeAssistant } from "../types";
|
||||
import { onBoardingStyles } from "./styles";
|
||||
|
||||
@@ -37,8 +37,8 @@ class OnboardingWelcome extends LitElement {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ha-md-list>
|
||||
<ha-md-list-item type="button" @click=${this._restoreBackupUpload}>
|
||||
<ha-list-base>
|
||||
<ha-list-item-button @click=${this._restoreBackupUpload}>
|
||||
<div slot="headline">
|
||||
${this.localize("ui.panel.page-onboarding.restore.upload_backup")}
|
||||
</div>
|
||||
@@ -47,18 +47,18 @@ class OnboardingWelcome extends LitElement {
|
||||
"ui.panel.page-onboarding.restore.options.upload_description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-button-next slot="end"></ha-icon-button-next>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item type="button" @click=${this._restoreBackupCloud}>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
<ha-list-item-button @click=${this._restoreBackupCloud}>
|
||||
<div slot="headline">Home Assistant Cloud</div>
|
||||
<div slot="supporting-text">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.ha-cloud.description"
|
||||
)}
|
||||
</div>
|
||||
<ha-icon-button-next slot="end"></ha-icon-button-next>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-list-item-button>
|
||||
</ha-list-base>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -123,11 +123,10 @@ class OnboardingWelcome extends LitElement {
|
||||
padding: 0 var(--ha-space-4);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
ha-list-base {
|
||||
width: 100%;
|
||||
padding-bottom: 0;
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -8,9 +8,8 @@ import type { HaProgressButton } from "../../components/buttons/ha-progress-butt
|
||||
import "../../components/ha-alert";
|
||||
import "../../components/ha-button";
|
||||
import "../../components/ha-icon-button-arrow-prev";
|
||||
import "../../components/ha-md-list";
|
||||
import "../../components/ha-md-list-item";
|
||||
import "../../components/input/ha-input";
|
||||
import "../../components/item/ha-row-item";
|
||||
import {
|
||||
getPreferredAgentForDownload,
|
||||
type BackupContentExtended,
|
||||
@@ -92,33 +91,30 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.summary.created"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">${formattedDate}</span>
|
||||
</ha-md-list-item>
|
||||
${onlyHomeAssistantBackup
|
||||
? html`<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.summary.content"
|
||||
)}
|
||||
</span>
|
||||
<ha-backup-formfield-label
|
||||
slot="supporting-text"
|
||||
.version=${this.backup.homeassistant_version}
|
||||
.label=${this.localize(
|
||||
`ui.panel.page-onboarding.restore.data_picker.${this.backup.database_included ? "settings_and_history" : "settings"}`
|
||||
)}
|
||||
></ha-backup-formfield-label>
|
||||
</ha-md-list-item>`
|
||||
: nothing}
|
||||
</ha-md-list>
|
||||
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.summary.created"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">${formattedDate}</span>
|
||||
</ha-row-item>
|
||||
${onlyHomeAssistantBackup
|
||||
? html`<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.localize(
|
||||
"ui.panel.page-onboarding.restore.details.summary.content"
|
||||
)}
|
||||
</span>
|
||||
<ha-backup-formfield-label
|
||||
slot="supporting-text"
|
||||
.version=${this.backup.homeassistant_version}
|
||||
.label=${this.localize(
|
||||
`ui.panel.page-onboarding.restore.data_picker.${this.backup.database_included ? "settings_and_history" : "settings"}`
|
||||
)}
|
||||
></ha-backup-formfield-label>
|
||||
</ha-row-item>`
|
||||
: nothing}
|
||||
${!onlyHomeAssistantBackup
|
||||
? html`<h2>
|
||||
${this.localize("ui.panel.page-onboarding.restore.select_type")}
|
||||
@@ -312,26 +308,8 @@ class OnboardingRestoreBackupRestore extends LitElement {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
}
|
||||
ha-md-list {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-list-item-two-line-container-height: 64px;
|
||||
--md-list-item-supporting-text-size: 1rem;
|
||||
--md-list-item-label-text-size: 0.875rem;
|
||||
|
||||
--md-list-item-label-text-color: var(--secondary-text-color);
|
||||
--md-list-item-supporting-text-color: var(--primary-text-color);
|
||||
}
|
||||
ha-md-list-item [slot="supporting-text"] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: var(--ha-space-2);
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
h2 {
|
||||
font-size: var(--ha-font-size-xl);
|
||||
|
||||
+17
-29
@@ -13,13 +13,12 @@ import "../../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../../components/ha-alert";
|
||||
import "../../../../../components/ha-button";
|
||||
import "../../../../../components/ha-card";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-faded";
|
||||
import "../../../../../components/ha-markdown";
|
||||
import "../../../../../components/ha-md-list";
|
||||
import "../../../../../components/ha-md-list-item";
|
||||
import "../../../../../components/ha-spinner";
|
||||
import "../../../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../../../components/ha-switch";
|
||||
import "../../../../../components/item/ha-row-item";
|
||||
import type { HassioAddonDetails } from "../../../../../data/hassio/addon";
|
||||
import {
|
||||
fetchHassioAddonChangelog,
|
||||
@@ -108,25 +107,20 @@ class SupervisorAppUpdateAvailableCard extends LitElement {
|
||||
${createBackupTexts
|
||||
? html`
|
||||
<hr />
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${createBackupTexts.title}
|
||||
</span>
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${createBackupTexts.title}
|
||||
</span>
|
||||
|
||||
${createBackupTexts.description
|
||||
? html`
|
||||
<span slot="supporting-text">
|
||||
${createBackupTexts.description}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<ha-switch
|
||||
slot="end"
|
||||
id="create-backup"
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
${createBackupTexts.description
|
||||
? html`
|
||||
<span slot="supporting-text">
|
||||
${createBackupTexts.description}
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<ha-switch slot="end" id="create-backup"></ha-switch>
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing}
|
||||
`
|
||||
@@ -273,16 +267,10 @@ class SupervisorAppUpdateAvailableCard extends LitElement {
|
||||
margin: var(--ha-space-4) 0 0 0;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
margin-bottom: calc(-1 * var(--ha-space-4));
|
||||
}
|
||||
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "@home-assistant/webawesome/dist/components/tag/tag";
|
||||
import {
|
||||
mdiCheckCircle,
|
||||
mdiChip,
|
||||
mdiCircleOffOutline,
|
||||
mdiCursorDefaultClickOutline,
|
||||
mdiDocker,
|
||||
mdiExclamationThick,
|
||||
@@ -17,11 +17,10 @@ import {
|
||||
mdiNumeric6,
|
||||
mdiNumeric7,
|
||||
mdiNumeric8,
|
||||
mdiPlayCircle,
|
||||
mdiPound,
|
||||
mdiShield,
|
||||
} from "@mdi/js";
|
||||
import type { CSSResultGroup, TemplateResult, PropertyValues } from "lit";
|
||||
import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { classMap } from "lit/directives/class-map";
|
||||
@@ -31,6 +30,7 @@ import { atLeastVersion } from "../../../../../common/config/version";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import { navigate } from "../../../../../common/navigate";
|
||||
import { capitalizeFirstLetter } from "../../../../../common/string/capitalize-first-letter";
|
||||
import type { LocalizeKeys } from "../../../../../common/translations/localize";
|
||||
import "../../../../../components/buttons/ha-progress-button";
|
||||
import "../../../../../components/chips/ha-assist-chip";
|
||||
import "../../../../../components/chips/ha-chip-set";
|
||||
@@ -79,9 +79,9 @@ import { bytesToString } from "../../../../../util/bytes-to-string";
|
||||
import { getAppDisplayName } from "../../common/app";
|
||||
import "../../components/supervisor-apps-card-content";
|
||||
import "../components/supervisor-app-metric";
|
||||
import "../components/supervisor-app-update-available-card";
|
||||
import { extractChangelog } from "../util/supervisor-app";
|
||||
import "./supervisor-app-system-managed";
|
||||
import "../components/supervisor-app-update-available-card";
|
||||
|
||||
const STAGE_ICON = {
|
||||
stable: mdiCheckCircle,
|
||||
@@ -203,28 +203,10 @@ class SupervisorAppInfo extends LitElement {
|
||||
: nothing}
|
||||
<div class="addon-version light-color">
|
||||
${this.addon.version
|
||||
? html`
|
||||
${this._computeIsRunning
|
||||
? html`
|
||||
<ha-svg-icon
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.app_running"
|
||||
)}
|
||||
class="running"
|
||||
.path=${mdiPlayCircle}
|
||||
></ha-svg-icon>
|
||||
`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
.title=${this.hass.localize(
|
||||
"ui.panel.config.apps.dashboard.app_stopped"
|
||||
)}
|
||||
class="stopped"
|
||||
.path=${mdiCircleOffOutline}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
`
|
||||
: html` ${this.addon.version_latest} `}
|
||||
? html`<supervisor-apps-state
|
||||
.state=${this.addon.state}
|
||||
></supervisor-apps-state>`
|
||||
: this.addon.version_latest}
|
||||
</div>
|
||||
</div>
|
||||
<div class="description light-color">
|
||||
@@ -837,7 +819,7 @@ class SupervisorAppInfo extends LitElement {
|
||||
const id = ev.currentTarget.id as AddonCapability;
|
||||
showAlertDialog(this, {
|
||||
title: this.hass.localize(
|
||||
`ui.panel.config.apps.dashboard.capability.${id}.title`
|
||||
`ui.panel.config.apps.dashboard.capability.${id}.title` as LocalizeKeys
|
||||
),
|
||||
text: this.hass.localize(
|
||||
`ui.panel.config.apps.dashboard.capability.${id}.description`
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import "@home-assistant/webawesome/dist/components/tag/tag";
|
||||
import { mdiHelpCircleOutline } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import type { AddonStage } from "../../../../data/hassio/addon";
|
||||
import type { AddonStage, AddonState } from "../../../../data/hassio/addon";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import { getAppDisplayName } from "../common/app";
|
||||
import "./supervisor-apps-state";
|
||||
import "./supervisor-apps-tag";
|
||||
|
||||
export interface AppTag {
|
||||
label: string;
|
||||
variant: "brand" | "success" | "warning" | "danger" | "neutral";
|
||||
iconPath?: string;
|
||||
}
|
||||
|
||||
@customElement("supervisor-apps-card-content")
|
||||
class SupervisorAppsCardContent extends LitElement {
|
||||
@@ -16,13 +25,13 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
|
||||
@property() public stage: AddonStage = "stable";
|
||||
|
||||
@property() public state: AddonState = null;
|
||||
|
||||
@property() public description?: string;
|
||||
|
||||
@property({ type: Boolean }) public available = true;
|
||||
|
||||
@property({ attribute: false }) public showTopbar = false;
|
||||
|
||||
@property({ attribute: false }) public topbarClass?: string;
|
||||
@property({ attribute: false }) public tags?: AppTag[];
|
||||
|
||||
@property({ attribute: false }) public iconTitle?: string;
|
||||
|
||||
@@ -33,78 +42,87 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
@property({ attribute: false }) public iconImage?: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
const stageLabel =
|
||||
this.stage !== "stable"
|
||||
? this.hass.localize(
|
||||
`ui.panel.config.apps.dashboard.capability.stages.${this.stage}`
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
${this.showTopbar
|
||||
? html` <div class="topbar ${this.topbarClass}"></div> `
|
||||
: ""}
|
||||
${this.iconImage
|
||||
? html`
|
||||
<div class="icon_image ${this.iconClass}">
|
||||
<img
|
||||
src=${this.iconImage}
|
||||
.title=${this.iconTitle}
|
||||
alt=${this.iconTitle ?? ""}
|
||||
/>
|
||||
<div></div>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
class=${this.iconClass!}
|
||||
.path=${this.icon}
|
||||
.title=${this.iconTitle}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
<div>
|
||||
<div class="title-row">
|
||||
<div class="title">${getAppDisplayName(this.title, this.stage)}</div>
|
||||
${stageLabel
|
||||
? html` <span class="stage ${this.stage}"> ${stageLabel} </span> `
|
||||
: nothing}
|
||||
<div class="app">
|
||||
<div class="icon-wrapper">
|
||||
${this.iconImage
|
||||
? html`
|
||||
<img
|
||||
class="icon-image"
|
||||
src=${this.iconImage}
|
||||
.title=${this.iconTitle}
|
||||
alt=${this.iconTitle ?? ""}
|
||||
/>
|
||||
`
|
||||
: html`
|
||||
<ha-svg-icon
|
||||
class="app-icon"
|
||||
.path=${this.icon}
|
||||
.title=${this.iconTitle}
|
||||
></ha-svg-icon>
|
||||
`}
|
||||
</div>
|
||||
<div class="addition">
|
||||
${this.description}
|
||||
${
|
||||
/* treat as available when undefined */
|
||||
this.available === false ? " (Not available)" : ""
|
||||
}
|
||||
<div>
|
||||
<div class="title-row">
|
||||
<div class="title">
|
||||
${getAppDisplayName(this.title, this.stage)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="addition">
|
||||
${this.description}
|
||||
${
|
||||
/* treat as available when undefined */
|
||||
this.available === false ? " (Not available)" : ""
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
${this.tags?.length || this.state
|
||||
? html`
|
||||
<div class="footer">
|
||||
<supervisor-apps-state
|
||||
.state=${this.state || "unknown"}
|
||||
></supervisor-apps-state>
|
||||
|
||||
${this.tags?.length
|
||||
? html`<div class="tags">
|
||||
${this.tags.map(
|
||||
(tag) =>
|
||||
html`<supervisor-apps-tag
|
||||
.variant=${tag.variant}
|
||||
.iconPath=${tag.iconPath}
|
||||
.label=${tag.label}
|
||||
></supervisor-apps-tag>`
|
||||
)}
|
||||
</div>`
|
||||
: nothing}
|
||||
</div>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
direction: ltr;
|
||||
.app {
|
||||
margin-bottom: var(--ha-space-2);
|
||||
gap: var(--ha-space-4);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
ha-svg-icon {
|
||||
margin-right: var(--ha-space-6);
|
||||
.icon-wrapper {
|
||||
position: relative;
|
||||
margin-top: var(--ha-space-1);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.app-icon {
|
||||
margin-left: var(--ha-space-2);
|
||||
margin-top: var(--ha-space-3);
|
||||
float: left;
|
||||
margin-top: var(--ha-space-2);
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-svg-icon.update {
|
||||
color: var(--warning-color);
|
||||
}
|
||||
ha-svg-icon.running,
|
||||
ha-svg-icon.installed {
|
||||
color: var(--success-color);
|
||||
}
|
||||
ha-svg-icon.hassupdate,
|
||||
ha-svg-icon.backup {
|
||||
color: var(--state-icon-color);
|
||||
}
|
||||
ha-svg-icon.not_available {
|
||||
color: var(--error-color);
|
||||
.icon-image {
|
||||
max-height: 40px;
|
||||
max-width: 40px;
|
||||
}
|
||||
.title {
|
||||
flex: 1;
|
||||
@@ -120,22 +138,6 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
gap: var(--ha-space-2);
|
||||
min-width: 0;
|
||||
}
|
||||
.stage {
|
||||
flex: none;
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
padding: 4px 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.stage.experimental {
|
||||
color: var(--warning-color);
|
||||
background-color: rgba(var(--rgb-warning-color), 0.12);
|
||||
}
|
||||
.stage.deprecated {
|
||||
color: var(--error-color);
|
||||
background-color: rgba(var(--rgb-error-color), 0.12);
|
||||
}
|
||||
.addition {
|
||||
color: var(--secondary-text-color);
|
||||
margin-top: var(--ha-space-1);
|
||||
@@ -144,43 +146,18 @@ class SupervisorAppsCardContent extends LitElement {
|
||||
height: 2.4em;
|
||||
line-height: var(--ha-line-height-condensed);
|
||||
}
|
||||
.icon_image img {
|
||||
max-height: 40px;
|
||||
max-width: 40px;
|
||||
margin-top: var(--ha-space-1);
|
||||
margin-right: var(--ha-space-4);
|
||||
float: left;
|
||||
.footer {
|
||||
border-top: var(--ha-border-width-sm) solid
|
||||
var(--ha-color-border-neutral-quiet);
|
||||
padding-top: var(--ha-space-2);
|
||||
display: flex;
|
||||
gap: var(--ha-space-2);
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.icon_image.stopped,
|
||||
.icon_image.not_available {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
.dot {
|
||||
position: absolute;
|
||||
background-color: var(--warning-color);
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: var(--ha-space-2);
|
||||
right: var(--ha-space-2);
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
}
|
||||
.topbar {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.topbar.installed {
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
.topbar.update {
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
.topbar.unavailable {
|
||||
background-color: var(--error-color);
|
||||
.tags {
|
||||
display: flex;
|
||||
gap: var(--ha-space-2);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { mdiHelpCircle } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import { internationalizationContext } from "../../../../data/context";
|
||||
import type { AddonState } from "../../../../data/hassio/addon";
|
||||
|
||||
@customElement("supervisor-apps-state")
|
||||
class SupervisorAppsState extends LitElement {
|
||||
@property() public state: Exclude<AddonState, null> = "unknown";
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
${this.state === "unknown"
|
||||
? html`<ha-svg-icon .path=${mdiHelpCircle}></ha-svg-icon>`
|
||||
: html` <div class="dot state-${this.state}"></div> `}
|
||||
<span
|
||||
>${this._i18n.localize(
|
||||
`ui.panel.config.apps.dashboard.capability.state.${this.state}`
|
||||
)}</span
|
||||
>
|
||||
`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--ha-space-2);
|
||||
color: var(--ha-color-text-secondary);
|
||||
font-size: var(--ha-font-size-m);
|
||||
}
|
||||
.dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: var(--ha-border-radius-circle);
|
||||
background-color: var(--ha-color-on-neutral-normal);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.dot.state-started {
|
||||
background-color: var(--ha-color-green-80);
|
||||
animation: state-dot-pulse 1.8s infinite;
|
||||
}
|
||||
.dot.state-startup {
|
||||
background-color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
.dot.state-error {
|
||||
background-color: var(--ha-color-on-danger-normal);
|
||||
}
|
||||
ha-svg-icon {
|
||||
--mdc-icon-size: 20px;
|
||||
}
|
||||
@keyframes state-dot-pulse {
|
||||
0% {
|
||||
box-shadow: 0 0 0 0 rgba(var(--rgb-success-color), 0.6);
|
||||
}
|
||||
100% {
|
||||
box-shadow: 0 0 0 6px rgba(var(--rgb-success-color), 0);
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion) {
|
||||
.dot.state-started {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"supervisor-apps-state": SupervisorAppsState;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import "@home-assistant/webawesome/dist/components/tag/tag";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
|
||||
@customElement("supervisor-apps-tag")
|
||||
class SupervisorAppsTag extends LitElement {
|
||||
@property() public variant:
|
||||
| "brand"
|
||||
| "success"
|
||||
| "warning"
|
||||
| "danger"
|
||||
| "neutral" = "neutral";
|
||||
|
||||
@property({ attribute: "icon-path" }) public iconPath?: string;
|
||||
|
||||
@property() public label!: string;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`<wa-tag .variant=${this.variant}>
|
||||
${this.iconPath
|
||||
? html`<ha-svg-icon .path=${this.iconPath}></ha-svg-icon>`
|
||||
: nothing}
|
||||
${this.label}
|
||||
</wa-tag>`;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
wa-tag {
|
||||
font-size: var(--ha-font-size-xs);
|
||||
border-radius: var(--ha-border-radius-pill);
|
||||
height: 20px;
|
||||
border: none;
|
||||
padding-inline: var(--ha-space-1) var(--ha-space-2);
|
||||
}
|
||||
wa-tag ha-svg-icon {
|
||||
--mdc-icon-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
wa-tag[variant="success"] {
|
||||
color: var(--ha-color-on-success-normal);
|
||||
}
|
||||
wa-tag[variant="warning"] {
|
||||
color: var(--ha-color-on-warning-normal);
|
||||
}
|
||||
wa-tag[variant="danger"] {
|
||||
color: var(--ha-color-on-error-normal);
|
||||
}
|
||||
wa-tag[variant="neutral"] {
|
||||
color: var(--ha-color-on-neutral-normal);
|
||||
}
|
||||
wa-tag[variant="brand"] {
|
||||
color: var(--ha-color-on-primary-normal);
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"supervisor-apps-tag": SupervisorAppsTag;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import {
|
||||
mdiAlertDecagramOutline,
|
||||
mdiArrowUpBoldCircle,
|
||||
mdiArrowUpBoldCircleOutline,
|
||||
mdiFlask,
|
||||
mdiPuzzle,
|
||||
mdiRefresh,
|
||||
mdiStorePlus,
|
||||
@@ -29,7 +32,9 @@ import "../../../layouts/hass-error-screen";
|
||||
import "../../../layouts/hass-loading-screen";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { getAppDisplayName } from "./common/app";
|
||||
import "./components/supervisor-apps-card-content";
|
||||
import type { AppTag } from "./components/supervisor-apps-card-content";
|
||||
import { supervisorAppsStyle } from "./resources/supervisor-apps-style";
|
||||
|
||||
@customElement("ha-config-apps-installed")
|
||||
@@ -96,65 +101,59 @@ export class HaConfigAppsInstalled extends LitElement {
|
||||
</ha-input-search>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="card-group">
|
||||
${addons.length === 0
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
${addons.length === 0
|
||||
? html`
|
||||
<ha-card outlined>
|
||||
<div class="card-content">
|
||||
<button class="link" @click=${this._openStore}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.apps.installed.no_apps"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: addons.map(
|
||||
(addon) => html`
|
||||
<ha-card
|
||||
role="button"
|
||||
tabindex="0"
|
||||
outlined
|
||||
.addon=${addon}
|
||||
@click=${this._addonTapped}
|
||||
aria-label=${getAppDisplayName(addon.name, addon.stage)}
|
||||
>
|
||||
<div class="card-content">
|
||||
<button class="link" @click=${this._openStore}>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.apps.installed.no_apps"
|
||||
)}
|
||||
</button>
|
||||
<supervisor-apps-card-content
|
||||
.hass=${this.hass}
|
||||
.title=${addon.name}
|
||||
.stage=${addon.stage}
|
||||
.description=${addon.description}
|
||||
available
|
||||
.tags=${this._getAppTags(addon)}
|
||||
.state=${addon.state}
|
||||
.icon=${addon.update_available
|
||||
? mdiArrowUpBoldCircle
|
||||
: mdiPuzzle}
|
||||
.iconTitle=${addon.state !== "started"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.apps.installed.app_stopped"
|
||||
)
|
||||
: addon.update_available
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.apps.installed.app_update_available"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.apps.installed.app_running"
|
||||
)}
|
||||
.iconImage=${addon.icon
|
||||
? `/api/hassio/addons/${addon.slug}/icon`
|
||||
: undefined}
|
||||
></supervisor-apps-card-content>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
: addons.map(
|
||||
(addon) => html`
|
||||
<ha-card
|
||||
outlined
|
||||
.addon=${addon}
|
||||
@click=${this._addonTapped}
|
||||
>
|
||||
<div class="card-content">
|
||||
<supervisor-apps-card-content
|
||||
.hass=${this.hass}
|
||||
.title=${addon.name}
|
||||
.stage=${addon.stage}
|
||||
.description=${addon.description}
|
||||
available
|
||||
.showTopbar=${addon.update_available}
|
||||
topbarClass="update"
|
||||
.icon=${addon.update_available
|
||||
? mdiArrowUpBoldCircle
|
||||
: mdiPuzzle}
|
||||
.iconTitle=${addon.state !== "started"
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.apps.installed.app_stopped"
|
||||
)
|
||||
: addon.update_available
|
||||
? this.hass.localize(
|
||||
"ui.panel.config.apps.installed.app_update_available"
|
||||
)
|
||||
: this.hass.localize(
|
||||
"ui.panel.config.apps.installed.app_running"
|
||||
)}
|
||||
.iconClass=${addon.update_available
|
||||
? addon.state === "started"
|
||||
? "update"
|
||||
: "update stopped"
|
||||
: addon.state === "started"
|
||||
? "running"
|
||||
: "stopped"}
|
||||
.iconImage=${addon.icon
|
||||
? `/api/hassio/addons/${addon.slug}/icon`
|
||||
: undefined}
|
||||
></supervisor-apps-card-content>
|
||||
</div>
|
||||
</ha-card>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ha-button size="large" href="/config/apps/available">
|
||||
@@ -217,6 +216,32 @@ export class HaConfigAppsInstalled extends LitElement {
|
||||
navigate("/config/apps/available");
|
||||
}
|
||||
|
||||
private _getAppTags(addon: HassioAddonInfo): AppTag[] {
|
||||
const labels: AppTag[] = [];
|
||||
|
||||
if (addon.update_available) {
|
||||
labels.push({
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.apps.state.update_available`
|
||||
),
|
||||
variant: "brand",
|
||||
iconPath: mdiArrowUpBoldCircleOutline,
|
||||
});
|
||||
}
|
||||
if (addon.stage !== "stable") {
|
||||
labels.push({
|
||||
label: this.hass.localize(
|
||||
`ui.panel.config.apps.dashboard.capability.stages.${addon.stage}`
|
||||
),
|
||||
variant: addon.stage === "experimental" ? "warning" : "danger",
|
||||
iconPath:
|
||||
addon.stage === "experimental" ? mdiFlask : mdiAlertDecagramOutline,
|
||||
});
|
||||
}
|
||||
|
||||
return labels;
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
supervisorAppsStyle,
|
||||
css`
|
||||
@@ -229,7 +254,10 @@ export class HaConfigAppsInstalled extends LitElement {
|
||||
ha-card {
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
ha-card:hover {
|
||||
background-color: var(--ha-color-fill-neutral-quiet-resting);
|
||||
}
|
||||
|
||||
.search {
|
||||
@@ -247,10 +275,13 @@ export class HaConfigAppsInstalled extends LitElement {
|
||||
.content {
|
||||
padding: var(--ha-space-4);
|
||||
margin-bottom: var(--ha-space-18);
|
||||
gap: var(--ha-space-4);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(336px, 100%), 1fr));
|
||||
}
|
||||
|
||||
.card-content {
|
||||
padding: var(--ha-space-4);
|
||||
padding: var(--ha-space-4) var(--ha-space-4) var(--ha-space-2);
|
||||
}
|
||||
|
||||
button.link {
|
||||
|
||||
@@ -71,7 +71,6 @@ export default class HaAutomationActionEditor extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this.action}
|
||||
@value-changed=${this._onYamlChange}
|
||||
.readOnly=${this.disabled}
|
||||
|
||||
@@ -54,7 +54,6 @@ export class HaEventAction extends LitElement implements ActionElement {
|
||||
@change=${this._eventChanged}
|
||||
></ha-input>
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.actions.type.event.event_data"
|
||||
)}
|
||||
|
||||
@@ -47,8 +47,6 @@ import "../../../components/ha-icon";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-icon-button-prev";
|
||||
import "../../../components/ha-icon-next";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import type { PickerComboBoxItem } from "../../../components/ha-picker-combo-box";
|
||||
import "../../../components/ha-section-title";
|
||||
import "../../../components/ha-service-icon";
|
||||
@@ -56,6 +54,8 @@ import "../../../components/ha-tooltip";
|
||||
import { TRIGGER_ICONS } from "../../../components/ha-trigger-icon";
|
||||
import "../../../components/input/ha-input-search";
|
||||
import type { HaInputSearch } from "../../../components/input/ha-input-search";
|
||||
import "../../../components/item/ha-list-item-button";
|
||||
import "../../../components/list/ha-list-base";
|
||||
import {
|
||||
ACTION_BUILDING_BLOCKS_GROUP,
|
||||
ACTION_COLLECTIONS,
|
||||
@@ -731,7 +731,7 @@ class DialogAddAutomationElement
|
||||
.manifests=${this._manifests}
|
||||
></ha-automation-add-from-target>`
|
||||
: html`
|
||||
<ha-md-list
|
||||
<ha-list-base
|
||||
class=${classMap({
|
||||
groups: true,
|
||||
hidden: hideCollections,
|
||||
@@ -739,9 +739,7 @@ class DialogAddAutomationElement
|
||||
})}
|
||||
>
|
||||
${this._params!.clipboardItem
|
||||
? html`<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
? html`<ha-list-item-button
|
||||
class="paste"
|
||||
@click=${this._paste}
|
||||
>
|
||||
@@ -785,7 +783,7 @@ class DialogAddAutomationElement
|
||||
slot="end"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-button>
|
||||
<wa-divider></wa-divider>`
|
||||
: nothing}
|
||||
${collections.map(
|
||||
@@ -799,9 +797,7 @@ class DialogAddAutomationElement
|
||||
collection.groups,
|
||||
(item) => item.key,
|
||||
(item) => html`
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
<ha-list-item-button
|
||||
.value=${item.key}
|
||||
.index=${collection.collectionIndex}
|
||||
@click=${this._groupSelected}
|
||||
@@ -821,12 +817,12 @@ class DialogAddAutomationElement
|
||||
${this._narrow
|
||||
? html`<ha-icon-next slot="end"></ha-icon-next>`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
)}
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-list-base>
|
||||
`}
|
||||
${!this._filter
|
||||
? html`
|
||||
@@ -2391,8 +2387,14 @@ class DialogAddAutomationElement
|
||||
gap: var(--ha-space-3);
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
ha-list-item-button {
|
||||
--ha-row-item-padding-block: var(--ha-space-1);
|
||||
--ha-row-item-padding-inline: var(--ha-space-3);
|
||||
--ha-row-item-min-height: 40px;
|
||||
}
|
||||
ha-list-item-button::part(start),
|
||||
ha-list-item-button::part(end) {
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
}
|
||||
|
||||
ha-automation-add-from-target,
|
||||
|
||||
+39
-41
@@ -23,11 +23,12 @@ import { stringCompare } from "../../../../common/string/compare";
|
||||
import "../../../../components/ha-floor-icon";
|
||||
import "../../../../components/ha-icon";
|
||||
import "../../../../components/ha-icon-next";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-section-title";
|
||||
import "../../../../components/ha-state-icon";
|
||||
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 {
|
||||
getAreaDeviceLookup,
|
||||
getAreaEntityLookup,
|
||||
@@ -328,15 +329,13 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
)}</ha-section-title
|
||||
>
|
||||
${emptyFloors
|
||||
? html`<ha-md-list>
|
||||
<ha-md-list-item type="text">
|
||||
<div slot="headline">
|
||||
${this._i18n.localize("ui.components.area-picker.no_areas")}
|
||||
</div>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>`
|
||||
? html`<ha-row-item>
|
||||
<div slot="headline">
|
||||
${this._i18n.localize("ui.components.area-picker.no_areas")}
|
||||
</div>
|
||||
</ha-row-item>`
|
||||
: html`${narrow
|
||||
? html`<ha-md-list>${floorAreas}</ha-md-list>`
|
||||
? html`<ha-list-base>${floorAreas}</ha-list-base>`
|
||||
: html`<wa-tree
|
||||
@wa-selection-change=${this._handleSelectionChange}
|
||||
>${floorAreas}</wa-tree
|
||||
@@ -370,12 +369,10 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
"ui.components.label-picker.labels"
|
||||
)}</ha-section-title
|
||||
>
|
||||
<ha-md-list>
|
||||
<ha-list-base>
|
||||
${labels.map(
|
||||
(label) =>
|
||||
html`<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
html`<ha-list-item-button
|
||||
.target=${label.id}
|
||||
@click=${this._selectItem}
|
||||
class=${this._getSelectedTargetId(value) === label.id
|
||||
@@ -393,9 +390,9 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
${narrow
|
||||
? html`<ha-icon-next slot="end"></ha-icon-next> `
|
||||
: nothing}
|
||||
</ha-md-list-item>`
|
||||
</ha-list-item-button>`
|
||||
)}
|
||||
</ha-md-list>`;
|
||||
</ha-list-base>`;
|
||||
}
|
||||
);
|
||||
|
||||
@@ -514,7 +511,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
"ui.panel.config.automation.editor.unassigned"
|
||||
)}</ha-section-title
|
||||
>${narrow
|
||||
? html`<ha-md-list>${items}</ha-md-list>`
|
||||
? html`<ha-list-base>${items}</ha-list-base>`
|
||||
: html`<wa-tree @wa-selection-change=${this._handleSelectionChange}>
|
||||
${items}
|
||||
</wa-tree>`} `;
|
||||
@@ -568,7 +565,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
"ui.components.target-picker.type.areas"
|
||||
)}</ha-section-title
|
||||
>
|
||||
<ha-md-list>${renderedAreas}</ha-md-list>`;
|
||||
<ha-list-base>${renderedAreas}</ha-list-base>`;
|
||||
}
|
||||
|
||||
return renderedAreas;
|
||||
@@ -617,7 +614,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
"ui.components.target-picker.type.devices"
|
||||
)}</ha-section-title
|
||||
>
|
||||
<ha-md-list>${renderedDevices}</ha-md-list>`;
|
||||
<ha-list-base>${renderedDevices}</ha-list-base>`;
|
||||
}
|
||||
|
||||
return renderedDevices;
|
||||
@@ -664,7 +661,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
"ui.components.target-picker.type.devices"
|
||||
)}</ha-section-title
|
||||
>
|
||||
<ha-md-list> ${renderedDomains} </ha-md-list>`;
|
||||
<ha-list-base>${renderedDomains}</ha-list-base>`;
|
||||
}
|
||||
|
||||
return renderedDomains;
|
||||
@@ -719,7 +716,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
"ui.components.target-picker.type.entities"
|
||||
)}</ha-section-title
|
||||
>
|
||||
<ha-md-list>${renderedEntites}</ha-md-list>`;
|
||||
<ha-list-base>${renderedEntites}</ha-list-base>`;
|
||||
}
|
||||
|
||||
return renderedEntites;
|
||||
@@ -784,17 +781,14 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
children?: TemplateResult | TemplateResult[] | typeof nothing
|
||||
) {
|
||||
if (this.narrow) {
|
||||
return html`<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
return html`<ha-list-item-button
|
||||
.target=${target}
|
||||
@click=${this._selectItem}
|
||||
.title=${label}
|
||||
>
|
||||
${icon?.("start")}
|
||||
<div slot="headline">${label}</div>
|
||||
<ha-icon-next slot="end"></ha-icon-next>
|
||||
</ha-md-list-item>`;
|
||||
</ha-list-item-button>`;
|
||||
}
|
||||
|
||||
return html`
|
||||
@@ -1191,7 +1185,7 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
await this.updateComplete;
|
||||
if (type === "label") {
|
||||
this.shadowRoot!.querySelector(
|
||||
"ha-md-list-item.selected"
|
||||
"ha-list-item-button.selected"
|
||||
)?.scrollIntoView({
|
||||
block: "center",
|
||||
});
|
||||
@@ -1416,9 +1410,9 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
overflow: hidden;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-label-text-weight: var(--ha-font-weight-medium);
|
||||
--md-list-item-label-text-font: var(--ha-font-family-heading);
|
||||
ha-list-item-button::part(label) {
|
||||
font-weight: var(--ha-font-weight-medium);
|
||||
font-family: var(--ha-font-family-heading);
|
||||
}
|
||||
|
||||
.item-label {
|
||||
@@ -1462,29 +1456,33 @@ export default class HaAutomationAddFromTarget extends LitElement {
|
||||
background-color: yellow;
|
||||
}
|
||||
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
--md-list-item-leading-space: var(--ha-space-3);
|
||||
--md-list-item-trailing-space: var(--md-list-item-leading-space);
|
||||
--md-list-item-bottom-space: var(--ha-space-1);
|
||||
--md-list-item-top-space: var(--md-list-item-bottom-space);
|
||||
--md-list-item-supporting-text-font: var(--ha-font-size-s);
|
||||
--md-list-item-one-line-container-height: var(--ha-space-10);
|
||||
ha-list-base {
|
||||
--ha-row-item-padding-inline: var(--ha-space-3);
|
||||
--ha-row-item-padding-block: var(--ha-space-1);
|
||||
--ha-row-item-min-height: 40px;
|
||||
}
|
||||
|
||||
ha-md-list-item.selected {
|
||||
ha-list-item-button::part(end) {
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
}
|
||||
|
||||
ha-list-item-button.selected {
|
||||
background-color: var(--ha-color-fill-primary-normal-active);
|
||||
--md-list-item-label-text-color: var(--ha-color-on-primary-normal);
|
||||
--icon-primary-color: var(--ha-color-on-primary-normal);
|
||||
}
|
||||
|
||||
ha-list-item-button.selected::part(headline) {
|
||||
color: var(--ha-color-on-primary-normal);
|
||||
}
|
||||
|
||||
wa-tree-item[selected],
|
||||
wa-tree-item[selected] > ha-svg-icon,
|
||||
wa-tree-item[selected] > ha-icon,
|
||||
wa-tree-item[selected] > ha-state-icon,
|
||||
wa-tree-item[selected] > ha-floor-icon,
|
||||
ha-md-list-item.selected ha-icon,
|
||||
ha-md-list-item.selected ha-svg-icon {
|
||||
ha-list-item-button.selected ha-icon,
|
||||
ha-list-item-button.selected ha-svg-icon {
|
||||
color: var(--ha-color-on-primary-normal);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,15 @@ import { repeat } from "lit/directives/repeat";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { stopPropagation } from "../../../../common/dom/stop_propagation";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-svg-icon";
|
||||
import "../../../../components/ha-tooltip";
|
||||
import "../../../../components/item/ha-list-item-button";
|
||||
import "../../../../components/list/ha-list-base";
|
||||
import type { ConfigEntry } from "../../../../data/config_entries";
|
||||
import type { LabelRegistryEntry } from "../../../../data/label/label_registry";
|
||||
import { haStyleScrollbar } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { AddAutomationElementListItem } from "../add-automation-element-dialog";
|
||||
import { haStyleScrollbar } from "../../../../resources/styles";
|
||||
import { getTargetIcon } from "../target/get_target_icon";
|
||||
|
||||
type Target = [string, string | undefined, string | undefined];
|
||||
@@ -103,17 +103,12 @@ export class HaAutomationAddItems extends LitElement {
|
||||
|
||||
return html`
|
||||
<div class="items-title">${title}</div>
|
||||
<ha-md-list>
|
||||
<ha-list-base>
|
||||
${repeat(
|
||||
items,
|
||||
(item) => item.key,
|
||||
(item) => html`
|
||||
<ha-md-list-item
|
||||
interactive
|
||||
type="button"
|
||||
.value=${item.key}
|
||||
@click=${this._selected}
|
||||
>
|
||||
<ha-list-item-button .value=${item.key} @click=${this._selected}>
|
||||
<div slot="headline" class=${this.target ? "item-headline" : ""}>
|
||||
${item.name}${this._renderTarget(this.target)}
|
||||
</div>
|
||||
@@ -138,6 +133,7 @@ export class HaAutomationAddItems extends LitElement {
|
||||
@click=${stopPropagation}
|
||||
></ha-svg-icon>
|
||||
<ha-tooltip
|
||||
slot="end"
|
||||
.for=${`description-tooltip-${item.key}`}
|
||||
@wa-show=${stopPropagation}
|
||||
@wa-hide=${stopPropagation}
|
||||
@@ -151,10 +147,10 @@ export class HaAutomationAddItems extends LitElement {
|
||||
class="plus"
|
||||
.path=${mdiPlus}
|
||||
></ha-svg-icon>
|
||||
</ha-md-list-item>
|
||||
</ha-list-item-button>
|
||||
`
|
||||
)}
|
||||
</ha-md-list>
|
||||
</ha-list-base>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -245,24 +241,26 @@ export class HaAutomationAddItems extends LitElement {
|
||||
background-color: var(--ha-color-fill-danger-quiet-resting);
|
||||
color: var(--ha-color-on-danger-normal);
|
||||
}
|
||||
.items ha-md-list {
|
||||
--md-list-item-two-line-container-height: var(--ha-space-12);
|
||||
--md-list-item-leading-space: var(--ha-space-3);
|
||||
--md-list-item-trailing-space: var(--md-list-item-leading-space);
|
||||
--md-list-item-bottom-space: var(--ha-space-2);
|
||||
--md-list-item-top-space: var(--md-list-item-bottom-space);
|
||||
--md-list-item-supporting-text-font: var(--ha-font-family-body);
|
||||
--ha-md-list-item-gap: var(--ha-space-3);
|
||||
.items ha-list-base {
|
||||
--ha-row-item-padding-inline: var(--ha-space-3);
|
||||
--ha-row-item-padding-block: var(--ha-space-2);
|
||||
--ha-list-gap: var(--ha-space-3);
|
||||
gap: var(--ha-space-2);
|
||||
padding: 0 var(--ha-space-4);
|
||||
padding-bottom: max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
||||
}
|
||||
.items ha-md-list ha-md-list-item {
|
||||
.items ha-list-base ha-list-item-button {
|
||||
border-radius: var(--ha-border-radius-lg);
|
||||
border: 1px solid var(--ha-color-border-neutral-quiet);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.items ha-md-list {
|
||||
padding-bottom: max(var(--safe-area-inset-bottom), var(--ha-space-3));
|
||||
.items ha-list-base ha-list-item-button::part(start),
|
||||
.items ha-list-base ha-list-item-button::part(end) {
|
||||
color: var(--ha-color-on-neutral-quiet);
|
||||
}
|
||||
.items ha-list-base ha-list-item-button::part(end) {
|
||||
gap: var(--ha-space-3);
|
||||
}
|
||||
|
||||
.items .item-headline {
|
||||
|
||||
@@ -8,8 +8,6 @@ import "../../../../components/ha-button";
|
||||
import "../../../../components/ha-dialog";
|
||||
import "../../../../components/ha-dialog-footer";
|
||||
import "../../../../components/ha-icon-button";
|
||||
import "../../../../components/ha-md-list";
|
||||
import "../../../../components/ha-md-list-item";
|
||||
import "../../../../components/ha-select-box";
|
||||
import "../../../../components/input/ha-input";
|
||||
|
||||
|
||||
@@ -79,7 +79,6 @@ export default class HaAutomationConditionEditor extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this.condition}
|
||||
@value-changed=${this._onYamlChange}
|
||||
.readOnly=${this.disabled}
|
||||
|
||||
@@ -186,7 +186,6 @@ export class HaPlatformCondition extends LitElement {
|
||||
: nothing}
|
||||
${shouldRenderDataYaml
|
||||
? html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.service-control.action_data"
|
||||
)}
|
||||
|
||||
@@ -541,7 +541,6 @@ export class HaAutomationEditor extends AutomationScriptEditorMixin<AutomationCo
|
||||
`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this.readOnly}
|
||||
@value-changed=${this._yamlChanged}
|
||||
|
||||
@@ -276,7 +276,6 @@ export class HaAutomationTrace extends LitElement {
|
||||
: this._view === "automation_config"
|
||||
? html`
|
||||
<ha-trace-config
|
||||
.hass=${this.hass}
|
||||
.trace=${this._trace}
|
||||
></ha-trace-config>
|
||||
`
|
||||
@@ -292,7 +291,6 @@ export class HaAutomationTrace extends LitElement {
|
||||
: this._view === "blueprint"
|
||||
? html`
|
||||
<ha-trace-blueprint-config
|
||||
.hass=${this.hass}
|
||||
.trace=${this._trace}
|
||||
></ha-trace-blueprint-config>
|
||||
`
|
||||
|
||||
@@ -52,7 +52,6 @@ class DialogPasteReplace extends LitElement {
|
||||
</p>
|
||||
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._params?.pastedConfig}
|
||||
read-only
|
||||
></ha-yaml-editor>
|
||||
|
||||
@@ -124,9 +124,9 @@ export const manualEditorStyles = css`
|
||||
.has-sidebar {
|
||||
--sidebar-width: min(
|
||||
max(var(--sidebar-dynamic-width), ${SIDEBAR_MIN_WIDTH}px),
|
||||
100vw - ${CONTENT_MIN_WIDTH}px - var(--mdc-drawer-width, 0px),
|
||||
100vw - ${CONTENT_MIN_WIDTH}px - var(--ha-sidebar-width, 0px),
|
||||
var(--ha-automation-editor-max-width) -
|
||||
${CONTENT_MIN_WIDTH}px - var(--mdc-drawer-width, 0px)
|
||||
${CONTENT_MIN_WIDTH}px - var(--ha-sidebar-width, 0px)
|
||||
);
|
||||
--sidebar-gap: var(--ha-space-4);
|
||||
}
|
||||
|
||||
@@ -71,7 +71,6 @@ export default class HaAutomationTriggerEditor extends LitElement {
|
||||
`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this.trigger}
|
||||
.readOnly=${this.disabled}
|
||||
@value-changed=${this._onYamlChange}
|
||||
|
||||
@@ -760,7 +760,6 @@ export default class HaAutomationTriggerRow extends LitElement {
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
disable-fullscreen
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._triggeredResult}
|
||||
></ha-yaml-editor>
|
||||
`,
|
||||
|
||||
@@ -34,7 +34,6 @@ export class HaEventTrigger extends LitElement implements TriggerElement {
|
||||
@change=${this._valueChanged}
|
||||
></ha-input>
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.automation.editor.triggers.type.event.event_data"
|
||||
)}
|
||||
|
||||
@@ -221,7 +221,6 @@ export class HaPlatformTrigger extends LitElement {
|
||||
: nothing}
|
||||
${shouldRenderDataYaml
|
||||
? html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.label=${this.hass.localize(
|
||||
"ui.components.service-control.action_data"
|
||||
)}
|
||||
|
||||
@@ -42,7 +42,6 @@ class SupervisorFormfieldLabel extends LitElement {
|
||||
margin-right: 4px;
|
||||
margin-inline-end: 4px;
|
||||
margin-inline-start: initial;
|
||||
font-size: var(--ha-font-size-m);
|
||||
font-weight: var(--ha-font-weight-normal);
|
||||
line-height: var(--ha-line-height-normal);
|
||||
letter-spacing: 0.5px;
|
||||
|
||||
@@ -149,7 +149,6 @@ class DialogImportBlueprint extends LitElement {
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
.value=${this._result.raw_data}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
dir="ltr"
|
||||
></ha-code-editor>
|
||||
|
||||
@@ -7,18 +7,17 @@ import { extractSearchParam } from "../../../common/url/search-params";
|
||||
import "../../../components/ha-analytics";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-card";
|
||||
import "../../../components/ha-md-list";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-spinner";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-switch";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import type { Analytics } from "../../../data/analytics";
|
||||
import {
|
||||
getAnalyticsDetails,
|
||||
setAnalyticsPreferences,
|
||||
} from "../../../data/analytics";
|
||||
import { getSignedPath } from "../../../data/auth";
|
||||
import { getConfigEntries } from "../../../data/config_entries";
|
||||
import type { LabPreviewFeature } from "../../../data/labs";
|
||||
import { subscribeLabFeature } from "../../../data/labs";
|
||||
@@ -103,26 +102,24 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.analytics.preferences.snapshots.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.analytics.preferences.snapshots.description`
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
@change=${this._handleDeviceRowClick}
|
||||
.checked=${!!this._analyticsDetails?.preferences.snapshots}
|
||||
.disabled=${this._analyticsDetails === undefined}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.analytics.preferences.snapshots.title`
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
`ui.panel.config.analytics.preferences.snapshots.description`
|
||||
)}
|
||||
</span>
|
||||
<ha-switch
|
||||
slot="end"
|
||||
@change=${this._handleDeviceRowClick}
|
||||
.checked=${!!this._analyticsDetails?.preferences.snapshots}
|
||||
.disabled=${this._analyticsDetails === undefined}
|
||||
></ha-switch>
|
||||
</ha-row-item>
|
||||
</div>
|
||||
<div class="card-actions">
|
||||
<ha-button
|
||||
@@ -163,29 +160,27 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
<ha-md-list>
|
||||
<ha-md-list-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.data_collection.toggle_title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.data_collection.toggle_description"
|
||||
)}
|
||||
</span>
|
||||
${this._zwaveDataCollectionOptIn !== undefined
|
||||
? html`
|
||||
<ha-switch
|
||||
slot="end"
|
||||
@change=${this._zwaveDataCollectionToggled}
|
||||
.checked=${this._zwaveDataCollectionOptIn === true}
|
||||
></ha-switch>
|
||||
`
|
||||
: html`<ha-spinner slot="end" size="small"></ha-spinner>`}
|
||||
</ha-md-list-item>
|
||||
</ha-md-list>
|
||||
<ha-row-item>
|
||||
<span slot="headline">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.data_collection.toggle_title"
|
||||
)}
|
||||
</span>
|
||||
<span slot="supporting-text">
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.zwave_js.dashboard.data_collection.toggle_description"
|
||||
)}
|
||||
</span>
|
||||
${this._zwaveDataCollectionOptIn !== undefined
|
||||
? html`
|
||||
<ha-switch
|
||||
slot="end"
|
||||
@change=${this._zwaveDataCollectionToggled}
|
||||
.checked=${this._zwaveDataCollectionOptIn === true}
|
||||
></ha-switch>
|
||||
`
|
||||
: html`<ha-spinner slot="end" size="small"></ha-spinner>`}
|
||||
</ha-row-item>
|
||||
</div>
|
||||
</ha-card>`
|
||||
: nothing}
|
||||
@@ -326,13 +321,8 @@ class ConfigAnalytics extends SubscribeMixin(LitElement) {
|
||||
ha-card:not(:first-of-type) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
ha-md-list {
|
||||
background: none;
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-item-overflow: visible;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
ha-card {
|
||||
transition: box-shadow 0.3s ease;
|
||||
|
||||
@@ -193,9 +193,6 @@ class HaConfigUpdates extends SubscribeMixin(LitElement) {
|
||||
outline: none;
|
||||
text-decoration: underline;
|
||||
}
|
||||
ha-md-list-item {
|
||||
font-size: var(--ha-font-size-l);
|
||||
}
|
||||
div[slot="start"] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,6 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
></ha-service-picker>
|
||||
<ha-yaml-editor
|
||||
id="yaml-editor"
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._serviceData}
|
||||
@value-changed=${this._yamlChanged}
|
||||
></ha-yaml-editor>
|
||||
@@ -238,7 +237,6 @@ class HaPanelDevAction extends MatchMinHeightMixin(LitElement) {
|
||||
>
|
||||
<div class="card-content">
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
auto-update
|
||||
has-extra-actions
|
||||
|
||||
@@ -203,7 +203,6 @@ class HaPanelDevAssist extends SubscribeMixin(LitElement) {
|
||||
? html`
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
.hass=${this.hass}
|
||||
.value=${dump(result).trimRight()}
|
||||
read-only
|
||||
dir="ltr"
|
||||
|
||||
@@ -242,7 +242,6 @@ export class HaDebugViewportEnvironmentCard extends LitElement {
|
||||
<ha-code-editor
|
||||
class="snapshot-editor"
|
||||
mode="yaml"
|
||||
.hass=${this.hass}
|
||||
.value=${text}
|
||||
read-only
|
||||
.linewrap=${true}
|
||||
|
||||
@@ -73,7 +73,6 @@ class HaPanelDevEvent extends LitElement {
|
||||
</div>
|
||||
<div class="code-editor">
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._eventData}
|
||||
.error=${!this._isValid}
|
||||
@value-changed=${this._yamlChanged}
|
||||
|
||||
@@ -227,7 +227,6 @@ class EventSubscribeCard extends LitElement {
|
||||
></ha-icon-button>
|
||||
</div>
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.value=${event.event}
|
||||
auto-update
|
||||
read-only
|
||||
|
||||
@@ -228,7 +228,6 @@ class HaPanelDevState extends LitElement {
|
||||
)}
|
||||
</p>
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.value=${this._stateAttributes}
|
||||
.error=${!this._validJSON}
|
||||
@value-changed=${this._yamlChanged}
|
||||
|
||||
@@ -12,11 +12,18 @@ import {
|
||||
|
||||
import "@home-assistant/webawesome/dist/components/divider/divider";
|
||||
import type { HassEntity } from "home-assistant-js-websocket";
|
||||
import { consume, type ContextType } from "@lit/context";
|
||||
import { css, type CSSResultGroup, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, query, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import type { HASSDomEvent } from "../../../../common/dom/fire_event";
|
||||
import { fireEvent } from "../../../../common/dom/fire_event";
|
||||
import { computeAreaName } from "../../../../common/entity/compute_area_name";
|
||||
import {
|
||||
computeDeviceName,
|
||||
getDuplicatedDeviceNames,
|
||||
} from "../../../../common/entity/compute_device_name";
|
||||
import { computeEntityEntryName } from "../../../../common/entity/compute_entity_name";
|
||||
import { computeStateName } from "../../../../common/entity/compute_state_name";
|
||||
import type { LocalizeFunc } from "../../../../common/translations/localize";
|
||||
import "../../../../components/chips/ha-assist-chip";
|
||||
@@ -24,6 +31,7 @@ import "../../../../components/data-table/ha-data-table";
|
||||
import type {
|
||||
DataTableColumnContainer,
|
||||
HaDataTable,
|
||||
RowClickedEvent,
|
||||
SelectionChangedEvent,
|
||||
SortingDirection,
|
||||
} from "../../../../components/data-table/ha-data-table";
|
||||
@@ -45,9 +53,16 @@ import {
|
||||
updateStatisticsIssues,
|
||||
validateStatistics,
|
||||
} from "../../../../data/recorder";
|
||||
import {
|
||||
apiContext,
|
||||
internationalizationContext,
|
||||
registriesContext,
|
||||
statesContext,
|
||||
} from "../../../../data/context";
|
||||
import { getAreaTableColumn } from "../../common/data-table-columns";
|
||||
import { KeyboardShortcutMixin } from "../../../../mixins/keyboard-shortcut-mixin";
|
||||
import { haStyle } from "../../../../resources/styles";
|
||||
import type { HomeAssistant } from "../../../../types";
|
||||
import type { HomeAssistantRegistries } from "../../../../types";
|
||||
import { showConfirmationDialog } from "../../../lovelace/custom-card-helpers";
|
||||
import { fixStatisticsIssue } from "./fix-statistics";
|
||||
import { showStatisticsAdjustSumDialog } from "./show-dialog-statistics-adjust-sum";
|
||||
@@ -77,13 +92,14 @@ type StatisticData = StatisticsMetaData & {
|
||||
|
||||
type DisplayedStatisticData = StatisticData & {
|
||||
displayName: string;
|
||||
device?: string;
|
||||
device_full?: string;
|
||||
area?: string;
|
||||
issues_string?: string;
|
||||
};
|
||||
|
||||
@customElement("developer-tools-statistics")
|
||||
class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean, reflect: true }) public narrow = false;
|
||||
|
||||
@state() private _data: StatisticData[] = [] as StatisticsMetaData[];
|
||||
@@ -106,6 +122,22 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
@state() private _selectMode = false;
|
||||
|
||||
@state()
|
||||
@consume({ context: apiContext, subscribe: true })
|
||||
private _api!: ContextType<typeof apiContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: registriesContext, subscribe: true })
|
||||
private _registries!: ContextType<typeof registriesContext>;
|
||||
|
||||
@state()
|
||||
@consume({ context: statesContext, subscribe: true })
|
||||
private _states!: ContextType<typeof statesContext>;
|
||||
|
||||
@query("ha-data-table", true) private _dataTable!: HaDataTable;
|
||||
|
||||
@query("ha-input-search") private _searchInput!: HaInputSearch;
|
||||
@@ -115,22 +147,55 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
}
|
||||
|
||||
private _displayData = memoizeOne(
|
||||
(data: StatisticData[], localize: LocalizeFunc): DisplayedStatisticData[] =>
|
||||
data.map((item) => ({
|
||||
...item,
|
||||
displayName: item.state
|
||||
? computeStateName(item.state)
|
||||
: item.name || item.statistic_id,
|
||||
issues_string: item.issues
|
||||
?.map(
|
||||
(issue) =>
|
||||
localize(
|
||||
`ui.panel.config.developer-tools.tabs.statistics.issues.${issue.type}`,
|
||||
issue.data
|
||||
) || issue.type
|
||||
)
|
||||
.join(" "),
|
||||
}))
|
||||
(
|
||||
data: StatisticData[],
|
||||
localize: LocalizeFunc,
|
||||
entities: HomeAssistantRegistries["entities"],
|
||||
devices: HomeAssistantRegistries["devices"],
|
||||
areas: HomeAssistantRegistries["areas"]
|
||||
): DisplayedStatisticData[] => {
|
||||
const duplicatedDeviceNames = getDuplicatedDeviceNames(devices);
|
||||
|
||||
return data.map((item) => {
|
||||
const entry = entities[item.statistic_id];
|
||||
const device = entry?.device_id ? devices[entry.device_id] : undefined;
|
||||
const areaId = entry?.area_id || device?.area_id;
|
||||
const area = areaId ? areas[areaId] : undefined;
|
||||
|
||||
const entityName = entry
|
||||
? computeEntityEntryName(entry, devices, item.state)
|
||||
: undefined;
|
||||
const deviceName = device ? computeDeviceName(device) : undefined;
|
||||
const areaName = area ? computeAreaName(area) : undefined;
|
||||
const deviceFullName = deviceName
|
||||
? duplicatedDeviceNames.has(deviceName) && areaName
|
||||
? `${deviceName} (${areaName})`
|
||||
: deviceName
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...item,
|
||||
displayName:
|
||||
entityName ||
|
||||
deviceName ||
|
||||
(item.state ? computeStateName(item.state) : undefined) ||
|
||||
item.name ||
|
||||
item.statistic_id,
|
||||
device: deviceName,
|
||||
device_full: deviceFullName,
|
||||
area: areaName,
|
||||
issues_string: item.issues
|
||||
?.map(
|
||||
(issue) =>
|
||||
localize(
|
||||
`ui.panel.config.developer-tools.tabs.statistics.issues.${issue.type}`,
|
||||
issue.data
|
||||
) || issue.type
|
||||
)
|
||||
.join(" "),
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
private _columns = memoizeOne(
|
||||
@@ -146,6 +211,18 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
filterable: true,
|
||||
flex: 2,
|
||||
},
|
||||
device: {
|
||||
title: localize("ui.panel.config.entities.picker.headers.device"),
|
||||
sortable: true,
|
||||
template: (entry) => entry.device || "",
|
||||
},
|
||||
device_full: {
|
||||
title: localize("ui.panel.config.entities.picker.headers.device"),
|
||||
filterable: true,
|
||||
groupable: true,
|
||||
hidden: true,
|
||||
},
|
||||
area: getAreaTableColumn(localize),
|
||||
statistic_id: {
|
||||
title: localize(
|
||||
"ui.panel.config.developer-tools.tabs.statistics.data_table.statistic_id"
|
||||
@@ -187,7 +264,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
},
|
||||
fix: {
|
||||
title: "",
|
||||
label: this.hass.localize(
|
||||
label: localize(
|
||||
"ui.panel.config.developer-tools.tabs.statistics.fix_issue.fix"
|
||||
),
|
||||
type: "icon",
|
||||
@@ -231,21 +308,20 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
@click=${this._showStatisticsAdjustSumDialog}
|
||||
></ha-icon-button>
|
||||
`
|
||||
: "",
|
||||
: nothing,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
protected render() {
|
||||
const localize = this.hass.localize;
|
||||
const columns = this._columns(this.hass.localize);
|
||||
const columns = this._columns(this._i18n.localize);
|
||||
|
||||
const selectModeBtn = !this._selectMode
|
||||
? html`<ha-assist-chip
|
||||
class="has-dropdown select-mode-chip"
|
||||
.active=${this._selectMode}
|
||||
@click=${this._enableSelectMode}
|
||||
.title=${localize(
|
||||
.title=${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.enter_selection_mode"
|
||||
)}
|
||||
>
|
||||
@@ -265,11 +341,14 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
<ha-dropdown @wa-select=${this._handleSortBy}>
|
||||
<ha-assist-chip
|
||||
slot="trigger"
|
||||
.label=${localize("ui.components.subpage-data-table.sort_by", {
|
||||
sortColumn: this._sortColumn
|
||||
? ` ${columns[this._sortColumn]?.title || columns[this._sortColumn]?.label}`
|
||||
: "",
|
||||
})}
|
||||
.label=${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.sort_by",
|
||||
{
|
||||
sortColumn: this._sortColumn
|
||||
? ` ${columns[this._sortColumn]?.title || columns[this._sortColumn]?.label}`
|
||||
: "",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
@@ -307,11 +386,14 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
<ha-dropdown @wa-select=${this._handleOverflowGroupBy}>
|
||||
<ha-assist-chip
|
||||
slot="trigger"
|
||||
.label=${localize("ui.components.subpage-data-table.group_by", {
|
||||
groupColumn: this._groupColumn
|
||||
? ` ${columns[this._groupColumn].title || columns[this._groupColumn].label}`
|
||||
: "",
|
||||
})}
|
||||
.label=${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.group_by",
|
||||
{
|
||||
groupColumn: this._groupColumn
|
||||
? ` ${columns[this._groupColumn].title || columns[this._groupColumn].label}`
|
||||
: "",
|
||||
}
|
||||
)}
|
||||
>
|
||||
<ha-svg-icon
|
||||
slot="trailing-icon"
|
||||
@@ -334,7 +416,9 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
value="none"
|
||||
.selected=${this._groupColumn === undefined}
|
||||
>
|
||||
${localize("ui.components.subpage-data-table.dont_group_by")}
|
||||
${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.dont_group_by"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item
|
||||
@@ -346,7 +430,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
slot="icon"
|
||||
.path=${mdiUnfoldLessHorizontal}
|
||||
></ha-svg-icon>
|
||||
${localize(
|
||||
${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.collapse_all_groups"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
@@ -358,7 +442,9 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
slot="icon"
|
||||
.path=${mdiUnfoldMoreHorizontal}
|
||||
></ha-svg-icon>
|
||||
${localize("ui.components.subpage-data-table.expand_all_groups")}
|
||||
${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.expand_all_groups"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
`
|
||||
@@ -367,7 +453,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
const settingsButton = html`<ha-assist-chip
|
||||
class="has-dropdown select-mode-chip"
|
||||
@click=${this._openSettings}
|
||||
.title=${localize("ui.components.subpage-data-table.settings")}
|
||||
.title=${this._i18n.localize("ui.components.subpage-data-table.settings")}
|
||||
>
|
||||
<ha-svg-icon slot="icon" .path=${mdiTableCog}></ha-svg-icon>
|
||||
</ha-assist-chip>`;
|
||||
@@ -380,13 +466,13 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
<ha-icon-button
|
||||
.path=${mdiClose}
|
||||
@click=${this._disableSelectMode}
|
||||
.label=${localize(
|
||||
.label=${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.exit_selection_mode"
|
||||
)}
|
||||
></ha-icon-button>
|
||||
<ha-dropdown>
|
||||
<ha-assist-chip
|
||||
.label=${localize(
|
||||
.label=${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.select"
|
||||
)}
|
||||
slot="trigger"
|
||||
@@ -401,34 +487,41 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
></ha-svg-icon
|
||||
></ha-assist-chip>
|
||||
<ha-dropdown-item @click=${this._selectAll}>
|
||||
${localize("ui.components.subpage-data-table.select_all")}
|
||||
${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.select_all"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item @click=${this._selectAllIssues}>
|
||||
${localize(
|
||||
${this._i18n.localize(
|
||||
"ui.panel.config.developer-tools.tabs.statistics.data_table.select_all_issues"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<ha-dropdown-item @click=${this._selectNone}>
|
||||
${localize("ui.components.subpage-data-table.select_none")}
|
||||
${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.select_none"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
<wa-divider></wa-divider>
|
||||
<ha-dropdown-item @click=${this._disableSelectMode}>
|
||||
${localize(
|
||||
${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.exit_selection_mode"
|
||||
)}
|
||||
</ha-dropdown-item>
|
||||
</ha-dropdown>
|
||||
<p>
|
||||
${localize("ui.components.subpage-data-table.selected", {
|
||||
selected: this._selected.length,
|
||||
})}
|
||||
${this._i18n.localize(
|
||||
"ui.components.subpage-data-table.selected",
|
||||
{
|
||||
selected: this._selected.length,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<div class="center-vertical">
|
||||
<slot name="selection-bar"></slot>
|
||||
</div>
|
||||
<ha-assist-chip
|
||||
.label=${localize(
|
||||
.label=${this._i18n.localize(
|
||||
"ui.panel.config.developer-tools.tabs.statistics.delete_selected"
|
||||
)}
|
||||
.disabled=${!this._selected.length}
|
||||
@@ -448,12 +541,18 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
</slot>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
: nothing}
|
||||
<ha-data-table
|
||||
.narrow=${this.narrow}
|
||||
.columns=${columns}
|
||||
.data=${this._displayData(this._data, this.hass.localize)}
|
||||
.noDataText=${this.hass.localize(
|
||||
.data=${this._displayData(
|
||||
this._data,
|
||||
this._i18n.localize,
|
||||
this._registries.entities,
|
||||
this._registries.devices,
|
||||
this._registries.areas
|
||||
)}
|
||||
.noDataText=${this._i18n.localize(
|
||||
"ui.panel.config.developer-tools.tabs.statistics.data_table.no_statistics"
|
||||
)}
|
||||
.filter=${this.filter}
|
||||
@@ -551,7 +650,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
|
||||
private _openSettings() {
|
||||
showDataTableSettingsDialog(this, {
|
||||
columns: this._columns(this.hass.localize),
|
||||
columns: this._columns(this._i18n.localize),
|
||||
hiddenColumns: this.hiddenColumns,
|
||||
columnOrder: this.columnOrder,
|
||||
onUpdate: (
|
||||
@@ -561,7 +660,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
this.columnOrder = columnOrder;
|
||||
this.hiddenColumns = hiddenColumns;
|
||||
},
|
||||
localizeFunc: this.hass.localize,
|
||||
localizeFunc: this._i18n.localize,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -599,27 +698,29 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
);
|
||||
}
|
||||
|
||||
private _showStatisticsAdjustSumDialog(ev) {
|
||||
private _showStatisticsAdjustSumDialog(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
showStatisticsAdjustSumDialog(this, {
|
||||
statistic: ev.currentTarget.statistic,
|
||||
statistic: (
|
||||
ev.currentTarget as HTMLElement & { statistic: StatisticData }
|
||||
).statistic,
|
||||
});
|
||||
}
|
||||
|
||||
private _rowClicked(ev) {
|
||||
private _rowClicked(ev: HASSDomEvent<RowClickedEvent>) {
|
||||
const id = ev.detail.id;
|
||||
if (id in this.hass.states) {
|
||||
if (id in this._states) {
|
||||
fireEvent(this, "hass-more-info", { entityId: id });
|
||||
}
|
||||
}
|
||||
|
||||
private async _validateStatistics() {
|
||||
const [statisticIds, issues] = await Promise.all([
|
||||
getStatisticIds(this.hass),
|
||||
validateStatistics(this.hass),
|
||||
getStatisticIds(this._api),
|
||||
validateStatistics(this._api),
|
||||
]);
|
||||
|
||||
updateStatisticsIssues(this.hass);
|
||||
updateStatisticsIssues(this._api);
|
||||
|
||||
const statsIds = new Set();
|
||||
|
||||
@@ -627,7 +728,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
statsIds.add(statistic.statistic_id);
|
||||
return {
|
||||
...statistic,
|
||||
state: this.hass.states[statistic.statistic_id],
|
||||
state: this._states[statistic.statistic_id],
|
||||
issues: issues[statistic.statistic_id],
|
||||
};
|
||||
});
|
||||
@@ -638,7 +739,7 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
statistic_id: statisticId,
|
||||
statistics_unit_of_measurement: "",
|
||||
source: "",
|
||||
state: this.hass.states[statisticId],
|
||||
state: this._states[statisticId],
|
||||
issues: issues[statisticId],
|
||||
mean_type: StatisticMeanType.NONE,
|
||||
has_sum: false,
|
||||
@@ -656,25 +757,27 @@ class HaPanelDevStatistics extends KeyboardShortcutMixin(LitElement) {
|
||||
const deletableIds = this._selected;
|
||||
|
||||
await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
title: this._i18n.localize(
|
||||
"ui.panel.config.developer-tools.tabs.statistics.multi_delete.title"
|
||||
),
|
||||
text: html`${this.hass.localize(
|
||||
text: html`${this._i18n.localize(
|
||||
"ui.panel.config.developer-tools.tabs.statistics.multi_delete.info_text",
|
||||
{ statistic_count: deletableIds.length }
|
||||
)}`,
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
confirmText: this._i18n.localize("ui.common.delete"),
|
||||
destructive: true,
|
||||
confirm: async () => {
|
||||
await clearStatistics(this.hass, deletableIds);
|
||||
await clearStatistics(this._api, deletableIds);
|
||||
this._validateStatistics();
|
||||
this._dataTable.clearSelection();
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
private _fixIssue = async (ev) => {
|
||||
const issues = (ev.currentTarget.data as StatisticsValidationResult[]).sort(
|
||||
private _fixIssue = async (ev: Event) => {
|
||||
const issues = (
|
||||
ev.currentTarget as HTMLElement & { data: StatisticsValidationResult[] }
|
||||
).data.sort(
|
||||
(itemA, itemB) =>
|
||||
(FIX_ISSUES_ORDER[itemA.type] ?? 99) -
|
||||
(FIX_ISSUES_ORDER[itemB.type] ?? 99)
|
||||
|
||||
@@ -154,7 +154,6 @@ class HaPanelDevTemplate extends LitElement {
|
||||
<div class="card-content">
|
||||
<ha-code-editor
|
||||
mode="jinja2"
|
||||
.hass=${this.hass}
|
||||
.value=${this._template}
|
||||
.error=${this._error}
|
||||
autofocus
|
||||
|
||||
@@ -96,7 +96,8 @@ export class EnergyGasSettings extends LitElement {
|
||||
></ha-icon>`
|
||||
: html`<ha-svg-icon .path=${mdiFire}></ha-svg-icon>`}
|
||||
<span class="content"
|
||||
>${getStatisticLabel(
|
||||
>${source.name ||
|
||||
getStatisticLabel(
|
||||
this.hass,
|
||||
source.stat_energy_from,
|
||||
this.statsMetadata?.[source.stat_energy_from]
|
||||
@@ -141,6 +142,7 @@ export class EnergyGasSettings extends LitElement {
|
||||
|
||||
private _addSource() {
|
||||
showEnergySettingsGasDialog(this, {
|
||||
statsMetadata: this.statsMetadata,
|
||||
allowedGasUnitClass: getEnergyGasUnitClass(
|
||||
this.preferences,
|
||||
undefined,
|
||||
@@ -164,12 +166,12 @@ export class EnergyGasSettings extends LitElement {
|
||||
ev.currentTarget.closest(".row").source;
|
||||
showEnergySettingsGasDialog(this, {
|
||||
source: { ...origSource },
|
||||
statsMetadata: this.statsMetadata,
|
||||
allowedGasUnitClass: getEnergyGasUnitClass(
|
||||
this.preferences,
|
||||
origSource.stat_energy_from,
|
||||
this.statsMetadata
|
||||
),
|
||||
metadata: this.statsMetadata?.[origSource.stat_energy_from],
|
||||
gas_sources: this.preferences.energy_sources.filter(
|
||||
(src) => src.type === "gas"
|
||||
) as GasSourceTypeEnergyPreference[],
|
||||
|
||||
@@ -95,7 +95,8 @@ export class EnergyWaterSettings extends LitElement {
|
||||
></ha-icon>`
|
||||
: html`<ha-svg-icon .path=${mdiWater}></ha-svg-icon>`}
|
||||
<span class="content"
|
||||
>${getStatisticLabel(
|
||||
>${source.name ||
|
||||
getStatisticLabel(
|
||||
this.hass,
|
||||
source.stat_energy_from,
|
||||
this.statsMetadata?.[source.stat_energy_from]
|
||||
@@ -140,6 +141,7 @@ export class EnergyWaterSettings extends LitElement {
|
||||
|
||||
private _addSource() {
|
||||
showEnergySettingsWaterDialog(this, {
|
||||
statsMetadata: this.statsMetadata,
|
||||
water_sources: this.preferences.energy_sources.filter(
|
||||
(src) => src.type === "water"
|
||||
) as WaterSourceTypeEnergyPreference[],
|
||||
@@ -157,8 +159,8 @@ export class EnergyWaterSettings extends LitElement {
|
||||
const origSource: WaterSourceTypeEnergyPreference =
|
||||
ev.currentTarget.closest(".row").source;
|
||||
showEnergySettingsWaterDialog(this, {
|
||||
statsMetadata: this.statsMetadata,
|
||||
source: { ...origSource },
|
||||
metadata: this.statsMetadata?.[origSource.stat_energy_from],
|
||||
water_sources: this.preferences.energy_sources.filter(
|
||||
(src) => src.type === "water"
|
||||
) as WaterSourceTypeEnergyPreference[],
|
||||
|
||||
@@ -13,7 +13,11 @@ import "../../../../components/input/ha-input";
|
||||
import type { HaInput } from "../../../../components/input/ha-input";
|
||||
import type { DeviceConsumptionEnergyPreference } from "../../../../data/energy";
|
||||
import { energyStatisticHelpUrl } from "../../../../data/energy";
|
||||
import { getStatisticLabel } from "../../../../data/recorder";
|
||||
import {
|
||||
getStatisticLabel,
|
||||
getStatisticMetadata,
|
||||
isExternalStatistic,
|
||||
} from "../../../../data/recorder";
|
||||
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
@@ -232,13 +236,27 @@ export class DialogEnergyDeviceSettingsWater
|
||||
`;
|
||||
}
|
||||
|
||||
private _statisticChanged(ev: ValueChangedEvent<string>) {
|
||||
private async _statisticChanged(ev: ValueChangedEvent<string>) {
|
||||
if (!ev.detail.value) {
|
||||
this._device = undefined;
|
||||
return;
|
||||
}
|
||||
this._device = { stat_consumption: ev.detail.value };
|
||||
this._computePossibleParents();
|
||||
|
||||
if (
|
||||
isExternalStatistic(ev.detail.value) &&
|
||||
this._params?.statsMetadata &&
|
||||
!(ev.detail.value in this._params.statsMetadata)
|
||||
) {
|
||||
const [metadata] = await getStatisticMetadata(this.hass, [
|
||||
ev.detail.value,
|
||||
]);
|
||||
if (metadata) {
|
||||
this._params.statsMetadata[ev.detail.value] = metadata;
|
||||
this.requestUpdate("_params");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _flowRateStatisticChanged(ev: ValueChangedEvent<string>) {
|
||||
|
||||
@@ -16,7 +16,11 @@ import "../../../../components/input/ha-input";
|
||||
import type { HaInput } from "../../../../components/input/ha-input";
|
||||
import type { DeviceConsumptionEnergyPreference } from "../../../../data/energy";
|
||||
import { energyStatisticHelpUrl } from "../../../../data/energy";
|
||||
import { getStatisticLabel } from "../../../../data/recorder";
|
||||
import {
|
||||
getStatisticLabel,
|
||||
getStatisticMetadata,
|
||||
isExternalStatistic,
|
||||
} from "../../../../data/recorder";
|
||||
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyleDialog } from "../../../../resources/styles";
|
||||
@@ -232,13 +236,27 @@ export class DialogEnergyDeviceSettings
|
||||
`;
|
||||
}
|
||||
|
||||
private _statisticChanged(ev: ValueChangedEvent<string>) {
|
||||
private async _statisticChanged(ev: ValueChangedEvent<string>) {
|
||||
if (!ev.detail.value) {
|
||||
this._device = undefined;
|
||||
return;
|
||||
}
|
||||
this._device = { stat_consumption: ev.detail.value };
|
||||
this._computePossibleParents();
|
||||
|
||||
if (
|
||||
isExternalStatistic(ev.detail.value) &&
|
||||
this._params?.statsMetadata &&
|
||||
!(ev.detail.value in this._params.statsMetadata)
|
||||
) {
|
||||
const [metadata] = await getStatisticMetadata(this.hass, [
|
||||
ev.detail.value,
|
||||
]);
|
||||
if (metadata) {
|
||||
this._params.statsMetadata[ev.detail.value] = metadata;
|
||||
this.requestUpdate("_params");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _powerStatisticChanged(ev: ValueChangedEvent<string>) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "../../../../data/energy";
|
||||
import {
|
||||
getDisplayUnit,
|
||||
getStatisticLabel,
|
||||
getStatisticMetadata,
|
||||
isExternalStatistic,
|
||||
} from "../../../../data/recorder";
|
||||
@@ -27,6 +28,7 @@ import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import type { EnergySettingsGasDialogParams } from "./show-dialogs-energy";
|
||||
import type { HaInput } from "../../../../components/input/ha-input";
|
||||
|
||||
const gasDeviceClasses = ["gas", "energy"];
|
||||
const gasUnitClasses = ["volume", "energy"];
|
||||
@@ -71,7 +73,9 @@ export class DialogEnergyGasSettings
|
||||
this._pickedDisplayUnit = getDisplayUnit(
|
||||
this.hass,
|
||||
params.source?.stat_energy_from,
|
||||
params.metadata
|
||||
params.source?.stat_energy_from
|
||||
? params.statsMetadata?.[params.source?.stat_energy_from]
|
||||
: undefined
|
||||
);
|
||||
this._costs = this._source.entity_energy_price
|
||||
? "entity"
|
||||
@@ -196,6 +200,24 @@ export class DialogEnergyGasSettings
|
||||
)}
|
||||
></ha-statistic-picker>
|
||||
|
||||
<ha-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.energy.gas.dialog.display_name"
|
||||
)}
|
||||
type="text"
|
||||
.disabled=${!this._source?.stat_energy_from}
|
||||
.value=${this._source?.name || ""}
|
||||
.placeholder=${this._source?.stat_energy_from
|
||||
? getStatisticLabel(
|
||||
this.hass,
|
||||
this._source.stat_energy_from,
|
||||
this._params?.statsMetadata?.[this._source.stat_energy_from]
|
||||
)
|
||||
: ""}
|
||||
@input=${this._nameChanged}
|
||||
>
|
||||
</ha-input>
|
||||
|
||||
<ha-radio-group
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.energy.gas.dialog.cost_para"
|
||||
@@ -350,11 +372,22 @@ export class DialogEnergyGasSettings
|
||||
|
||||
private async _statisticChanged(ev: ValueChangedEvent<string>) {
|
||||
if (ev.detail.value) {
|
||||
const metadata = await getStatisticMetadata(this.hass, [ev.detail.value]);
|
||||
const [metadata] = await getStatisticMetadata(this.hass, [
|
||||
ev.detail.value,
|
||||
]);
|
||||
if (
|
||||
metadata &&
|
||||
isExternalStatistic(ev.detail.value) &&
|
||||
this._params?.statsMetadata &&
|
||||
!(ev.detail.value in this._params.statsMetadata)
|
||||
) {
|
||||
this._params.statsMetadata[ev.detail.value] = metadata;
|
||||
this.requestUpdate("_params");
|
||||
}
|
||||
this._pickedDisplayUnit = getDisplayUnit(
|
||||
this.hass,
|
||||
ev.detail.value,
|
||||
metadata[0]
|
||||
metadata
|
||||
);
|
||||
if (isExternalStatistic(ev.detail.value) && this._costs !== "statistic") {
|
||||
this._costs = "no-costs";
|
||||
@@ -368,6 +401,16 @@ export class DialogEnergyGasSettings
|
||||
};
|
||||
}
|
||||
|
||||
private _nameChanged(ev: InputEvent) {
|
||||
this._source = {
|
||||
...this._source!,
|
||||
name: (ev.target as HaInput).value,
|
||||
};
|
||||
if (!this._source.name) {
|
||||
delete this._source.name;
|
||||
}
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
try {
|
||||
if (this._costs === "no-costs") {
|
||||
|
||||
@@ -17,12 +17,17 @@ import {
|
||||
emptyWaterEnergyPreference,
|
||||
energyStatisticHelpUrl,
|
||||
} from "../../../../data/energy";
|
||||
import { isExternalStatistic } from "../../../../data/recorder";
|
||||
import {
|
||||
getStatisticLabel,
|
||||
getStatisticMetadata,
|
||||
isExternalStatistic,
|
||||
} from "../../../../data/recorder";
|
||||
import { getSensorDeviceClassConvertibleUnits } from "../../../../data/sensor";
|
||||
import type { HassDialog } from "../../../../dialogs/make-dialog-manager";
|
||||
import { haStyle, haStyleDialog } from "../../../../resources/styles";
|
||||
import type { HomeAssistant, ValueChangedEvent } from "../../../../types";
|
||||
import type { EnergySettingsWaterDialogParams } from "./show-dialogs-energy";
|
||||
import type { HaInput } from "../../../../components/input/ha-input";
|
||||
|
||||
const flowRateUnitClasses = ["volume_flow_rate"];
|
||||
|
||||
@@ -154,6 +159,24 @@ export class DialogEnergyWaterSettings
|
||||
)}
|
||||
></ha-statistic-picker>
|
||||
|
||||
<ha-input
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.energy.water.dialog.display_name"
|
||||
)}
|
||||
type="text"
|
||||
.disabled=${!this._source?.stat_energy_from}
|
||||
.value=${this._source?.name || ""}
|
||||
.placeholder=${this._source?.stat_energy_from
|
||||
? getStatisticLabel(
|
||||
this.hass,
|
||||
this._source.stat_energy_from,
|
||||
this._params?.statsMetadata?.[this._source.stat_energy_from]
|
||||
)
|
||||
: ""}
|
||||
@input=${this._nameChanged}
|
||||
>
|
||||
</ha-input>
|
||||
|
||||
<ha-radio-group
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.energy.water.dialog.cost_para"
|
||||
@@ -300,6 +323,31 @@ export class DialogEnergyWaterSettings
|
||||
...this._source!,
|
||||
stat_energy_from: ev.detail.value,
|
||||
};
|
||||
|
||||
if (
|
||||
ev.detail.value &&
|
||||
isExternalStatistic(ev.detail.value) &&
|
||||
this._params?.statsMetadata &&
|
||||
!(ev.detail.value in this._params.statsMetadata)
|
||||
) {
|
||||
const [metadata] = await getStatisticMetadata(this.hass, [
|
||||
ev.detail.value,
|
||||
]);
|
||||
if (metadata) {
|
||||
this._params.statsMetadata[ev.detail.value] = metadata;
|
||||
this.requestUpdate("_params");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _nameChanged(ev: InputEvent) {
|
||||
this._source = {
|
||||
...this._source!,
|
||||
name: (ev.target as HaInput).value,
|
||||
};
|
||||
if (!this._source.name) {
|
||||
delete this._source.name;
|
||||
}
|
||||
}
|
||||
|
||||
private async _save() {
|
||||
|
||||
@@ -33,15 +33,15 @@ export interface EnergySettingsBatteryDialogParams {
|
||||
export interface EnergySettingsGasDialogParams {
|
||||
source?: GasSourceTypeEnergyPreference;
|
||||
allowedGasUnitClass?: EnergyGasUnitClass;
|
||||
metadata?: StatisticsMetaData;
|
||||
gas_sources: GasSourceTypeEnergyPreference[];
|
||||
statsMetadata?: Record<string, StatisticsMetaData>;
|
||||
saveCallback: (source: GasSourceTypeEnergyPreference) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface EnergySettingsWaterDialogParams {
|
||||
source?: WaterSourceTypeEnergyPreference;
|
||||
metadata?: StatisticsMetaData;
|
||||
water_sources: WaterSourceTypeEnergyPreference[];
|
||||
statsMetadata?: Record<string, StatisticsMetaData>;
|
||||
saveCallback: (source: WaterSourceTypeEnergyPreference) => Promise<void>;
|
||||
}
|
||||
|
||||
|
||||
@@ -135,7 +135,6 @@ class HaConfigHardwareAll extends LitElement {
|
||||
|
||||
return html`<ha-code-editor
|
||||
mode="yaml"
|
||||
.hass=${this.hass}
|
||||
.value=${dump(device.attributes, { indent: 2 })}
|
||||
read-only
|
||||
></ha-code-editor>`;
|
||||
|
||||
-2
@@ -2,8 +2,6 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import "../../../../../../components/input/ha-input";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
|
||||
-2
@@ -2,8 +2,6 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import "../../../../../../components/input/ha-input";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
|
||||
-2
@@ -2,8 +2,6 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import "../../../../../../components/input/ha-input";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
|
||||
-2
@@ -2,8 +2,6 @@ import { LitElement, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import { fireEvent } from "../../../../../../common/dom/fire_event";
|
||||
import "../../../../../../components/ha-icon-next";
|
||||
import "../../../../../../components/ha-md-list-item";
|
||||
import "../../../../../../components/ha-md-list";
|
||||
import type { HomeAssistant } from "../../../../../../types";
|
||||
import { sharedStyles } from "./matter-add-device-shared-styles";
|
||||
|
||||
|
||||
@@ -107,7 +107,6 @@ export class MQTTConfigPanel extends LitElement {
|
||||
mode="jinja2"
|
||||
autocomplete-entities
|
||||
autocomplete-icons
|
||||
.hass=${this.hass}
|
||||
.value=${this._payload}
|
||||
@value-changed=${this._handlePayload}
|
||||
dir="ltr"
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { LitElement, html, nothing, css } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { customElement, state } from "lit/decorators";
|
||||
import type { TemplateResult } from "lit";
|
||||
import type { ContextType } from "@lit/context";
|
||||
import { consume } from "@lit/context";
|
||||
import { dump } from "js-yaml";
|
||||
import { fireEvent } from "../../../../../common/dom/fire_event";
|
||||
import type { HomeAssistant } from "../../../../../types";
|
||||
import "../../../../../components/ha-code-editor";
|
||||
import "../../../../../components/ha-dialog";
|
||||
import { internationalizationContext } from "../../../../../data/context";
|
||||
|
||||
export interface SSDPRawDataDialogParams {
|
||||
key: string;
|
||||
@@ -14,12 +16,14 @@ export interface SSDPRawDataDialogParams {
|
||||
|
||||
@customElement("dialog-ssdp-raw-data")
|
||||
class DialogSSDPRawData extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@state() private _params?: SSDPRawDataDialogParams;
|
||||
|
||||
@state() private _open = false;
|
||||
|
||||
@state()
|
||||
@consume({ context: internationalizationContext, subscribe: true })
|
||||
private _i18n!: ContextType<typeof internationalizationContext>;
|
||||
|
||||
public async showDialog(params: SSDPRawDataDialogParams): Promise<void> {
|
||||
this._params = params;
|
||||
this._open = true;
|
||||
@@ -42,13 +46,12 @@ class DialogSSDPRawData extends LitElement {
|
||||
return html`
|
||||
<ha-dialog
|
||||
.open=${this._open}
|
||||
header-title=${`${this.hass.localize("ui.panel.config.ssdp.raw_data_title")}: ${this._params.key}`}
|
||||
header-title=${`${this._i18n.localize("ui.panel.config.ssdp.raw_data_title")}: ${this._params.key}`}
|
||||
@closed=${this._dialogClosed}
|
||||
>
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
.value=${dump(this._params.data)}
|
||||
.hass=${this.hass}
|
||||
read-only
|
||||
autofocus
|
||||
></ha-code-editor>
|
||||
|
||||
@@ -35,13 +35,7 @@ class ZHADeviceZigbeeInfo extends LitElement {
|
||||
}
|
||||
|
||||
return html`
|
||||
<ha-code-editor
|
||||
mode="yaml"
|
||||
read-only
|
||||
.hass=${this.hass}
|
||||
.value=${this._signature}
|
||||
dir="ltr"
|
||||
>
|
||||
<ha-code-editor mode="yaml" read-only .value=${this._signature} dir="ltr">
|
||||
</ha-code-editor>
|
||||
`;
|
||||
}
|
||||
|
||||
-3
@@ -89,9 +89,6 @@ export class ZWaveJsAddNodeSelectMethod extends LitElement {
|
||||
display: block;
|
||||
padding: 16px;
|
||||
}
|
||||
ha-md-list {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@ import memoizeOne from "memoize-one";
|
||||
import { fireEvent } from "../../../common/dom/fire_event";
|
||||
import "../../../components/entity/ha-entities-picker";
|
||||
import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-picture-upload";
|
||||
import type { HaPictureUpload } from "../../../components/ha-picture-upload";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/input/ha-input";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import { adminChangeUsername } from "../../../data/auth";
|
||||
import type { PersonMutableParams } from "../../../data/person";
|
||||
import type { User } from "../../../data/user";
|
||||
@@ -163,7 +163,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
@change=${this._pictureChanged}
|
||||
></ha-picture-upload>
|
||||
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass!.localize(
|
||||
"ui.panel.config.person.detail.allow_login"
|
||||
@@ -183,7 +183,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
this._user.is_owner)}
|
||||
.checked=${this._userId}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
|
||||
${this._renderUserFields()}
|
||||
${this._deviceTrackersAvailable(this.hass)
|
||||
@@ -279,7 +279,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
return html`
|
||||
${!user.system_generated
|
||||
? html`
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.person.detail.username"
|
||||
@@ -299,12 +299,12 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing}
|
||||
${!user.system_generated && this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.person.detail.password"
|
||||
@@ -324,10 +324,10 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing}
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.person.detail.local_access_only"
|
||||
@@ -344,8 +344,8 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize("ui.panel.config.person.detail.admin")}</span
|
||||
>
|
||||
@@ -360,7 +360,7 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -560,10 +560,8 @@ class DialogPersonDetail extends LitElement implements HassDialog {
|
||||
margin-bottom: 16px;
|
||||
--file-upload-image-border-radius: var(--ha-border-radius-circle);
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
|
||||
@@ -325,7 +325,6 @@ export class HaSceneEditor extends PreventUnsavedMixin(
|
||||
|
||||
private _renderYamlMode() {
|
||||
return html` <ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._config}
|
||||
@value-changed=${this._yamlChanged}
|
||||
@editor-save=${this._saveScene}
|
||||
|
||||
@@ -458,7 +458,6 @@ export class HaScriptEditor extends SubscribeMixin(
|
||||
`
|
||||
: this.mode === "yaml"
|
||||
? html`<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${this._preprocessYaml()}
|
||||
.readOnly=${this.readOnly}
|
||||
disable-fullscreen
|
||||
|
||||
@@ -69,7 +69,6 @@ export default class HaScriptFieldEditor extends LitElement {
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${yamlValue}
|
||||
@value-changed=${this._onYamlChange}
|
||||
></ha-yaml-editor>`
|
||||
|
||||
@@ -87,7 +87,6 @@ export default class HaScriptFieldSelectorEditor extends LitElement {
|
||||
</ha-alert>`
|
||||
: nothing}
|
||||
<ha-yaml-editor
|
||||
.hass=${this.hass}
|
||||
.defaultValue=${data}
|
||||
@value-changed=${this._onYamlChange}
|
||||
></ha-yaml-editor>`
|
||||
|
||||
@@ -267,7 +267,6 @@ export class HaScriptTrace extends LitElement {
|
||||
: this._view === "config"
|
||||
? html`
|
||||
<ha-trace-config
|
||||
.hass=${this.hass}
|
||||
.trace=${this._trace}
|
||||
></ha-trace-config>
|
||||
`
|
||||
@@ -283,7 +282,6 @@ export class HaScriptTrace extends LitElement {
|
||||
: this._view === "blueprint"
|
||||
? html`
|
||||
<ha-trace-blueprint-config
|
||||
.hass=${this.hass}
|
||||
.trace=${this._trace}
|
||||
></ha-trace-blueprint-config>
|
||||
`
|
||||
|
||||
@@ -7,11 +7,11 @@ import "../../../components/ha-button";
|
||||
import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../../components/ha-switch";
|
||||
import "../../../components/input/ha-input";
|
||||
import type { HaInput } from "../../../components/input/ha-input";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import { createAuthForUser } from "../../../data/auth";
|
||||
import type { User } from "../../../data/user";
|
||||
import {
|
||||
@@ -157,7 +157,7 @@ export class DialogAddUser extends LitElement {
|
||||
"ui.panel.config.users.add_user.password_not_match"
|
||||
)}
|
||||
></ha-input>
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_access_only"
|
||||
@@ -173,8 +173,8 @@ export class DialogAddUser extends LitElement {
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize("ui.panel.config.users.editor.admin")}</span
|
||||
>
|
||||
@@ -188,7 +188,7 @@ export class DialogAddUser extends LitElement {
|
||||
.checked=${this._isAdmin}
|
||||
@change=${this._adminChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
${!this._isAdmin
|
||||
? html`
|
||||
<ha-alert alert-type="info">
|
||||
@@ -320,10 +320,8 @@ export class DialogAddUser extends LitElement {
|
||||
display: flex;
|
||||
padding: 8px 0;
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -9,11 +9,11 @@ import "../../../components/ha-dialog";
|
||||
import "../../../components/ha-dialog-footer";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-label";
|
||||
import "../../../components/ha-md-list-item";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import "../../../components/ha-switch";
|
||||
import "../../../components/input/ha-input";
|
||||
import type { HaInput } from "../../../components/input/ha-input";
|
||||
import "../../../components/item/ha-row-item";
|
||||
import { adminChangeUsername } from "../../../data/auth";
|
||||
import {
|
||||
computeUserBadges,
|
||||
@@ -111,7 +111,7 @@ class DialogUserDetail extends LitElement {
|
||||
"ui.panel.config.users.editor.name"
|
||||
)}
|
||||
></ha-input>
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.username"
|
||||
@@ -131,14 +131,14 @@ class DialogUserDetail extends LitElement {
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
${
|
||||
!user.system_generated && this.hass.user?.is_owner
|
||||
? html`
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.password"
|
||||
@@ -158,11 +158,11 @@ class DialogUserDetail extends LitElement {
|
||||
</ha-icon-button>
|
||||
`
|
||||
: nothing}
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
`
|
||||
: nothing
|
||||
}
|
||||
<ha-md-list-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.active"
|
||||
@@ -179,8 +179,8 @@ class DialogUserDetail extends LitElement {
|
||||
.checked=${this._isActive}
|
||||
@change=${this._activeChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.local_access_only"
|
||||
@@ -197,8 +197,8 @@ class DialogUserDetail extends LitElement {
|
||||
.checked=${this._localOnly}
|
||||
@change=${this._localOnlyChanged}
|
||||
></ha-switch>
|
||||
</ha-md-list-item>
|
||||
<ha-md-list-item>
|
||||
</ha-row-item>
|
||||
<ha-row-item>
|
||||
<span slot="headline"
|
||||
>${this.hass.localize(
|
||||
"ui.panel.config.users.editor.admin"
|
||||
@@ -216,7 +216,7 @@ class DialogUserDetail extends LitElement {
|
||||
@change=${this._adminChanged}
|
||||
></ha-switch>
|
||||
</ha-switch>
|
||||
</ha-md-list-item>
|
||||
</ha-row-item>
|
||||
${
|
||||
!this._isAdmin && !user.system_generated
|
||||
? html`
|
||||
@@ -398,10 +398,8 @@ class DialogUserDetail extends LitElement {
|
||||
.secondary {
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
ha-md-list-item {
|
||||
--md-list-item-leading-space: 0;
|
||||
--md-list-item-trailing-space: 0;
|
||||
--md-item-overflow: visible;
|
||||
ha-row-item {
|
||||
--ha-row-item-padding-inline: 0;
|
||||
}
|
||||
.badge-container {
|
||||
margin-top: 4px;
|
||||
|
||||
@@ -129,12 +129,7 @@ const dataMinusKeysRender = (
|
||||
<span slot="header"
|
||||
>${hass.localize("ui.panel.config.voice_assistants.debug.raw")}</span
|
||||
>
|
||||
<ha-yaml-editor
|
||||
readOnly
|
||||
autoUpdate
|
||||
.hass=${hass}
|
||||
.value=${result}
|
||||
></ha-yaml-editor>
|
||||
<ha-yaml-editor readOnly autoUpdate .value=${result}></ha-yaml-editor>
|
||||
</ha-expansion-panel>`
|
||||
: "";
|
||||
};
|
||||
@@ -242,7 +237,6 @@ export class AssistPipelineDebug extends LitElement {
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
auto-update
|
||||
.hass=${this.hass}
|
||||
.value=${content}
|
||||
></ha-yaml-editor>
|
||||
</ha-expansion-panel>
|
||||
@@ -271,7 +265,6 @@ export class AssistPipelineDebug extends LitElement {
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
auto-update
|
||||
.hass=${this.hass}
|
||||
.value=${content.tool_calls}
|
||||
></ha-yaml-editor>
|
||||
</ha-expansion-panel>
|
||||
@@ -535,7 +528,6 @@ export class AssistPipelineDebug extends LitElement {
|
||||
<ha-yaml-editor
|
||||
read-only
|
||||
auto-update
|
||||
.hass=${this.hass}
|
||||
.value=${this.pipelineRun}
|
||||
></ha-yaml-editor>
|
||||
</ha-expansion-panel>
|
||||
|
||||
@@ -392,7 +392,7 @@ class PanelHome extends LitElement {
|
||||
gap: var(--ha-space-2);
|
||||
position: fixed;
|
||||
top: var(--header-height, 56px);
|
||||
left: var(--mdc-drawer-width, 0px);
|
||||
left: var(--ha-sidebar-width, 0px);
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import {
|
||||
mdiSkipNext,
|
||||
mdiSkipPrevious,
|
||||
mdiStop,
|
||||
mdiVolumeHigh,
|
||||
mdiVolumeMinus,
|
||||
mdiVolumeOff,
|
||||
mdiVolumePlus,
|
||||
} from "@mdi/js";
|
||||
import type { PropertyValues } from "lit";
|
||||
@@ -56,6 +58,7 @@ const MEDIA_PLAYER_PLAYBACK_CONTROLS_FEATURES: Record<
|
||||
media_next_track: [MediaPlayerEntityFeature.NEXT_TRACK],
|
||||
volume_down: [MediaPlayerEntityFeature.VOLUME_STEP],
|
||||
volume_up: [MediaPlayerEntityFeature.VOLUME_STEP],
|
||||
volume_mute: [MediaPlayerEntityFeature.VOLUME_MUTE],
|
||||
};
|
||||
|
||||
export const supportsMediaPlayerPlaybackControl = (
|
||||
@@ -280,6 +283,16 @@ class HuiMediaPlayerPlaybackCardFeature
|
||||
buttons.push({ icon: mdiVolumePlus, action: "volume_up" });
|
||||
}
|
||||
break;
|
||||
case "volume_mute":
|
||||
if (supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_MUTE)) {
|
||||
buttons.push({
|
||||
icon: stateObj.attributes.is_volume_muted
|
||||
? mdiVolumeOff
|
||||
: mdiVolumeHigh,
|
||||
action: "volume_mute",
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -315,6 +328,14 @@ class HuiMediaPlayerPlaybackCardFeature
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "volume_mute") {
|
||||
this.hass!.callService("media_player", "volume_mute", {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
is_volume_muted: !this._stateObj.attributes.is_volume_muted,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.hass!.callService("media_player", action, {
|
||||
entity_id: this._stateObj.entity_id,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { html, LitElement, nothing } from "lit";
|
||||
import { mdiVolumeHigh, mdiVolumeOff } from "@mdi/js";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { computeDomain } from "../../../common/entity/compute_domain";
|
||||
import { supportsFeature } from "../../../common/entity/supports-feature";
|
||||
import { clamp } from "../../../common/number/clamp";
|
||||
import "../../../components/ha-control-button";
|
||||
import "../../../components/ha-control-number-buttons";
|
||||
import "../../../components/ha-svg-icon";
|
||||
import { forwardHaptic } from "../../../data/haptics";
|
||||
import { isUnavailableState } from "../../../data/entity/entity";
|
||||
import {
|
||||
MediaPlayerEntityFeature,
|
||||
@@ -84,14 +88,21 @@ class HuiMediaPlayerVolumeButtonsCardFeature
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const stateObj = this._stateObj;
|
||||
const disabled = isUnavailableState(stateObj.state);
|
||||
const showMute =
|
||||
(this._config.show_mute_button ?? true) &&
|
||||
supportsFeature(stateObj, MediaPlayerEntityFeature.VOLUME_MUTE);
|
||||
const isMuted = stateObj.attributes.is_volume_muted;
|
||||
|
||||
const position =
|
||||
this._stateObj.attributes.volume_level != null
|
||||
? Math.round(this._stateObj.attributes.volume_level * 100)
|
||||
stateObj.attributes.volume_level != null
|
||||
? Math.round(stateObj.attributes.volume_level * 100)
|
||||
: undefined;
|
||||
|
||||
return html`
|
||||
<ha-control-number-buttons
|
||||
.disabled=${!this._stateObj || isUnavailableState(this._stateObj.state)}
|
||||
.disabled=${disabled}
|
||||
.locale=${this.hass.locale}
|
||||
min="0"
|
||||
max="100"
|
||||
@@ -100,6 +111,22 @@ class HuiMediaPlayerVolumeButtonsCardFeature
|
||||
unit="%"
|
||||
@value-changed=${this._valueChanged}
|
||||
></ha-control-number-buttons>
|
||||
${showMute
|
||||
? html`
|
||||
<ha-control-button
|
||||
class="mute"
|
||||
.label=${this.hass.localize(
|
||||
`ui.card.media_player.${isMuted ? "media_volume_unmute" : "media_volume_mute"}`
|
||||
)}
|
||||
.disabled=${disabled}
|
||||
@click=${this._toggleMute}
|
||||
>
|
||||
<ha-svg-icon
|
||||
.path=${isMuted ? mdiVolumeOff : mdiVolumeHigh}
|
||||
></ha-svg-icon>
|
||||
</ha-control-button>
|
||||
`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -112,8 +139,34 @@ class HuiMediaPlayerVolumeButtonsCardFeature
|
||||
});
|
||||
}
|
||||
|
||||
private _toggleMute(ev: Event) {
|
||||
ev.stopPropagation();
|
||||
forwardHaptic(this, "light");
|
||||
this.hass!.callService("media_player", "volume_mute", {
|
||||
entity_id: this._stateObj!.entity_id,
|
||||
is_volume_muted: !this._stateObj!.attributes.is_volume_muted,
|
||||
});
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return cardFeatureStyles;
|
||||
return [
|
||||
cardFeatureStyles,
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--feature-button-spacing);
|
||||
}
|
||||
ha-control-number-buttons {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.mute {
|
||||
width: var(--feature-height);
|
||||
height: var(--feature-height);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ export const MEDIA_PLAYER_PLAYBACK_CONTROLS = [
|
||||
"media_next_track",
|
||||
"volume_down",
|
||||
"volume_up",
|
||||
"volume_mute",
|
||||
] as const;
|
||||
|
||||
export type MediaPlayerPlaybackControl =
|
||||
@@ -86,6 +87,7 @@ export interface MediaPlayerVolumeSliderCardFeatureConfig {
|
||||
export interface MediaPlayerVolumeButtonsCardFeatureConfig {
|
||||
type: "media-player-volume-buttons";
|
||||
step?: number;
|
||||
show_mute_button?: boolean;
|
||||
}
|
||||
|
||||
export interface MediaPlayerSoundModeCardFeatureConfig {
|
||||
|
||||
@@ -91,17 +91,12 @@ export function getSuggestedMax(
|
||||
return suggestedMax;
|
||||
}
|
||||
|
||||
function createYAxisLabelFormatter(locale: FrontendLocaleData) {
|
||||
let previousValue: number | undefined;
|
||||
|
||||
return (value: number): string => {
|
||||
const maximumFractionDigits = Math.max(
|
||||
1,
|
||||
-Math.floor(Math.log10(Math.abs(value - (previousValue ?? value) || 1)))
|
||||
);
|
||||
previousValue = value;
|
||||
return formatNumber(value, locale, { maximumFractionDigits });
|
||||
};
|
||||
function createYAxisLabelFormatter(
|
||||
locale: FrontendLocaleData,
|
||||
fractionDigits: number
|
||||
) {
|
||||
return (value: number): string =>
|
||||
formatNumber(value, locale, { maximumFractionDigits: fractionDigits });
|
||||
}
|
||||
|
||||
export function getCommonOptions(
|
||||
@@ -113,7 +108,8 @@ export function getCommonOptions(
|
||||
compareStart?: Date,
|
||||
compareEnd?: Date,
|
||||
formatTotal?: (total: number) => string,
|
||||
detailedDailyData = false
|
||||
detailedDailyData = false,
|
||||
yAxisFractionDigits = 1
|
||||
): ECOption {
|
||||
const suggestedPeriod = getSuggestedPeriod(start, end, detailedDailyData);
|
||||
const suggestedMax = getSuggestedMax(suggestedPeriod, end, detailedDailyData);
|
||||
@@ -152,7 +148,7 @@ export function getCommonOptions(
|
||||
align: "left",
|
||||
},
|
||||
axisLabel: {
|
||||
formatter: createYAxisLabelFormatter(locale),
|
||||
formatter: createYAxisLabelFormatter(locale, yAxisFractionDigits),
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user