-
Notifications
You must be signed in to change notification settings - Fork 264
feat: add visual editor #1128
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
base: dev
Are you sure you want to change the base?
feat: add visual editor #1128
Changes from 25 commits
bb5366f
fc89200
12f4c86
0d9c7d5
984ffae
8834ab0
9f5ace9
e7276f6
f43e017
4224e50
765fa0a
61c3f6f
00d2c09
05b81a5
f7fb286
83a7661
1ff2815
56f324b
e210a83
0a0f90f
78369e0
b689ee5
aa10d65
cbc8880
e16f430
87d963b
5f82f5f
1fcae0f
86cb9d8
a69556c
138875d
86f544d
100dd42
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,3 +14,4 @@ globals: | |
| window: true | ||
| Event: true | ||
| customElements: true | ||
| document: true | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,127 @@ | ||
| import { mdiClose } from '@mdi/js'; | ||
| import { fireEvent } from 'custom-card-helpers'; | ||
| import { css, html, LitElement } from 'lit-element'; | ||
|
|
||
| export const colorSelector = { | ||
| hex_color: {}, | ||
| }; | ||
|
|
||
| export class CustomColorSelector extends LitElement { | ||
| static get properties() { | ||
| return { | ||
| hass: { attribute: false }, | ||
| selector: { attribute: false }, | ||
| value: {}, | ||
| label: {}, | ||
| }; | ||
| } | ||
|
|
||
| render() { | ||
| return html` | ||
| <div class="color-container"> | ||
| <label id="hex" for="color-input"> | ||
| <span class="label">${this.label}</span> | ||
| <span class="input-wrapper"> | ||
| <div class="overflow"> | ||
| <input class=${this.value ? '' : 'empty'} | ||
| id="color-input" | ||
| @input=${this.valueChanged} | ||
| type="color" | ||
| .value=${this.value ? this.value : '#000000'}> | ||
| </div> | ||
| </span> | ||
| </label> | ||
| ${this.selector.hex_color.clearable ? html` | ||
| <ha-icon-button | ||
| class="clear-button" | ||
| .label=${this.hass.localize('ui.common.clear')} | ||
| .path=${mdiClose} | ||
| @click=${this.clearValue} | ||
| ><ha-icon-button> | ||
| ` : html``} | ||
| </div> | ||
| `; | ||
| } | ||
|
|
||
| valueChanged(ev) { | ||
| const value = (ev.target).value || '#000000'; | ||
| fireEvent(this, 'value-changed', { value }); | ||
| } | ||
|
|
||
| clearValue() { | ||
| fireEvent(this, 'value-changed', { undefined }); | ||
| } | ||
|
|
||
| static get styles() { | ||
| return css` | ||
| #hex { | ||
| display: flex; | ||
| align-items: center; | ||
| margin: 4px 0px; | ||
| flex: 1; | ||
| } | ||
|
|
||
| .input-wrapper { | ||
| width: 48px; | ||
| height: 48px; | ||
| box-sizing: border-box; | ||
| border: 1px solid var(--outline-color); | ||
| position: relative; | ||
| border-radius: 50%; | ||
| } | ||
|
|
||
| #hex:hover .input-wrapper { | ||
| border: 2px solid var(--outline-color); | ||
| } | ||
|
|
||
| .label { | ||
| font-family: var(--mdc-typography-body2-font-family, var(--mdc-typography-font-family, Roboto, sans-serif)); | ||
| color: var(--mdc-theme-text-primary-on-background, rgba(0, 0, 0, .87)); | ||
| font-size: 1em; | ||
| line-height: var(--mdc-typography-body2-line-height, 1.25rem); | ||
| font-weight: var(--mdc-typography-body2-font-weight, 400); | ||
| flex-grow: 1; | ||
| padding-inline-start: 4px; | ||
| } | ||
|
|
||
| .overflow { | ||
| width: 100%; | ||
| height: 100%; | ||
| overflow: hidden; | ||
| display: flex; | ||
| align-items: center; | ||
| justify-content: center; | ||
| border-radius: inherit; | ||
| } | ||
|
|
||
| #hex input { | ||
| min-width: 200%; | ||
| min-height: 200%; | ||
| } | ||
|
|
||
| #hex .empty::before { | ||
| background: | ||
| repeating-conic-gradient( | ||
| var(--secondary-background-color) 0 90deg, | ||
| var(--disabled-text-color) 0 180deg) | ||
| 0 0/40px 40px round; | ||
| content: ''; | ||
| min-width: 200%; | ||
| min-height: 200%; | ||
| display: block; | ||
| } | ||
|
|
||
| .color-container { | ||
| display: flex; | ||
| align-items: center; | ||
| } | ||
|
|
||
| .clear-button { | ||
| --mdc-icon-size: 20px; | ||
| color: var(--input-dropdown-icon-color); | ||
| } | ||
| `; | ||
| } | ||
| } | ||
|
|
||
| customElements.define('ha-selector-hex_color', CustomColorSelector); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,159 @@ | ||
| import { css, html, LitElement } from 'lit-element'; | ||
| import { mdiClose } from '@mdi/js'; | ||
| import { fireEvent } from 'custom-card-helpers'; | ||
| import { localize } from '../../localize/localize'; | ||
| import './colorSelector'; | ||
| import './subPageHeader'; | ||
|
|
||
| const SCHEMA = [ | ||
| { | ||
| name: '', | ||
| type: 'grid', | ||
| schema: [ | ||
| { | ||
| name: 'value', | ||
| selector: { number: { step: 0.1 } }, | ||
| }, | ||
| { | ||
| name: 'color', | ||
| selector: { hex_color: {} }, | ||
| }, | ||
| ], | ||
| }, | ||
| ]; | ||
|
|
||
| class ColorThresholdsEditor extends LitElement { | ||
| static get properties() { | ||
| return { | ||
| hass: { attribute: false }, | ||
| config: { attribute: false }, | ||
| }; | ||
| } | ||
|
|
||
| computeLabel(schema) { | ||
| return localize(`editor.form.color_thresholds.${schema.name}`, this.hass); | ||
| } | ||
|
|
||
| render() { | ||
| if (!this.hass) { | ||
| return html``; | ||
| } | ||
|
|
||
| if (!this.config) { | ||
| return html` | ||
| <mini-graph-card-subpage-header | ||
| adding=true | ||
| .name=${localize('editor.edit_color_thresholds', this.hass)} | ||
| @go-back=${this.goBack} | ||
| @add-row=${this.addRow} | ||
| ></mini-graph-card-subpage-header> | ||
| `; | ||
| } | ||
|
|
||
| return html` | ||
| <mini-graph-card-subpage-header | ||
| adding=true | ||
| .name=${localize('editor.edit_color_thresholds', this.hass)} | ||
| @go-back=${this.goBack} | ||
| @add-row=${this.addRow} | ||
| ></mini-graph-card-subpage-header> | ||
| <div class="thresholds"> | ||
| ${this.config.map((threshold, index) => html` | ||
| <div class="threshold"> | ||
| <ha-expansion-panel outlined> | ||
|
||
| <span slot="header"><span class="circle-color" style="background-color: ${threshold.color}"></span>${threshold.value}</span> | ||
| <ha-form | ||
| .hass=${this.hass} | ||
| .schema=${SCHEMA} | ||
| .data=${threshold} | ||
| .index=${index} | ||
| .computeLabel=${this.computeLabel} | ||
| @value-changed=${this.valueChanged} | ||
| ></ha-form> | ||
| </ha-expansion-panel> | ||
| <ha-icon-button | ||
| .label=${this.hass.localize('ui.components.entity.entity-picker.clear')} | ||
| .path=${mdiClose} | ||
| .index=${index} | ||
| @click=${this.removeRow} | ||
| ></ha-icon-button> | ||
| </div> | ||
| `)} | ||
| </div> | ||
| `; | ||
| } | ||
|
|
||
| goBack() { | ||
| fireEvent(this, 'go-back'); | ||
| } | ||
|
|
||
| removeRow(ev) { | ||
| ev.stopPropagation(); | ||
| if (!this.config || !this.hass) { | ||
| return; | ||
| } | ||
| const index = (ev.currentTarget).index || ''; | ||
| const newColorThresholds = this.config.concat(); | ||
|
|
||
| newColorThresholds.splice(index, 1); | ||
|
|
||
| fireEvent(this, 'config-changed', newColorThresholds); | ||
| } | ||
|
|
||
| addRow(ev) { | ||
| ev.stopPropagation(); | ||
| const value = { value: 0, color: '#000000' }; | ||
| if (!this.config) { | ||
| fireEvent(this, 'config-changed', [value]); | ||
| return; | ||
| } | ||
| fireEvent(this, 'config-changed', [...this.config, value]); | ||
| } | ||
|
|
||
| valueChanged(ev) { | ||
| if (!this.config || !this.hass) { | ||
| return; | ||
| } | ||
| const value = ev.detail.value || ''; | ||
| const index = (ev.target).index || 0; | ||
| const newColorThresholds = this.config.concat(); | ||
|
|
||
| newColorThresholds[index] = value; | ||
|
|
||
| fireEvent(this, 'config-changed', newColorThresholds); | ||
| } | ||
|
|
||
| static get styles() { | ||
| return css` | ||
| .threshold-header { | ||
| display: flex; | ||
| gap: 10px; | ||
| } | ||
|
|
||
| .threshold { | ||
| display: flex; | ||
| align-items: center; | ||
| margin-bottom: 8px; | ||
| } | ||
|
|
||
| ha-expansion-panel > span { | ||
| display: flex; | ||
| gap: 1rem; | ||
| } | ||
|
|
||
| .threshold > ha-expansion-panel { | ||
| flex-grow: 1; | ||
| } | ||
|
|
||
| .circle-color { | ||
| display: block; | ||
| background-color: #ffffff; | ||
| border-radius: 10px; | ||
| width: 20px; | ||
| height: 20px; | ||
| } | ||
| `; | ||
| } | ||
| } | ||
|
|
||
| customElements.define('color-thresholds-editor', ColorThresholdsEditor); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason not to use the color picker provided by hass itself?
If it is the "clearable" feature, it could be handled in the usual hass way by having an enable/disable toggle in front of the color picker.
If it is the hex format, could it be converted "output-only" in the main
valueChangedfunction? I am not familiar enough with the way these things work...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mainly it was the hex format. I've tried to do the convertion on output but this resulted in warnings in console about supplied color value to hass color picker but the selecter was rendering just fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see... I'd guess, we'd have to convert back and forth to not loose colours on each startup of the editor... At least in
setConfig,configChangedandentitiesChanged... Then use[r,g,b]internally in the editor and hex outside...Another idea: How about we allow
[r,g,b]as a new colour input format for the mini-graph-card itself? Better suited for a follow-up PR then, maybe... We'd still loose non-hex colours on first startup of the editor, I assume...I shall test, if losing colours is actually an issue...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did a test, and yes, loosing colours is an issue. Try this:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've addressed this issue in the latest commit