mirror of
https://github.com/home-assistant/frontend.git
synced 2025-11-24 02:07:19 +00:00
Compare commits
13 Commits
add_winter
...
sec_pypi_p
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a45fbc488 | ||
|
|
4674384f7d | ||
|
|
2e7c95a8cc | ||
|
|
9630b175a1 | ||
|
|
be319503f7 | ||
|
|
aef3cb1c36 | ||
|
|
8535ee0694 | ||
|
|
b8110d1a45 | ||
|
|
19e9de39c5 | ||
|
|
f22f01e513 | ||
|
|
3f86f144b5 | ||
|
|
4efef5ed16 | ||
|
|
cac7ae2a40 |
13
.github/workflows/release.yaml
vendored
13
.github/workflows/release.yaml
vendored
@@ -19,8 +19,11 @@ jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
environment: pypi
|
||||
permissions:
|
||||
contents: write # Required to upload release assets
|
||||
id-token: write # For "Trusted Publisher" to PyPi
|
||||
if: github.repository_owner == 'home-assistant'
|
||||
steps:
|
||||
- name: Checkout the repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
@@ -46,14 +49,18 @@ jobs:
|
||||
run: ./script/translations_download
|
||||
env:
|
||||
LOKALISE_TOKEN: ${{ secrets.LOKALISE_TOKEN }}
|
||||
|
||||
- name: Build and release package
|
||||
run: |
|
||||
python3 -m pip install twine build
|
||||
export TWINE_USERNAME="__token__"
|
||||
export TWINE_PASSWORD="${{ secrets.TWINE_TOKEN }}"
|
||||
python3 -m pip install build
|
||||
export SKIP_FETCH_NIGHTLY_TRANSLATIONS=1
|
||||
script/release
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e # v1.13.0
|
||||
with:
|
||||
skip-existing: true
|
||||
|
||||
- name: Upload release assets
|
||||
uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,7 @@ A compact, accessible dropdown menu for choosing actions or settings. `ha-dropdo
|
||||
### Example usage (composition)
|
||||
|
||||
```html
|
||||
<ha-dropdown open>
|
||||
<ha-dropdown>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
|
||||
@@ -28,7 +28,7 @@ export class DemoHaDropdown extends LitElement {
|
||||
<div class=${mode}>
|
||||
<ha-card header="ha-button in ${mode}">
|
||||
<div class="card-content">
|
||||
<ha-dropdown open>
|
||||
<ha-dropdown>
|
||||
<ha-button slot="trigger" with-caret>Dropdown</ha-button>
|
||||
|
||||
<ha-dropdown-item>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/bin/sh
|
||||
# Pushes a new version to PyPi.
|
||||
|
||||
# Stop on errors
|
||||
set -e
|
||||
@@ -12,5 +11,4 @@ yarn install
|
||||
script/build_frontend
|
||||
|
||||
rm -rf dist home_assistant_frontend.egg-info
|
||||
python3 -m build
|
||||
python3 -m twine upload dist/*.whl --skip-existing
|
||||
python3 -m build -q
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { storage } from "../common/decorators/storage";
|
||||
import type { HomeAssistant } from "../types";
|
||||
|
||||
interface Snowflake {
|
||||
id: number;
|
||||
left: number;
|
||||
size: number;
|
||||
duration: number;
|
||||
delay: number;
|
||||
blur: number;
|
||||
}
|
||||
|
||||
@customElement("ha-snowflakes")
|
||||
export class HaSnowflakes extends LitElement {
|
||||
@property({ attribute: false }) public hass?: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@storage({ key: "winter-mode", state: true, subscribe: true })
|
||||
@state()
|
||||
private _enabled = true;
|
||||
|
||||
@state() private _snowflakes: Snowflake[] = [];
|
||||
|
||||
private _maxSnowflakes = 50;
|
||||
|
||||
private _generateSnowflakes() {
|
||||
if (!this._enabled) {
|
||||
this._snowflakes = [];
|
||||
return;
|
||||
}
|
||||
|
||||
const snowflakes: Snowflake[] = [];
|
||||
for (let i = 0; i < this._maxSnowflakes; i++) {
|
||||
snowflakes.push({
|
||||
id: i,
|
||||
left: Math.random() * 100, // Random position from 0-100%
|
||||
size: Math.random() * 12 + 8, // Random size between 8-20px
|
||||
duration: Math.random() * 8 + 8, // Random duration between 8-16s
|
||||
delay: Math.random() * 8, // Random delay between 0-8s
|
||||
blur: Math.random() * 1, // Random blur between 0-1px
|
||||
});
|
||||
}
|
||||
this._snowflakes = snowflakes;
|
||||
}
|
||||
|
||||
protected willUpdate(changedProps: Map<string, unknown>) {
|
||||
super.willUpdate(changedProps);
|
||||
if (changedProps.has("_enabled")) {
|
||||
this._generateSnowflakes();
|
||||
}
|
||||
}
|
||||
|
||||
protected render() {
|
||||
if (!this._enabled) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const isDark = this.hass?.themes.darkMode ?? false;
|
||||
|
||||
return html`
|
||||
<div class="snowflakes ${isDark ? "dark" : "light"}" aria-hidden="true">
|
||||
${this._snowflakes.map(
|
||||
(flake) => html`
|
||||
<div
|
||||
class="snowflake ${this.narrow && flake.id >= 30
|
||||
? "hide-narrow"
|
||||
: ""}"
|
||||
style="
|
||||
left: ${flake.left}%;
|
||||
font-size: ${flake.size}px;
|
||||
animation-duration: ${flake.duration}s;
|
||||
animation-delay: ${flake.delay}s;
|
||||
filter: blur(${flake.blur}px);
|
||||
"
|
||||
>
|
||||
❄
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
static readonly styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 9999;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.snowflakes {
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 110%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.snowflake {
|
||||
position: absolute;
|
||||
top: -10%;
|
||||
opacity: 0.7;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
animation: fall linear infinite;
|
||||
}
|
||||
|
||||
.light .snowflake {
|
||||
color: #00bcd4;
|
||||
text-shadow:
|
||||
0 0 5px #00bcd4,
|
||||
0 0 10px #00e5ff;
|
||||
}
|
||||
|
||||
.dark .snowflake {
|
||||
color: #fff;
|
||||
text-shadow:
|
||||
0 0 5px rgba(255, 255, 255, 0.8),
|
||||
0 0 10px rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.snowflake.hide-narrow {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@keyframes fall {
|
||||
0% {
|
||||
transform: translateY(-10vh) translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateY(30vh) translateX(10px);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(60vh) translateX(-10px);
|
||||
}
|
||||
75% {
|
||||
transform: translateY(85vh) translateX(10px);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(120vh) translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.snowflake {
|
||||
animation: none;
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-snowflakes": HaSnowflakes;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import { listenMediaQuery } from "../common/dom/media_query";
|
||||
import { toggleAttribute } from "../common/dom/toggle_attribute";
|
||||
import { computeRTLDirection } from "../common/util/compute_rtl";
|
||||
import "../components/ha-drawer";
|
||||
import "../components/ha-snowflakes";
|
||||
import { showNotificationDrawer } from "../dialogs/notifications/show-notification-drawer";
|
||||
import type { HomeAssistant, Route } from "../types";
|
||||
import "./partial-panel-resolver";
|
||||
@@ -51,7 +50,6 @@ export class HomeAssistantMain extends LitElement {
|
||||
this.hass.panels && this.hass.userData && this.hass.systemData;
|
||||
|
||||
return html`
|
||||
<ha-snowflakes .hass=${this.hass} .narrow=${this.narrow}></ha-snowflakes>
|
||||
<ha-drawer
|
||||
.type=${sidebarNarrow ? "modal" : ""}
|
||||
.open=${sidebarNarrow ? this._drawerOpen : false}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
mdiCancel,
|
||||
mdiChevronRight,
|
||||
mdiCog,
|
||||
mdiDelete,
|
||||
mdiDotsVertical,
|
||||
mdiMenuDown,
|
||||
mdiPencilOff,
|
||||
@@ -109,10 +110,11 @@ import { configSections } from "../ha-panel-config";
|
||||
import "../integrations/ha-integration-overflow-menu";
|
||||
import { renderConfigEntryError } from "../integrations/ha-config-integration-page";
|
||||
import { showLabelDetailDialog } from "../labels/show-dialog-label-detail";
|
||||
import { isHelperDomain } from "./const";
|
||||
import { isHelperDomain, type HelperDomain } from "./const";
|
||||
import { showHelperDetailDialog } from "./show-dialog-helper-detail";
|
||||
import { slugify } from "../../../common/string/slugify";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { HELPERS_CRUD } from "../../../data/helpers_crud";
|
||||
import {
|
||||
fetchDiagnosticHandlers,
|
||||
getConfigEntryDiagnosticsDownloadUrl,
|
||||
@@ -451,6 +453,19 @@ export class HaConfigHelpers extends SubscribeMixin(LitElement) {
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(helper.editable && helper.entity
|
||||
? [
|
||||
{
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
path: mdiDelete,
|
||||
label: this.hass.localize("ui.common.delete"),
|
||||
warning: true,
|
||||
action: () => this._deleteHelper(helper),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
>
|
||||
</ha-icon-overflow-menu>
|
||||
@@ -1280,6 +1295,62 @@ ${rejected
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteHelper(helper: HelperItem) {
|
||||
if (!helper.entity_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmed = await showConfirmationDialog(this, {
|
||||
title: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.delete_confirm_title"
|
||||
),
|
||||
text: this.hass.localize(
|
||||
"ui.panel.config.helpers.picker.delete_confirm_text",
|
||||
{ name: helper.name }
|
||||
),
|
||||
confirmText: this.hass.localize("ui.common.delete"),
|
||||
dismissText: this.hass.localize("ui.common.cancel"),
|
||||
destructive: true,
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// For old-style helpers (input_boolean, etc.), use HELPERS_CRUD
|
||||
if (isHelperDomain(helper.type)) {
|
||||
const entityReg = this._entityReg.find(
|
||||
(e) => e.entity_id === helper.entity_id
|
||||
);
|
||||
if (
|
||||
!entityReg?.unique_id ||
|
||||
!isComponentLoaded(this.hass, helper.type)
|
||||
) {
|
||||
throw new Error(
|
||||
this.hass.localize("ui.panel.config.helpers.picker.delete_failed")
|
||||
);
|
||||
}
|
||||
await HELPERS_CRUD[helper.type as HelperDomain].delete(
|
||||
this.hass,
|
||||
entityReg.unique_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// For config entry-based helpers, delete the config entry
|
||||
if (helper.configEntry) {
|
||||
await deleteConfigEntry(this.hass, helper.configEntry.entry_id);
|
||||
}
|
||||
} catch (err: any) {
|
||||
showAlertDialog(this, {
|
||||
text:
|
||||
err.message ||
|
||||
this.hass.localize("ui.panel.config.helpers.picker.delete_failed"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _createHelper() {
|
||||
showHelperDetailDialog(this, {});
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
color:
|
||||
route.route_status === "Active"
|
||||
? primaryColor
|
||||
: style.getPropertyValue("--disabled-color"),
|
||||
: style.getPropertyValue("--dark-primary-color"),
|
||||
type: ["Child", "Parent"].includes(neighbor.relationship)
|
||||
? "solid"
|
||||
: "dotted",
|
||||
@@ -335,7 +335,7 @@ export class ZHANetworkVisualizationPage extends LitElement {
|
||||
symbolSize: 5,
|
||||
lineStyle: {
|
||||
width: 1,
|
||||
color: style.getPropertyValue("--disabled-color"),
|
||||
color: style.getPropertyValue("--dark-primary-color"),
|
||||
type: "dotted",
|
||||
},
|
||||
ignoreForceLayout: true,
|
||||
|
||||
@@ -309,7 +309,20 @@ export class DialogLovelaceDashboardDetail extends LitElement {
|
||||
}
|
||||
this.closeDialog();
|
||||
} catch (err: any) {
|
||||
this._error = { base: err?.message || "Unknown error" };
|
||||
let localizedErrorMessage: string | undefined;
|
||||
if (err?.translation_domain && err?.translation_key) {
|
||||
const localize = await this.hass.loadBackendTranslation(
|
||||
"exceptions",
|
||||
err.translation_domain
|
||||
);
|
||||
localizedErrorMessage = localize(
|
||||
`component.${err.translation_domain}.exceptions.${err.translation_key}.message`,
|
||||
err.translation_placeholders
|
||||
);
|
||||
}
|
||||
this._error = {
|
||||
base: localizedErrorMessage || err?.message || "Unknown error",
|
||||
};
|
||||
} finally {
|
||||
this._submitting = false;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import "./ha-pick-time-zone-row";
|
||||
import "./ha-push-notifications-row";
|
||||
import "./ha-set-suspend-row";
|
||||
import "./ha-set-vibrate-row";
|
||||
import "./ha-set-winter-mode-row";
|
||||
|
||||
@customElement("ha-profile-section-general")
|
||||
class HaProfileSectionGeneral extends LitElement {
|
||||
@@ -241,10 +240,6 @@ class HaProfileSectionGeneral extends LitElement {
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
></ha-set-suspend-row>
|
||||
<ha-set-winter-mode-row
|
||||
.narrow=${this.narrow}
|
||||
.hass=${this.hass}
|
||||
></ha-set-winter-mode-row>
|
||||
${!isMobileClient
|
||||
? html`
|
||||
<ha-enable-shortcuts-row
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import type { TemplateResult } from "lit";
|
||||
import { html, LitElement } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import { storage } from "../../common/decorators/storage";
|
||||
import "../../components/ha-settings-row";
|
||||
import "../../components/ha-switch";
|
||||
import type { HaSwitch } from "../../components/ha-switch";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
|
||||
@customElement("ha-set-winter-mode-row")
|
||||
class HaSetWinterModeRow extends LitElement {
|
||||
@property({ attribute: false }) public hass!: HomeAssistant;
|
||||
|
||||
@property({ type: Boolean }) public narrow = false;
|
||||
|
||||
@storage({ key: "winter-mode", state: true, subscribe: true })
|
||||
@state()
|
||||
private _winterMode = true;
|
||||
|
||||
protected render(): TemplateResult {
|
||||
return html`
|
||||
<ha-settings-row .narrow=${this.narrow}>
|
||||
<span slot="heading">
|
||||
${this.hass.localize("ui.panel.profile.winter_mode.header")}
|
||||
</span>
|
||||
<span slot="description">
|
||||
${this.hass.localize("ui.panel.profile.winter_mode.description")}
|
||||
</span>
|
||||
<ha-switch
|
||||
.checked=${this._winterMode}
|
||||
@change=${this._checkedChanged}
|
||||
></ha-switch>
|
||||
</ha-settings-row>
|
||||
`;
|
||||
}
|
||||
|
||||
private _checkedChanged(ev: Event) {
|
||||
const winterMode = (ev.target as HaSwitch).checked;
|
||||
if (winterMode === this._winterMode) {
|
||||
return;
|
||||
}
|
||||
this._winterMode = winterMode;
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-set-winter-mode-row": HaSetWinterModeRow;
|
||||
}
|
||||
}
|
||||
@@ -3265,7 +3265,10 @@
|
||||
"create_helper": "Create helper",
|
||||
"no_helpers": "Looks like you don't have any helpers yet!",
|
||||
"search": "Search {number} {number, plural,\n one {helper}\n other {helpers}\n}",
|
||||
"error_information": "Error information"
|
||||
"error_information": "Error information",
|
||||
"delete_confirm_title": "Delete helper?",
|
||||
"delete_confirm_text": "Are you sure you want to delete {name}?",
|
||||
"delete_failed": "Failed to delete helper"
|
||||
},
|
||||
"dialog": {
|
||||
"create": "Create",
|
||||
@@ -8618,10 +8621,6 @@
|
||||
"header": "Vibrate",
|
||||
"description": "Enable or disable vibration on this device when controlling devices."
|
||||
},
|
||||
"winter_mode": {
|
||||
"header": "Winter mode",
|
||||
"description": "Add a seasonal touch to your Home Assistant interface."
|
||||
},
|
||||
"enable_shortcuts": {
|
||||
"header": "Keyboard shortcuts",
|
||||
"description": "Enable or disable keyboard shortcuts for performing various actions in the UI."
|
||||
|
||||
Reference in New Issue
Block a user