Initial dashboard
This commit is contained in:
parent
1c697a028b
commit
1af3f59b1a
10 changed files with 380 additions and 3 deletions
137
static/js/dashboard.mjs
Normal file
137
static/js/dashboard.mjs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import { CustomHTMLElement } from './lib/custom_html_element.mjs'
|
||||
|
||||
export class SmonDashboard extends CustomHTMLElement {
|
||||
static {
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.el-grid {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-auto-columns: min-content;
|
||||
grid-gap: 8px 16px;
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<div data-el="grid">
|
||||
</div>
|
||||
`
|
||||
}
|
||||
constructor() {
|
||||
super(true)
|
||||
const script = this.querySelector('script')
|
||||
this.data = JSON.parse(script.textContent)
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
const widgets = []
|
||||
for (const wd of this.data.Widgets) {
|
||||
const widget = SmonWidget.create(wd.Type, wd)
|
||||
widgets.push(widget)
|
||||
}
|
||||
this.elGrid.replaceChildren(...widgets)
|
||||
}
|
||||
}
|
||||
|
||||
class SmonWidget extends CustomHTMLElement {
|
||||
static create(t, data) {
|
||||
switch (t) {
|
||||
case 0: return new SmonWidgetLabel(data)
|
||||
case 1: return new SmonWidgetOnOff(data)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(data) {
|
||||
super(true)
|
||||
this.data = data
|
||||
}
|
||||
|
||||
render() {
|
||||
this.style.gridColumn = this.data.X
|
||||
this.style.gridRow = this.data.Y
|
||||
}
|
||||
}
|
||||
|
||||
class SmonWidgetLabel extends SmonWidget {
|
||||
static {
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<style>
|
||||
.el-label {
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
<div data-el="label"></div>
|
||||
`
|
||||
}
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
super.render()
|
||||
this.elLabel.innerText = this.data.Attributes?.Label || '[set Label]'
|
||||
}
|
||||
}
|
||||
|
||||
class SmonWidgetOnOff extends SmonWidget {
|
||||
static {
|
||||
this.tmpl = document.createElement('template')
|
||||
this.tmpl.innerHTML = `
|
||||
<style>
|
||||
:host {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
align-items: center;
|
||||
grid-gap: 8px;
|
||||
|
||||
font-size: 1.25em;
|
||||
}
|
||||
</style>
|
||||
<img data-el="status">
|
||||
<div data-el="label"></div>
|
||||
`
|
||||
}
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
this.render()
|
||||
}
|
||||
|
||||
render() {
|
||||
super.render()
|
||||
|
||||
let img
|
||||
switch (this.data.Attributes.Status) {
|
||||
case 'ON':
|
||||
img = 'widget_light_green.svg'
|
||||
break
|
||||
|
||||
case 'OFF':
|
||||
img = 'widget_light_red.svg'
|
||||
break
|
||||
|
||||
default:
|
||||
img = 'widget_light_off.svg'
|
||||
}
|
||||
|
||||
this.elStatus.setAttribute('src', `/images/${_VERSION}/${img}`)
|
||||
this.elLabel.innerText = this.data.Attributes.Label
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
customElements.define("smon-widget", SmonWidget)
|
||||
customElements.define("smon-widget-label", SmonWidgetLabel)
|
||||
customElements.define("smon-widget-onoff", SmonWidgetOnOff)
|
||||
customElements.define("smon-dashboard", SmonDashboard)
|
||||
51
static/js/lib/custom_html_element.mjs
Normal file
51
static/js/lib/custom_html_element.mjs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/* Use data-el or data-field attribute.
|
||||
* Element with data-el="hum-ding" is accessible as this.elHumDing and fields with
|
||||
* data-field="long-dong" as this.fieldLongDong.
|
||||
*
|
||||
* All field values can be retrieved with fieldValues() and uses the data-field attribute
|
||||
* as LongDong as key.
|
||||
*/
|
||||
|
||||
export class CustomHTMLElement extends HTMLElement {
|
||||
constructor(useShadow) {// {{{
|
||||
super()
|
||||
|
||||
this._fields = new Map()
|
||||
|
||||
const workOn = useShadow ? this.attachShadow({ mode: 'open' }) : this
|
||||
workOn.appendChild(this.constructor.tmpl.content.cloneNode(true))
|
||||
workOn.querySelectorAll('*').forEach(el => {
|
||||
const field = el.dataset.field
|
||||
if (field !== undefined) {
|
||||
const fieldName = this.toElementName('field', field)
|
||||
this[fieldName] = el
|
||||
this._fields.set(this.toElementName('', field), el)
|
||||
}
|
||||
|
||||
const name = el.dataset.el
|
||||
if (name !== undefined) {
|
||||
const elName = this.toElementName('el', name)
|
||||
this[elName] = el
|
||||
el.classList.add('el-' + name)
|
||||
}
|
||||
})
|
||||
}// }}}
|
||||
allFields() {// {{{
|
||||
return this._fields
|
||||
}// }}}
|
||||
fieldValues() {// {{{
|
||||
const state = {}
|
||||
for (const [name, field] of this._fields) {
|
||||
if (field.tagName.toLowerCase() == 'input' && field.getAttribute('type').toLowerCase() == 'checkbox')
|
||||
state[name] = field.checked
|
||||
else
|
||||
state[name] = field.value
|
||||
|
||||
}
|
||||
return state
|
||||
}// }}}
|
||||
toElementName(prefix, str) {// {{{
|
||||
str = prefix + '-' + str
|
||||
return str.replace(/-(id|[a-z])/g, match => match.toUpperCase().replace('-', ''))
|
||||
}// }}}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue