wallpanell/custom_components/wall_panel/frontend/panel.js
2026-03-25 15:35:16 +03:00

251 lines
6.0 KiB
JavaScript
Executable File

class WallPanelPanel extends HTMLElement {
constructor() {
super();
this._hass = null;
this._panel = null;
this._narrow = false;
this._pollTimer = null;
this._activePanelUrl = '';
this.attachShadow({ mode: 'open' });
}
set hass(hass) {
this._hass = hass;
this._render();
}
set panel(panel) {
this._panel = panel;
this._render();
}
set narrow(narrow) {
this._narrow = Boolean(narrow);
this._render();
}
connectedCallback() {
this._render();
}
disconnectedCallback() {
if (this._pollTimer) {
window.clearInterval(this._pollTimer);
this._pollTimer = null;
}
}
_resolveUrl(rawUrl) {
const value = String(rawUrl || '').trim();
if (!value) {
return '';
}
try {
const url = new URL(value, window.location.href);
if (!url.searchParams.has('embed')) {
url.searchParams.set('embed', '1');
}
if (!url.searchParams.has('mode')) {
url.searchParams.set('mode', 'ha');
}
return url.toString();
} catch (error) {
return value;
}
}
_panelConfig() {
const config = this._panel?.config || {};
const customConfig = config._panel_custom || {};
return customConfig.config || customConfig || config;
}
_configUrl() {
return '/api/wall_panel/panel';
}
_legacyConfigUrl() {
const payload = this._panelConfig();
const entryId = String(payload.entry_id || '').trim();
if (!entryId) {
return '';
}
return `/api/wall_panel/config/${encodeURIComponent(entryId)}`;
}
async _fetchPanelUrl() {
const payload = this._panelConfig();
const syncToken = String(payload.sync_token || '').trim();
if (!syncToken) {
return '';
}
const configUrls = [this._configUrl(), this._legacyConfigUrl()].filter(Boolean);
for (const configUrl of configUrls) {
try {
const response = await fetch(configUrl, {
method: 'GET',
headers: {
'X-Wall-Panel-Token': syncToken,
Accept: 'application/json',
},
credentials: 'same-origin',
cache: 'no-store',
});
if (!response.ok) {
continue;
}
const data = await response.json();
const panelUrl = String(data?.panel?.panel_url || data?.panel_url || '').trim();
if (panelUrl) {
return panelUrl;
}
} catch (error) {
continue;
}
}
return '';
}
_renderMessage(title, body, extra = '') {
if (!this.shadowRoot) {
return;
}
this.shadowRoot.innerHTML = `
<style>
:host {
display: block;
width: 100%;
height: 100%;
min-height: 100vh;
background: var(--primary-background-color, #0d0f14);
color: var(--primary-text-color, #fff);
}
.wrap {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
background: var(--primary-background-color, #0d0f14);
}
.message {
display: grid;
place-items: center;
height: 100%;
padding: 24px;
box-sizing: border-box;
text-align: center;
font-family: var(--primary-font-family, sans-serif);
color: var(--secondary-text-color, #b3b8c2);
}
.message strong {
display: block;
color: var(--primary-text-color, #fff);
margin-bottom: 8px;
font-size: 1.1rem;
}
.message code {
display: inline-block;
margin-top: 10px;
padding: 6px 10px;
border-radius: 8px;
background: rgba(255, 255, 255, 0.06);
color: var(--primary-text-color, #fff);
word-break: break-all;
}
</style>
<div class="wrap">
<div class="message">
<div>
<strong>${title}</strong>
<div>${body}</div>
${extra}
</div>
</div>
</div>
`;
}
_renderIframe(panelUrl) {
if (!this.shadowRoot) {
return;
}
const wrap = this.shadowRoot.querySelector('.wrap');
if (!wrap) {
return;
}
const iframe = document.createElement('iframe');
iframe.src = panelUrl;
iframe.loading = 'eager';
iframe.referrerPolicy = 'no-referrer';
iframe.allow = 'autoplay; fullscreen; picture-in-picture';
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = '0';
iframe.style.display = 'block';
iframe.style.background = 'transparent';
wrap.replaceChildren(iframe);
this._activePanelUrl = panelUrl;
}
async _tryAttachPanel() {
const payload = this._panelConfig();
const initialUrl = this._resolveUrl(
payload.panel_url || payload.ingress_url || ''
);
if (initialUrl && initialUrl === this._activePanelUrl) {
return;
}
if (initialUrl && !(window.location.protocol === 'https:' && initialUrl.startsWith('http://'))) {
this._renderIframe(initialUrl);
return;
}
const panelUrl = this._resolveUrl(await this._fetchPanelUrl());
if (panelUrl && panelUrl === this._activePanelUrl) {
return;
}
if (panelUrl && !(window.location.protocol === 'https:' && panelUrl.startsWith('http://'))) {
this._renderIframe(panelUrl);
return;
}
const configUrl = this._configUrl();
this._renderMessage(
'Waiting for Wall Panel',
'The add-on will publish a secure HTTPS ingress URL here.',
configUrl ? `<code>${configUrl}</code>` : ''
);
}
_render() {
if (!this.shadowRoot) {
return;
}
if (this._pollTimer) {
window.clearInterval(this._pollTimer);
this._pollTimer = null;
}
this._tryAttachPanel();
this._pollTimer = window.setInterval(() => {
this._tryAttachPanel();
}, 2000);
}
}
if (!customElements.get('wall-panel-panel')) {
customElements.define('wall-panel-panel', WallPanelPanel);
}