config-editor-card/config-editor-card.js

365 lines
9.5 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

console.info("Config Editor 5.0.0");
const LitElement = window.LitElement || Object.getPrototypeOf(customElements.get("hui-masonry-view") );
const html = LitElement.prototype.html;
const css = LitElement.prototype.css;
class ConfigEditor extends LitElement {
static get properties() {
return {
_hass: {type: Object},
code: {type: String},
fileList: {type: Array},
openedFile: {type: String},
infoLine: {type: String},
alertLine: {type: String},
edit: {},
};
}
constructor() {
super();
this.code = '';
this.fileList = [];
this.openedFile = '';
this.infoLine = '';
this.alertLine = '';
}
static get styles() {
return css`
textarea{
width:98%;
height:80vh;
padding:5px;
overflow-wrap:normal;
white-space:pre}
.top{
min-height:calc(95vh - var(--header-height,56px))}
.pin,.filebar{
display:flex}
.pin label{
cursor:pointer}
.right{text-align:right;
flex-grow:1}
.right button{
font-family:Times,serif;
font-weight:bold}
.pin,.bar{
position:-webkit-sticky;
position:sticky;
z-index:2;}
.pin{
top:0;
background:var(--secondary-background-color,silver)}
.bar{
bottom:0;
z-index:2;
background:var(--app-header-background-color,gray);
color:var(--app-header-text-color,white);
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis}
.bar i{
background:#ff7a81;
cursor:pointer}
.bar select{
flex-grow:1;
text-overflow:ellipsis;
width:100%;
overflow:hidden}
`;
}
render(){
const targetver=5;
if(!this._hass){return html``;}
if(this.fileList.length<1){
this.openedFile = this.localGet('Open')||'';
this.edit.ext = this.localGet('Ext')||'yaml';
this.edit.basic = this.localGet('Basic')||'';
if(this.fileList = JSON.parse(this.localGet('List'+this.edit.ext))){
if(this.extOk(this.openedFile)){
setTimeout(this.oldText, 500, this);
}
}else{this.List();}
}
const hver=this.edit.cver;
const dlink=html`<a href="https://github.com/junkfix/config-editor">download</a>`;
return html`
<ha-card>
${(!this._hass.config.components.includes("config_editor")) ? html`<div>Missing 'config_editor:' in configuration.yaml ${dlink}</div>` : ''}
${(hver && hver != targetver) ? html`<div>Please ${dlink} upgrade from ${hver} to ${targetver}</div>` : ''}
<div class="top">
<div class="pin">
<div class="left"><button @click="${this.reLoad}">Reload</button></div>
<div class="right">
<button @click="${e=>this.txtSize(0)}">-</button>
<button @click="${e=>this.txtSize(2)}">A</button>
<button @click="${e=>this.txtSize(1)}">+</button>
<select @change=${this.extChange}>
${["yaml","py","json","conf","js","txt","log","jinja","all"].map(value =>
html`<option ?selected=${value === this.edit.ext }
value=${value}>${value.toUpperCase()}</option>`)}
</select>
<label>Basic Editor<input type="checkbox" ?checked=${this.edit.basic=='1'}
name="basic" value="1" @change=${this.basicChange}></label>
</div>
</div>
${(this.edit.basic || this.edit.coder ) ?
html`<textarea rows="10" ?readonly=${!0==this.edit.readonly}
@change=${this.updateText} id="code" @keydown=${this.saveKey}>${this.code}</textarea>`:
html`<ha-code-editor id="code" mode="yaml" ?readOnly=${!0==this.edit.readonly}
@keydown=${this.saveKey} .hass=${this._hass} @value-changed=${this.updateText}
dir="ltr" autocomplete-entities autocomplete-icons></ha-code-editor>`}
</div>
${this.edit.hidefooter ? '' : html`
<div class="bar">
<div>${this.alertLine}</div>
<div class="filebar">${!this.edit.readonly ?
html`<button @click="${this.Save}">Save</button>`:''}
<select @change=${this.Load}>
${[''].concat(this.fileList).map(value =>
html`<option ?selected=${value === this.openedFile}
value=${value}>${value}</option>`)}
</select>
<button @click="${this.List}">Get List</button>
</div>
<code>#${this.infoLine}</code>
</div>`}
</ha-card>
`;
}
txtSize(e){
if(e<3){
if(e>1){
this.edit.size=100;
}else if(e>0){
this.edit.size+=5;
}else{
this.edit.size-=5;
}
this.localSet('Size', this.edit.size);
this.infoLine = 'size: '+this.edit.size;
}
this.renderRoot.querySelector('#code').style.fontSize=this.edit.size+'%';
}
extChange(e){
this.edit.ext = e.target.value;
this.localSet('Ext', this.edit.ext);
this.openedFile = '';
this.oldText(this);
this.List();
}
basicChange(){
this.edit.basic = this.edit.basic?'':'1';
this.localSet('Basic', this.edit.basic);
this.reLoad();
}
updateText(e) {
e.stopPropagation();
this.code = this.edit.basic ? e.target.value : e.detail.value;
if(this.openedFile){this.localSet('Text', this.code);}
}
Unsave(){
this.code = this.localGet('Unsaved');
this.renderRoot.querySelector('#code').value=this.code;
this.localSet('Unsaved','');
this.alertLine = '';
this.Toast("Loaded from browser",1500);
}
localGet(e){
return localStorage.getItem('config_editor'+e);
}
localSet(k,v){
localStorage.setItem('config_editor'+k,v);
}
cmd(action, data, file){
return this._hass.callWS({type: "config_editor/ws", action: action,
data: data, file: file, ext: this.edit.ext, depth: this.edit.depth});
}
saveList(){
this.localSet('List'+this.edit.ext, JSON.stringify(this.fileList));
}
reLoad(e){
this.Load({target:{value:this.openedFile},reload:1});
}
oldText(dhis){
dhis.Load({target:{value:dhis.openedFile}});
}
saveKey(e) {
if((e.key == 'S' || e.key == 's' ) && (e.ctrlKey || e.metaKey)){
e.preventDefault();
this.Save();
return false;
}
return true;
}
Toast(message, duration){
const e = new Event("hass-notification",
{bubbles: true, cancelable: false, composed: true});
e.detail = {message, duration, dismissable: true,
//action: {text:"Save",action:()=>{this.sureSave();}},
};
document.querySelector("home-assistant").dispatchEvent(e);
}
//sureSave(){console.log(this.openedFile);}
async Coder(){
const c="ha-yaml-editor";
if(!customElements.get(c)){
await customElements.whenDefined("partial-panel-resolver");
const p = document.createElement('partial-panel-resolver');
p.hass = {panels: [{url_path: "tmp", component_name: "config"}]};
p._updateRoutes();
await p.routerOptions.routes.tmp.load();
const d=document.createElement("ha-panel-config");
await d.routerOptions.routes.automation.load();
}
const a=document.createElement(c);
this.edit.coder=0;
if(!a){
this.localSet('Basic', 1);
console.error('failed '+c);
}
this.render();
}
async List(){
this.infoLine = 'List Loading...';
const e=await this.cmd('list','','');
if(e){
this.edit.cver = e.cver;
this.infoLine = e.msg;
this.fileList = e.file.slice().sort();
this.saveList();
if(this.extOk(this.openedFile)){
setTimeout(this.oldText, 500, this);
}
}
}
async Load(x) {
if(x.target.value == this.openedFile && this.code && !x.hasOwnProperty('reload')){return;}
if(this.edit.orgCode.trim() != this.code.trim()){
if(!confirm("Switch without Saving?")){x.target.value = this.openedFile; return;}
}
this.code = ''; this.renderRoot.querySelector('#code').value='';this.infoLine = '';
this.openedFile = x.target.value;
if(this.openedFile){
this.infoLine = 'Loading: '+this.openedFile;
const e=await this.cmd('load','',this.openedFile);
if(e){
this.edit.cver = e.cver;
this.openedFile = e.file;
this.infoLine = e.msg;
this.Toast(this.infoLine,1000);
const uns={f:this.localGet('Open'),
d:this.localGet('Text')};
if(uns.f == this.openedFile && uns.d && uns.d != e.data){
this.localSet('Unsaved', uns.d);
this.alertLine = html`<i @click="${this.Unsave}"> 
Load unsaved from browser </i>`;
}else{
this.localSet('Text','');this.alertLine = '';
}
this.renderRoot.querySelector('#code').value=e.data;
this.code = e.data;
}
}
this.edit.orgCode = this.code;
this.localSet('Open', this.openedFile);
this.txtSize(3);
}
extOk(f){
if(f.length && (this.edit.ext=='all' || f.endsWith("."+this.edit.ext) )){return 1;}
return 0;
}
async Save() {
if(this.renderRoot.querySelector('#code').value != this.code || this.edit.readonly){
this.infoLine='Something not right!';
return;
}
let savenew=0;
if(!this.openedFile && this.code){
this.openedFile=prompt("type abc."+this.edit.ext+" or folder/abc."+this.edit.ext);
savenew=1;
}
if(this.extOk(this.openedFile)){
if(!confirm("Save?")){if(savenew){this.openedFile='';}return;}
if(!this.code){this.infoLine=''; this.infoLine = 'Text is empty!'; return;}
this.infoLine = 'Saving: '+this.openedFile;
const e=await this.cmd('save', this.code, this.openedFile);
if(e){
this.infoLine = e.msg;
this.Toast(this.infoLine,2000);
if(e.msg.includes('Saved:')){
this.localSet('Text','');
if(savenew){
this.fileList.unshift(this.openedFile);
this.saveList();
}
}
}
}else{this.openedFile='';}
this.edit.orgCode = this.code;
}
getCardSize() {
return 5;
}
setConfig(config) {
this.edit = {file: '', hidefooter: false, readonly: false, basic: false, size: 0, depth: 2, ext: '', orgCode: '', coder: 1, cver: 0, ...config};
if(this.edit.file){
const f=this.edit.file.split('.')[1];
if(f){
this.localSet('Open', this.edit.file);
this.localSet('Ext', f);
}
}
if(!this.edit.size){
this.edit.size=Number(this.localGet('Size'))||100;
}
this.Coder();
}
set hass(hass) {
this._hass = hass;
}
shouldUpdate(changedProps) {
for(const e of ['code','openedFile','fileList','infoLine','alertLine','edit']) {
if(changedProps.has(e)){return true;}
}
}
} customElements.define('config-editor-card', ConfigEditor);
window.customCards = window.customCards || [];
window.customCards.push({
type: 'config-editor-card',
name: 'Config Editor Card',
preview: false,
description: 'Basic editor for configuration.yaml'
});