Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

20241127.1 #23045

Merged
merged 7 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion build-scripts/gulp/locale-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ const outDir = join(paths.build_dir, "locale-data");

const INTL_POLYFILLS = {
"intl-datetimeformat": "DateTimeFormat",
"intl-durationFormat": "DurationFormat",
"intl-displaynames": "DisplayNames",
"intl-listformat": "ListFormat",
"intl-numberformat": "NumberFormat",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "home-assistant-frontend"
version = "20241127.0"
version = "20241127.1"
license = {text = "Apache-2.0"}
description = "The Home Assistant frontend"
readme = "README.md"
Expand Down
30 changes: 7 additions & 23 deletions src/common/datetime/format_duration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { DurationFormat } from "@formatjs/intl-durationformat";
import type { DurationInput } from "@formatjs/intl-durationformat/src/types";
import memoizeOne from "memoize-one";
import type { HaDurationData } from "../../components/ha-duration-input";
Expand Down Expand Up @@ -49,7 +48,7 @@ export const formatNumericDuration = (

const formatDurationLongMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
new Intl.DurationFormat(locale.language, {
style: "long",
})
);
Expand All @@ -61,7 +60,7 @@ export const formatDurationLong = (

const formatDigitalDurationMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
new Intl.DurationFormat(locale.language, {
style: "digital",
hoursDisplay: "auto",
})
Expand All @@ -72,50 +71,42 @@ export const formatDurationDigital = (
duration: HaDurationData
) => formatDigitalDurationMem(locale).format(duration);

export const DURATION_UNITS = ["ms", "s", "min", "h", "d"] as const;
export const DURATION_UNITS = ["s", "min", "h", "d"] as const;

type DurationUnit = (typeof DURATION_UNITS)[number];

const formatDurationDayMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
new Intl.DurationFormat(locale.language, {
style: "narrow",
daysDisplay: "always",
})
);

const formatDurationHourMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
new Intl.DurationFormat(locale.language, {
style: "narrow",
hoursDisplay: "always",
})
);

const formatDurationMinuteMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
new Intl.DurationFormat(locale.language, {
style: "narrow",
minutesDisplay: "always",
})
);

const formatDurationSecondMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
new Intl.DurationFormat(locale.language, {
style: "narrow",
secondsDisplay: "always",
})
);

const formatDurationMillisecondMem = memoizeOne(
(locale: FrontendLocaleData) =>
new DurationFormat(locale.language, {
style: "narrow",
millisecondsDisplay: "always",
})
);

export const formatDuration = (
locale: FrontendLocaleData,
duration: string,
Expand Down Expand Up @@ -164,13 +155,6 @@ export const formatDuration = (
};
return formatDurationSecondMem(locale).format(input);
}
case "ms": {
const milliseconds = Math.floor(value);
const input: DurationInput = {
milliseconds,
};
return formatDurationMillisecondMem(locale).format(input);
}
default:
throw new Error("Invalid duration unit");
}
Expand Down
9 changes: 5 additions & 4 deletions src/data/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@ export interface IntegrationManifest {
loggers?: string[];
quality_scale?:
| "bronze"
| "silver"
| "gold"
| "internal"
| "platinum"
| "silver"
| "custom"
| "no_score";
| "no_score"
| "internal"
| "legacy"
| "custom";
iot_class:
| "assumed_state"
| "cloud_polling"
Expand Down
39 changes: 39 additions & 0 deletions src/data/integration_quality_scale.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { mdiContentSave, mdiMedal, mdiTrophy } from "@mdi/js";
import { mdiHomeAssistant } from "../resources/home-assistant-logo-svg";
import type { LocalizeKeys } from "../common/translations/localize";

/**
* Map integration quality scale to icon and translation key.
*/
export const QUALITY_SCALE_MAP: Record<
string,
{ icon: string; translationKey: LocalizeKeys }
> = {
bronze: {
icon: mdiMedal,
translationKey: "ui.panel.config.integrations.config_entry.bronze_quality",
},
silver: {
icon: mdiMedal,
translationKey: "ui.panel.config.integrations.config_entry.silver_quality",
},
gold: {
icon: mdiMedal,
translationKey: "ui.panel.config.integrations.config_entry.gold_quality",
},
platinum: {
icon: mdiTrophy,
translationKey:
"ui.panel.config.integrations.config_entry.platinum_quality",
},
internal: {
icon: mdiHomeAssistant,
translationKey:
"ui.panel.config.integrations.config_entry.internal_integration",
},
legacy: {
icon: mdiContentSave,
translationKey:
"ui.panel.config.integrations.config_entry.legacy_integration",
},
};
81 changes: 44 additions & 37 deletions src/panels/config/integrations/ha-config-integration-page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
mdiDownload,
mdiFileCodeOutline,
mdiHandExtendedOutline,
mdiMedal,
mdiOpenInNew,
mdiPackageVariant,
mdiPlayCircleOutline,
Expand All @@ -23,7 +22,6 @@ import {
mdiRenameBox,
mdiShapeOutline,
mdiStopCircleOutline,
mdiTrophy,
mdiWeb,
mdiWrench,
} from "@mdi/js";
Expand Down Expand Up @@ -107,9 +105,7 @@ import { documentationUrl } from "../../../util/documentation-url";
import { fileDownload } from "../../../util/file_download";
import type { DataEntryFlowProgressExtended } from "./ha-config-integrations";
import { showAddIntegrationDialog } from "./show-add-integration-dialog";

type MedalColor = "gold" | "silver" | "bronze" | "platinum";
const MEDAL_COLORS = ["bronze", "silver", "gold", "platinum"];
import { QUALITY_SCALE_MAP } from "../../../data/integration_quality_scale";

export const renderConfigEntryError = (
hass: HomeAssistant,
Expand Down Expand Up @@ -344,36 +340,30 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
? html`<div class="version">${this._manifest.version}</div>`
: nothing}
${this._manifest?.quality_scale &&
MEDAL_COLORS.includes(this._manifest.quality_scale)
Object.keys(QUALITY_SCALE_MAP).includes(
this._manifest.quality_scale
)
? html`
<div class="quality-scale integration-info">
<ha-svg-icon
class=${`${this._manifest.quality_scale}-medal`}
.path=${this._manifest.quality_scale === "platinum"
? mdiTrophy
: mdiMedal}
class=${`${this._manifest.quality_scale}-quality`}
.path=${QUALITY_SCALE_MAP[
this._manifest.quality_scale
].icon}
></ha-svg-icon>
<span>
<a
href=${documentationUrl(
this.hass,
`/docs/quality_scale/#-${this._manifest.quality_scale}`
)}
rel="noopener noreferrer"
target="_blank"
>
${this.hass.localize(
`ui.panel.config.integrations.config_entry.${this._manifest.quality_scale as MedalColor}_quality`,
{
quality_scale: html`
<a
href=${documentationUrl(
this.hass,
`/docs/quality_scale/#${this._manifest.quality_scale}-`
)}
rel="noopener noreferrer"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.integrations.config_entry.quality_scale"
)}
</a>
`,
}
QUALITY_SCALE_MAP[this._manifest.quality_scale]
.translationKey
)}
</span>
</a>
</div>
`
: nothing}
Expand All @@ -383,9 +373,18 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
class="warning"
path=${mdiPackageVariant}
></ha-svg-icon>
${this.hass.localize(
"ui.panel.config.integrations.config_entry.custom_integration"
)}
<a
href=${documentationUrl(
this.hass,
`/docs/quality_scale/#-custom`
)}
rel="noopener noreferrer"
target="_blank"
>
${this.hass.localize(
"ui.panel.config.integrations.config_entry.custom_integration"
)}
</a>
</div>`
: nothing}
${this._manifest?.iot_class?.startsWith("cloud_")
Expand Down Expand Up @@ -1496,6 +1495,7 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
.logo-container {
display: flex;
justify-content: center;
margin-bottom: 8px;
}
.version {
padding-top: 8px;
Expand Down Expand Up @@ -1538,17 +1538,24 @@ class HaConfigIntegrationPage extends SubscribeMixin(LitElement) {
100%;
animation: shimmer 2.5s infinite;
}
ha-svg-icon.bronze-medal {
ha-svg-icon.bronze-quality {
color: #cd7f32;
}
ha-svg-icon.silver-medal {
ha-svg-icon.silver-quality {
color: silver;
}
ha-svg-icon.gold-medal {
ha-svg-icon.gold-quality {
color: gold;
}
ha-svg-icon.platinum-medal {
color: #d9d9d9;
ha-svg-icon.platinum-quality {
color: #727272;
}
ha-svg-icon.internal-quality {
color: var(--primary-color);
}
ha-svg-icon.legacy-quality {
color: var(--mdc-theme-text-icon-on-background, rgba(0, 0, 0, 0.38));
animation: unset;
}
ha-md-list-item {
position: relative;
Expand Down
13 changes: 7 additions & 6 deletions src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -4505,8 +4505,10 @@
}
},
"custom_integration": "Custom integration",
"internal_integration": "Internal integration",
"legacy_integration": "Legacy integration",
"custom_overwrites_core": "Custom integration that replaces a core component",
"depends_on_cloud": "Depends on Internet connection",
"depends_on_cloud": "Requires Internet",
"yaml_only": "This integration cannot be setup from the UI",
"no_config_flow": "This integration was not set up from the UI",
"disabled_polling": "Automatic polling for updated data disabled",
Expand All @@ -4521,11 +4523,10 @@
"setup_in_progress": "Initializing"
},
"open_configuration_url": "Visit device",
"bronze_quality": "Bronze on our {quality_scale}",
"silver_quality": "Silver on our {quality_scale}",
"gold_quality": "Gold on our {quality_scale}",
"platinum_quality": "Platinum on our {quality_scale}",
"quality_scale": "quality scale"
"bronze_quality": "Bronze quality",
"silver_quality": "Silver quality",
"gold_quality": "Gold quality",
"platinum_quality": "Platinum quality"
},
"config_flow": {
"success": "Success",
Expand Down
11 changes: 9 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { DurationFormatConstructor } from "@formatjs/intl-durationformat/src/types";
import type {
Auth,
Connection,
Expand All @@ -22,15 +23,15 @@ import type { Themes } from "./data/ws-themes";
import type { ExternalMessaging } from "./external_app/external_messaging";

declare global {
/* eslint-disable no-var, no-redeclare */
/* eslint-disable no-var */
var __DEV__: boolean;
var __DEMO__: boolean;
var __BUILD__: "modern" | "legacy";
var __VERSION__: string;
var __STATIC_PATH__: string;
var __BACKWARDS_COMPAT__: boolean;
var __SUPERVISOR__: boolean;
/* eslint-enable no-var, no-redeclare */
/* eslint-enable no-var */

interface Window {
// Custom panel entry point url
Expand Down Expand Up @@ -64,6 +65,12 @@ declare global {
interface ImportMeta {
url: string;
}

// Intl.DurationFormat is not yet part of the TypeScript standard
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Intl {
const DurationFormat: DurationFormatConstructor;
}
}

export interface ValueChangedEvent<T> extends CustomEvent {
Expand Down
10 changes: 1 addition & 9 deletions test/common/datetime/format_duration.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "@formatjs/intl-durationformat/polyfill-force";
import { assert, describe, it } from "vitest";

import { formatDuration } from "../../../src/common/datetime/format_duration";
import type { FrontendLocaleData } from "../../../src/data/translation";
import {
Expand All @@ -21,14 +21,6 @@ const LOCALE: FrontendLocaleData = {

describe("formatDuration", () => {
it("works", () => {
assert.strictEqual(formatDuration(LOCALE, "0", "ms"), "0ms");
assert.strictEqual(formatDuration(LOCALE, "1", "ms"), "1ms");
assert.strictEqual(formatDuration(LOCALE, "10", "ms"), "10ms");
assert.strictEqual(formatDuration(LOCALE, "100", "ms"), "100ms");
assert.strictEqual(formatDuration(LOCALE, "1000", "ms"), "1,000ms");
assert.strictEqual(formatDuration(LOCALE, "1001", "ms"), "1,001ms");
assert.strictEqual(formatDuration(LOCALE, "65000", "ms"), "65,000ms");

assert.strictEqual(formatDuration(LOCALE, "0.5", "s"), "0s 500ms");
assert.strictEqual(formatDuration(LOCALE, "1", "s"), "1s");
assert.strictEqual(formatDuration(LOCALE, "1.1", "s"), "1s 100ms");
Expand Down