From edd03225d22b56ac299fe2a77dd7a84720bbb440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Lov=C3=A9n?= Date: Mon, 25 Apr 2022 13:02:01 +0000 Subject: [PATCH] Star working on a config panel --- .../browser_mod/browser_mod_panel.js | 158 ++++++++++++++++++ custom_components/browser_mod/const.py | 1 + custom_components/browser_mod/manifest.json | 2 +- custom_components/browser_mod/mod_view.py | 52 +++--- js/{main.ts => browser_mod.ts} | 0 js/browser_mod_panel.ts | 107 ++++++++++++ js/types.ts | 2 +- rollup.config.js | 48 ++++-- 8 files changed, 323 insertions(+), 47 deletions(-) create mode 100644 custom_components/browser_mod/browser_mod_panel.js rename js/{main.ts => browser_mod.ts} (100%) create mode 100644 js/browser_mod_panel.ts diff --git a/custom_components/browser_mod/browser_mod_panel.js b/custom_components/browser_mod/browser_mod_panel.js new file mode 100644 index 0000000..c630aaf --- /dev/null +++ b/custom_components/browser_mod/browser_mod_panel.js @@ -0,0 +1,158 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const t$1=window.ShadowRoot&&(void 0===window.ShadyCSS||window.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,e$2=Symbol(),n$3=new Map;class s$3{constructor(t,n){if(this._$cssResult$=!0,n!==e$2)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t;}get styleSheet(){let e=n$3.get(this.cssText);return t$1&&void 0===e&&(n$3.set(this.cssText,e=new CSSStyleSheet),e.replaceSync(this.cssText)),e}toString(){return this.cssText}}const o$3=t=>new s$3("string"==typeof t?t:t+"",e$2),r$2=(t,...n)=>{const o=1===t.length?t[0]:n.reduce(((e,n,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(n)+t[s+1]),t[0]);return new s$3(o,e$2)},i$1=(e,n)=>{t$1?e.adoptedStyleSheets=n.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):n.forEach((t=>{const n=document.createElement("style"),s=window.litNonce;void 0!==s&&n.setAttribute("nonce",s),n.textContent=t.cssText,e.appendChild(n);}));},S$1=t$1?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const n of t.cssRules)e+=n.cssText;return o$3(e)})(t):t; + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */var s$2;const e$1=window.trustedTypes,r$1=e$1?e$1.emptyScript:"",h$1=window.reactiveElementPolyfillSupport,o$2={toAttribute(t,i){switch(i){case Boolean:t=t?r$1:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t);}return t},fromAttribute(t,i){let s=t;switch(i){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t);}catch(t){s=null;}}return s}},n$2=(t,i)=>i!==t&&(i==i||t==t),l$2={attribute:!0,type:String,converter:o$2,reflect:!1,hasChanged:n$2};class a$1 extends HTMLElement{constructor(){super(),this._$Et=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$Ei=null,this.o();}static addInitializer(t){var i;null!==(i=this.l)&&void 0!==i||(this.l=[]),this.l.push(t);}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((i,s)=>{const e=this._$Eh(s,i);void 0!==e&&(this._$Eu.set(e,s),t.push(e));})),t}static createProperty(t,i=l$2){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(t,i),!i.noAccessor&&!this.prototype.hasOwnProperty(t)){const s="symbol"==typeof t?Symbol():"__"+t,e=this.getPropertyDescriptor(t,s,i);void 0!==e&&Object.defineProperty(this.prototype,t,e);}}static getPropertyDescriptor(t,i,s){return {get(){return this[i]},set(e){const r=this[t];this[i]=e,this.requestUpdate(t,r,s);},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||l$2}static finalize(){if(this.hasOwnProperty("finalized"))return !1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),this.elementProperties=new Map(t.elementProperties),this._$Eu=new Map,this.hasOwnProperty("properties")){const t=this.properties,i=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of i)this.createProperty(s,t[s]);}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(i){const s=[];if(Array.isArray(i)){const e=new Set(i.flat(1/0).reverse());for(const i of e)s.unshift(S$1(i));}else void 0!==i&&s.push(S$1(i));return s}static _$Eh(t,i){const s=i.attribute;return !1===s?void 0:"string"==typeof s?s:"string"==typeof t?t.toLowerCase():void 0}o(){var t;this._$Ep=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Em(),this.requestUpdate(),null===(t=this.constructor.l)||void 0===t||t.forEach((t=>t(this)));}addController(t){var i,s;(null!==(i=this._$Eg)&&void 0!==i?i:this._$Eg=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t));}removeController(t){var i;null===(i=this._$Eg)||void 0===i||i.splice(this._$Eg.indexOf(t)>>>0,1);}_$Em(){this.constructor.elementProperties.forEach(((t,i)=>{this.hasOwnProperty(i)&&(this._$Et.set(i,this[i]),delete this[i]);}));}createRenderRoot(){var t;const s=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return i$1(s,this.constructor.elementStyles),s}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$Eg)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostConnected)||void 0===i?void 0:i.call(t)}));}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$Eg)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostDisconnected)||void 0===i?void 0:i.call(t)}));}attributeChangedCallback(t,i,s){this._$AK(t,s);}_$ES(t,i,s=l$2){var e,r;const h=this.constructor._$Eh(t,s);if(void 0!==h&&!0===s.reflect){const n=(null!==(r=null===(e=s.converter)||void 0===e?void 0:e.toAttribute)&&void 0!==r?r:o$2.toAttribute)(i,s.type);this._$Ei=t,null==n?this.removeAttribute(h):this.setAttribute(h,n),this._$Ei=null;}}_$AK(t,i){var s,e,r;const h=this.constructor,n=h._$Eu.get(t);if(void 0!==n&&this._$Ei!==n){const t=h.getPropertyOptions(n),l=t.converter,a=null!==(r=null!==(e=null===(s=l)||void 0===s?void 0:s.fromAttribute)&&void 0!==e?e:"function"==typeof l?l:null)&&void 0!==r?r:o$2.fromAttribute;this._$Ei=n,this[n]=a(i,t.type),this._$Ei=null;}}requestUpdate(t,i,s){let e=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||n$2)(this[t],i)?(this._$AL.has(t)||this._$AL.set(t,i),!0===s.reflect&&this._$Ei!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,s))):e=!1),!this.isUpdatePending&&e&&(this._$Ep=this._$E_());}async _$E_(){this.isUpdatePending=!0;try{await this._$Ep;}catch(t){Promise.reject(t);}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Et&&(this._$Et.forEach(((t,i)=>this[i]=t)),this._$Et=void 0);let i=!1;const s=this._$AL;try{i=this.shouldUpdate(s),i?(this.willUpdate(s),null===(t=this._$Eg)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostUpdate)||void 0===i?void 0:i.call(t)})),this.update(s)):this._$EU();}catch(t){throw i=!1,this._$EU(),t}i&&this._$AE(s);}willUpdate(t){}_$AE(t){var i;null===(i=this._$Eg)||void 0===i||i.forEach((t=>{var i;return null===(i=t.hostUpdated)||void 0===i?void 0:i.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t);}_$EU(){this._$AL=new Map,this.isUpdatePending=!1;}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$Ep}shouldUpdate(t){return !0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,i)=>this._$ES(i,this[i],t))),this._$EC=void 0),this._$EU();}updated(t){}firstUpdated(t){}}a$1.finalized=!0,a$1.elementProperties=new Map,a$1.elementStyles=[],a$1.shadowRootOptions={mode:"open"},null==h$1||h$1({ReactiveElement:a$1}),(null!==(s$2=globalThis.reactiveElementVersions)&&void 0!==s$2?s$2:globalThis.reactiveElementVersions=[]).push("1.3.1"); + +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +var t;const i=globalThis.trustedTypes,s$1=i?i.createPolicy("lit-html",{createHTML:t=>t}):void 0,e=`lit$${(Math.random()+"").slice(9)}$`,o$1="?"+e,n$1=`<${o$1}>`,l$1=document,h=(t="")=>l$1.createComment(t),r=t=>null===t||"object"!=typeof t&&"function"!=typeof t,d=Array.isArray,u=t=>{var i;return d(t)||"function"==typeof(null===(i=t)||void 0===i?void 0:i[Symbol.iterator])},c=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,v=/-->/g,a=/>/g,f=/>|[ \n \r](?:([^\s"'>=/]+)([ \n \r]*=[ \n \r]*(?:[^ \n \r"'`<>=]|("|')|))|$)/g,_=/'/g,m=/"/g,g=/^(?:script|style|textarea|title)$/i,p=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),$=p(1),b=Symbol.for("lit-noChange"),w=Symbol.for("lit-nothing"),T=new WeakMap,x=(t,i,s)=>{var e,o;const n=null!==(e=null==s?void 0:s.renderBefore)&&void 0!==e?e:i;let l=n._$litPart$;if(void 0===l){const t=null!==(o=null==s?void 0:s.renderBefore)&&void 0!==o?o:null;n._$litPart$=l=new N(i.insertBefore(h(),t),t,void 0,null!=s?s:{});}return l._$AI(t),l},A=l$1.createTreeWalker(l$1,129,null,!1),C=(t,i)=>{const o=t.length-1,l=[];let h,r=2===i?"":"",d=c;for(let i=0;i"===u[0]?(d=null!=h?h:c,p=-1):void 0===u[1]?p=-2:(p=d.lastIndex-u[2].length,o=u[1],d=void 0===u[3]?f:'"'===u[3]?m:_):d===m||d===_?d=f:d===v||d===a?d=c:(d=f,h=void 0);const y=d===f&&t[i+1].startsWith("/>")?" ":"";r+=d===c?s+n$1:p>=0?(l.push(o),s.slice(0,p)+"$lit$"+s.slice(p)+e+y):s+e+(-2===p?(l.push(void 0),i):y);}const u=r+(t[o]||"")+(2===i?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return [void 0!==s$1?s$1.createHTML(u):u,l]};class E{constructor({strings:t,_$litType$:s},n){let l;this.parts=[];let r=0,d=0;const u=t.length-1,c=this.parts,[v,a]=C(t,s);if(this.el=E.createElement(v,n),A.currentNode=this.el.content,2===s){const t=this.el.content,i=t.firstChild;i.remove(),t.append(...i.childNodes);}for(;null!==(l=A.nextNode())&&c.length0){l.textContent=i?i.emptyScript:"";for(let i=0;i2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=w;}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,i=this,s,e){const o=this.strings;let n=!1;if(void 0===o)t=P(this,t,i,0),n=!r(t)||t!==this._$AH&&t!==b,n&&(this._$AH=t);else {const e=t;let l,h;for(t=o[0],l=0;l { + return Math.floor((1+Math.random())*100000).toString(16).substring(1); + }; + if(window['fully'] && typeof fully.getDeviceId === "function") + localStorage[ID_STORAGE_KEY] = fully.getDeviceId(); + else + localStorage[ID_STORAGE_KEY] = `${s4()}${s4()}-${s4()}${s4()}`; + } + return localStorage[ID_STORAGE_KEY]; +} +let deviceID = _deviceID(); + +const setDeviceID = (id) => { + if(id === null) return; + if(id === "clear") { + localStorage.removeItem(ID_STORAGE_KEY); + } else { + localStorage[ID_STORAGE_KEY] = id; + } + deviceID = _deviceID(); +}; + +const params = new URLSearchParams(window.location.search); +if(params.get('deviceID')) { + setDeviceID(params.get('deviceID')); +} + +class BrowserModPanel extends s { + render() { + return $ ` + + + + +
Browser Mod Settingss
+
+
+ + + +
+ The device ID is a unique identifier for your browser/device + combination. + +
+
+
+ Update +
+
+ + +
+
+

Cool function

+ +
+ Enabling this will cause cool stuff to happen. +
+

Another function

+ +
+ Enabling this will cause less cool stuff to happen. +
+
+
+
+ `; + } + static get styles() { + return [ + ...customElements.get("ha-config-dashboard").styles, + r$2 ` + :host { + --app-header-background-color: var(--sidebar-background-color); + --app-header-text-color: var(--sidebar-text-color); + --app-header-border-bottom: 1px solid var(--divider-color); + } + .card-actions { + display: flex; + } + .spacer { + flex-grow: 1; + } + ha-textfield { + width: 250px; + display: block; + margin-top: 8px; + } + .option { + display: flex; + margin-top: 16px; + } + .option h3 { + flex-grow: 1; + margin: 0; + } + .option ha-switch { + margin-top: 0.25em; + margin-right: 7px; + margin-left: 0.5em; + } + `, + ]; + } +} +const loadDevTools = async () => { + var _a, _b, _c, _d, _e, _f, _g; + if (customElements.get("ha-config-dashboard")) + return; + const ppResolver = document.createElement("partial-panel-resolver"); + const routes = ppResolver.getRoutes([ + { + component_name: "config", + url_path: "a", + }, + ]); + await ((_c = (_b = (_a = routes === null || routes === void 0 ? void 0 : routes.routes) === null || _a === void 0 ? void 0 : _a.a) === null || _b === void 0 ? void 0 : _b.load) === null || _c === void 0 ? void 0 : _c.call(_b)); + const configRouter = document.createElement("ha-panel-config"); + await ((_g = (_f = (_e = (_d = configRouter === null || configRouter === void 0 ? void 0 : configRouter.routerOptions) === null || _d === void 0 ? void 0 : _d.routes) === null || _e === void 0 ? void 0 : _e.dashboard) === null || _f === void 0 ? void 0 : _f.load) === null || _g === void 0 ? void 0 : _g.call(_f)); + await customElements.whenDefined("ha-config-dashboard"); +}; +loadDevTools().then(() => { + customElements.define("browser-mod-panel", BrowserModPanel); +}); diff --git a/custom_components/browser_mod/const.py b/custom_components/browser_mod/const.py index 9a49b0f..fb3f446 100644 --- a/custom_components/browser_mod/const.py +++ b/custom_components/browser_mod/const.py @@ -1,6 +1,7 @@ DOMAIN = "browser_mod" FRONTEND_SCRIPT_URL = "/browser_mod.js" +SETTINGS_PANEL_URL = "/browser_mod_panel.js" DATA_EXTRA_MODULE_URL = "frontend_extra_module_url" diff --git a/custom_components/browser_mod/manifest.json b/custom_components/browser_mod/manifest.json index 022aa6f..75828cb 100644 --- a/custom_components/browser_mod/manifest.json +++ b/custom_components/browser_mod/manifest.json @@ -2,7 +2,7 @@ "domain": "browser_mod", "name": "Browser mod", "documentation": "https://github.com/thomasloven/hass-browser_mod/blob/master/README.md", - "dependencies": ["websocket_api", "http", "frontend"], + "dependencies": ["panel_custom", "websocket_api", "http", "frontend"], "codeowners": [], "requirements": [], "version": "1.5.3", diff --git a/custom_components/browser_mod/mod_view.py b/custom_components/browser_mod/mod_view.py index 8c487ea..37e772a 100644 --- a/custom_components/browser_mod/mod_view.py +++ b/custom_components/browser_mod/mod_view.py @@ -1,37 +1,29 @@ -from aiohttp import web -from homeassistant.components.http import HomeAssistantView - -from .const import FRONTEND_SCRIPT_URL, DATA_EXTRA_MODULE_URL +from .const import FRONTEND_SCRIPT_URL, DATA_EXTRA_MODULE_URL, SETTINGS_PANEL_URL def setup_view(hass): url_set = hass.data[DATA_EXTRA_MODULE_URL] url_set.add(FRONTEND_SCRIPT_URL) - hass.http.register_view(ModView(hass, FRONTEND_SCRIPT_URL)) + hass.components.frontend.async_register_built_in_panel( + component_name="custom", + sidebar_title="Browser Mod", + sidebar_icon="mdi:server", + frontend_url_path="browser-mod", + require_admin=True, + config={ + "_panel_custom": { + "name": "browser-mod-panel", + "js_url": SETTINGS_PANEL_URL, + } + }, + ) - -class ModView(HomeAssistantView): - - name = "browser_mod_script" - requires_auth = False - - def __init__(self, hass, url): - self.url = url - self.config_dir = hass.config.path() - - async def get(self, request): - path = "{}/custom_components/browser_mod/browser_mod.js".format(self.config_dir) - - filecontent = "" - - try: - with open(path, mode="r", encoding="utf-8", errors="ignore") as localfile: - filecontent = localfile.read() - localfile.close() - except Exception: - pass - - return web.Response( - body=filecontent, content_type="text/javascript", charset="utf-8" - ) + hass.http.register_static_path( + FRONTEND_SCRIPT_URL, + hass.config.path("custom_components/browser_mod/browser_mod.js"), + ) + hass.http.register_static_path( + SETTINGS_PANEL_URL, + hass.config.path("custom_components/browser_mod/browser_mod_panel.js"), + ) diff --git a/js/main.ts b/js/browser_mod.ts similarity index 100% rename from js/main.ts rename to js/browser_mod.ts diff --git a/js/browser_mod_panel.ts b/js/browser_mod_panel.ts new file mode 100644 index 0000000..e642c60 --- /dev/null +++ b/js/browser_mod_panel.ts @@ -0,0 +1,107 @@ +import { LitElement, html, css } from "lit"; +import { deviceID } from "card-tools/src/deviceID"; + +class BrowserModPanel extends LitElement { + hass; + narrow; + render() { + return html` + + + + +
Browser Mod Settingss
+
+
+ + + +
+ The device ID is a unique identifier for your browser/device + combination. + +
+
+
+ Update +
+
+ + +
+
+

Cool function

+ +
+ Enabling this will cause cool stuff to happen. +
+

Another function

+ +
+ Enabling this will cause less cool stuff to happen. +
+
+
+
+ `; + } + + static get styles() { + return [ + ...(customElements.get("ha-config-dashboard") as any).styles, + css` + :host { + --app-header-background-color: var(--sidebar-background-color); + --app-header-text-color: var(--sidebar-text-color); + --app-header-border-bottom: 1px solid var(--divider-color); + } + .card-actions { + display: flex; + } + .spacer { + flex-grow: 1; + } + ha-textfield { + width: 250px; + display: block; + margin-top: 8px; + } + .option { + display: flex; + margin-top: 16px; + } + .option h3 { + flex-grow: 1; + margin: 0; + } + .option ha-switch { + margin-top: 0.25em; + margin-right: 7px; + margin-left: 0.5em; + } + `, + ]; + } +} + +const loadDevTools = async () => { + if (customElements.get("ha-config-dashboard")) return; + const ppResolver = document.createElement("partial-panel-resolver"); + const routes = (ppResolver as any).getRoutes([ + { + component_name: "config", + url_path: "a", + }, + ]); + await routes?.routes?.a?.load?.(); + const configRouter = document.createElement("ha-panel-config"); + await (configRouter as any)?.routerOptions?.routes?.dashboard?.load?.(); + await customElements.whenDefined("ha-config-dashboard"); +}; + +loadDevTools().then(() => { + customElements.define("browser-mod-panel", BrowserModPanel); +}); diff --git a/js/types.ts b/js/types.ts index 26199b2..f88440c 100644 --- a/js/types.ts +++ b/js/types.ts @@ -1,6 +1,6 @@ const a = {}; -import { BrowserMod } from "./main"; +import { BrowserMod } from "./browser_mod"; interface FullyKiosk { // Get device info diff --git a/rollup.config.js b/rollup.config.js index a029db0..7654ef8 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,19 +6,37 @@ import babel from "@rollup/plugin-babel"; const dev = process.env.ROLLUP_WATCH; -export default { - input: "js/main.ts", - output: { - file: "custom_components/browser_mod/browser_mod.js", - format: "es", +module.exports = [ + { + input: "js/browser_mod.ts", + output: { + file: "custom_components/browser_mod/browser_mod.js", + format: "es", + }, + plugins: [ + nodeResolve(), + json(), + typescript(), + babel({ + exclude: "node_modules/**", + }), + !dev && terser({ format: { comments: false } }), + ], }, - plugins: [ - nodeResolve(), - json(), - typescript(), - babel({ - exclude: "node_modules/**", - }), - !dev && terser({ format: { comments: false } }), - ], -}; + { + input: "js/browser_mod_panel.ts", + output: { + file: "custom_components/browser_mod/browser_mod_panel.js", + format: "es", + }, + plugins: [ + nodeResolve(), + json(), + typescript(), + babel({ + exclude: "node_modules/**", + }), + !dev && terser({ format: { comments: false } }), + ], + }, +];